RED    = Color('#FF3333')
GREEN  = Color('#4FFF55')
BLUE   = Color('#585EFF')
YELLOW = Color('#EBFF55')
PURPLE = Color('#FF55D5')
WHITE  = Color('#FFFFFF')
BLACK  = Color('#000000')
COLOR_BLIND_FRAMES  = tuple(Rect(32 * i, 32, 32, 32) for i in range(4, 10))
COLOR_BLIND_SYMBOLS = {
                        id(RED)    : config.get_sprite(COLOR_BLIND_FRAMES[0]),
                        id(BLUE)   : config.get_sprite(COLOR_BLIND_FRAMES[1]),
                        id(GREEN)  : config.get_sprite(COLOR_BLIND_FRAMES[2]),
                        id(YELLOW) : config.get_sprite(COLOR_BLIND_FRAMES[3]),
                        id(PURPLE) : config.get_sprite(COLOR_BLIND_FRAMES[4]),
                        id(WHITE)  : None                                    ,
                       }
COLOR_KEY           = config.SPRITES.get_at((0, config.SPRITES.get_height() - 1))
LIST                = (RED, BLUE, GREEN, YELLOW, PURPLE)
def blend_color(surface, color):
def get_colored_objects(frames, has_alpha=True, color_blind=False, pause_frame=True):
def _rand_color_appear(self): def get_sprite(frame):
def load_image(name):
def load_sound(name):
def load_text(name, lang):
def loop_sound(sound):
def on_off(condition):
def percent_str(var):
def play_music(name):
def set_volume():
def init_music_volume():
def show_fps():
def take_screenshot():
def toggle_frame_limit():
def toggle_fullscreen():
def toggle_pause():
    def __init__(self, *keys):
APP_DATA_WIN = 'APPDATA'
APP_DATA_LIN = 'HOME'
BLIT_FLAGS    = HWSURFACE | HWACCEL | ASYNCBLIT | RLEACCEL
CURSOR_BEEP   = load_sound('cursor.wav')
CURSOR_SELECT = load_sound('select.wav')
DATA_STORE    = join(environ[APP_DATA_WIN if 'win' in sys.platform else APP_DATA_LIN], 'Invasodado')
DEBUG         =  __debug__ and not hasattr(sys, 'frozen') #cx_freeze adds 'frozen' to the sys module
DEPTH         = 8
EARTH         = load_image('earth.png')
ENCODING      = 'utf-8'
EVENTS_OK     = (KEYDOWN, MOUSEBUTTONDOWN, QUIT)
FONT          = pygame.font.Font(join('gfx', 'font.ttf'), 18)
FULL_FLAGS    = FULLSCREEN | HWSURFACE | DOUBLEBUF
GRID_BG       = load_image('bg.png')
ON_OFF        = {
                 'en' : ("Off", "On"),
                 'es' : ("No" , "Sí"),
                }
PAUSE         = load_sound('pause.wav')
SCREEN_DIMS   = tuple(pygame.display.list_modes())
SCREEN_HEIGHT = screen.get_height()
SCREEN_RECT   = Rect((0, 0), screen.get_size())
SCREEN_WIDTH  = screen.get_width()
SPRITES       = load_image('sprites.png')
EARTH = EARTH.subsurface(Rect(0, 0, EARTH.get_width(), EARTH.get_height() / 2))
class Enum: class ParticleEmitter:
    '''
    ParticleEmitters...emit particles.  They are not to act independently;
    ParticleEmitters are to emit particles only when called by whatever
    object holds them.

    ParticleEmitters should not be in Pygame Groups; the objects that hold them
    should call emit().
    '''

    def __init__(self, pool, rect, period=1, group=Particle.group):
        '''
        @ivar period: Frames between emits, e.g. 4 = 1 Particle per 4 frames
        @ivar pool: The ParticlePool where Particles are drawn from
        @ivar rate: Used to count to emits
        @ivar rect: The area that will emit Particles
        '''
        self.group  = group
        self.period = period
        self.pool   = pool
        self.rate   = 0
        self.rect   = rect

    def _release(self, particle):
        '''
        Actually releases a particle

        @param particle: The particle to release
        '''
        rect                  = self.rect
        particle.position     = [
                                 uniform(rect.left, rect.right),
                                 uniform(rect.top , rect.bottom)
                                ]
        particle.rect.topleft = particle.position
        particle.change_state(Particle.STATES.APPEARING)
        particle.add(self.group)

    def emit(self):
        '''
        Takes one step to release a particle.
        '''

        self.rate += 1
        self.rate %= self.period

        if not self.rate:
        #If we've gone a particle release cycle...
            self._release(self.pool.get_particle())
            self.pool.clean()

    def burst(self, amount):
        '''
        Releases a one-shot burst of particles.

        @param amount: No. of particles to release, max is self.pool.amount
        '''
        pool     = self.pool
        _release = self._release
        self.group = Particle.group
        for i in range(amount):
            _release(pool.get_particle())

    actions    = None
    collisions = None class ParticlePool:
    '''
    A pool of particles that multiple emitters can draw from.
    Each pool may hold one type of particle.

    @ivar particles_in: Particles not visible on-screen
    @ivar particles_out: Particles visible on-screen
    '''

    def __init__(self, image, move_func=_p_move, appear_func=_p_appear):
        self.appear_func   = appear_func
        self.image         = image
        self.move_func     = move_func
        self.particles_in  = set()
        self.particles_out = set()

    def get_particle(self):
        '''
        Returns a spare Particle, or creates a new one if none exist.
        '''
        if not self.particles_in:
        #If we don't have any particles to spare...
            self.particles_in.add(Particle(self.image, self.move_func, self.appear_func))

        particle = self.particles_in.pop()
        self.particles_out.add(particle)
        return particle

    def clean(self):
        '''
        Removes all particles that are off-screen from the game, but not from
        memory.
        '''
        particles_to_remove = set(filterfalse(config.SCREEN_RECT.colliderect, self.particles_out))
        self.particles_in.update(particles_to_remove)
        self.particles_out -= particles_to_remove class Particle(GameObject):
    '''
    Tiny bits and pieces used for graphical effects.  Meant for things like
    explosions, sparkles, impacts, etc.

    Not meant to be instantiated alone; should only be held by ParticleEmitters.

    Particles should have some randomization, particularly in initial direction.
    '''
    START_POS = (-100.0, -100.0)
    STATES    = config.Enum(*PARTICLE_STATES)

    def __init__(self, image, move_func=_p_move, appear_func=_p_appear):
        '''
        @ivar move_func: The function that defines motion; called each update()
                         Takes one parameter
        '''
        super().__init__()
        self.appear_func = partial(appear_func, self)
        self.image       = image
        self.move_func   = partial(move_func, self)
        self.position    = list(Particle.START_POS)
        self.rect        = Rect(self.position, self.image.get_size())
        self.state       = Particle.STATES.IDLE

    def appear(self):
        self.appear_func()
        self.change_state(Particle.STATES.ACTIVE)

    def move(self):
        self.move_func()

        if not self.rect.colliderect(config.SCREEN_RECT):
        #If we're no longer on-screen...
            self.change_state(Particle.STATES.LEAVING)

    def leave(self):
        self.kill()
        self.acceleration = [0.0, 0.0]
        self.velocity     = [0.0, 0.0]
        self.position     = list(Particle.START_POS)
        self.rect.topleft = self.position
        self.change_state(Particle.STATES.IDLE)

    actions = {
               STATES.IDLE     : None    ,
               STATES.APPEARING: 'appear',
               STATES.ACTIVE   : 'move'  ,
               STATES.LEAVING  : 'leave' ,
              } DEFAULT_LIVES = 3
MAX_COMBO_TIME = 60  #In frames
def clean_up(): def get_combo_counter(num, pos):
    '''
    Returns a ComboCounter object, creating a new one if need be.
    
    @param num: The number to display
    @param pos: Position on-screen this ComboCounter should appear
    @type num: int
    @type pos: [float, float]
    @return: A new ComboCounter
    @rtype: ComboCounter
    '''
    if not _counters:
        _counters.add(ComboCounter(num, pos))
        
    counter           = _counters.pop()
    counter.position  = list(pos)
    counter.time_left = 0
    counter.image     = hudobject.make_text(str(num), pos=pos, surfaces=True)
    counter.rect      = Rect(pos, counter.image.get_size())
    return counter class GameObject(Sprite, object):
    '''
    @invariant: self.state in self.__class__.STATES
    @
    '''
    actions    = None
    collisions = None
    group      = None

    def __init__(self):
        Sprite.__init__(self)
        self.acceleration = [0.0, 0.0]
        self.next_state   = None
        self.position     = [-300.0, -300.0]
        self.rect         = None
        self.state        = None
        self.velocity     = [0.0, 0.0]

    def on_collide(self, other):
        '''
        Reacts based on the type of the other object.
        
        @param other: The object that we collided with.
        @type other: A subclass of GameObject
        '''
        other_type = other.__class__
        collisions = self.collisions
        if isinstance(collisions, dict):
        #If this object can interact with more than one other type...
            if other_type in collisions and callable(collisions[other_type]):
            #If we can interact with the other object...
                collisions[other_type](self, other)

    def update(self):
        '''
        Acts on this object's current state.
        '''
        assert self.state in self.__class__.actions, \
        "A %s tried to act on an invalid state %s!" % (self, self.state)

        method_name = self.__class__.actions[self.state]
        if method_name and callable(getattr(self, method_name)):
        #If this key has a function for its element...
            getattr(self, method_name)()
            
    def change_state(self, next_state):
        '''
        Queues this object for a state change in the next frame.
        
        @param next_state: The state to switch to, in type(self).STATES.
        @type next_state: The values used in config.Enum to represent constants
        '''
        self.next_state = next_state
        _state_changes.add(self) class ShipBullet(Bullet):
    SPEED     = -8
    START_POS = pygame.Rect(30, config.SCREEN_HEIGHT * 2, 26, 19)
    GROUP     = None

    def __init__(self):
        super().__init__()
        self.hit_enemy = False
        
    def start_moving(self):
        SHOOT.play()
        super().start_moving()
        self.hit_enemy = False

    def move(self):
        '''
        Moves up the screen, seeing if it's hit an enemy or exited.
        '''
        super().move()

        if self.rect.bottom < 0:
        #If above the top of the screen...
            self.change_state(self.__class__.STATES.RESET)

    def kill_enemy(self, other):
        '''
        Kills whatever enemy we collided with.
        '''
        if not self.hit_enemy and other.state in {other.__class__.STATES.ACTIVE, other.__class__.STATES.LOWERING}:
        #If we hit another enemy that's alive...
            self.hit_enemy = True
            gamedata.score += 10
            self.change_state(self.__class__.STATES.RESET)
            other.change_state(other.__class__.STATES.DYING)
            other.column = round(self.position[0] / 32)

    
    collisions = {
                  Enemy: kill_enemy,
                  UFO  : kill_enemy,
                 } class Enemy(GameObject):
    STATES       = config.Enum(*ENEMY_STATES)
    anim         = 0.0
    base_speed   = 0.5
    GROUP        = None
    shoot_odds   = 0.002
    should_flip  = False
    start_time   = None
    velocity     = [0.5, 0.0]

    def __init__(self, form_position):
        super().__init__()
        ### Local variables ####################################################
        the_color = choice(color.LIST)
        the_id    = id(the_color)
        ########################################################################
        
        ### Object Attributes ##################################################
        self.amount_lowered     = 0
        self._anim              = 0.0
        self.color              = the_color
        self.column             = None
        self._form_position     = form_position
        self.current_frame_list = ENEMY_FRAMES_COLOR_BLIND if settings.SETTINGS['color_blind'] else ENEMY_FRAMES
        self.image              = self.current_frame_list[the_id][0]
        self.position           = list(START_POS)
        self.rect               = Rect(START_POS, self.image.get_size())
        self.state              = Enemy.STATES.IDLE
        self.emitter            = ParticleEmitter(color.color_particles[the_id], self.rect, 1)
        ########################################################################
        
        ### Preparation ########################################################
        del self.acceleration, self.velocity
        ########################################################################

    def appear(self):
        self.add(Enemy.GROUP)
        self.position     = [
                             START_POS[0] * (self._form_position[0] + 1) * 1.5,
                             START_POS[1] * (self._form_position[1] + 1) * 1.5,
                            ]
        self.rect.topleft = (self.position[0] + .5, self.position[1] + .5)
        self.color        = choice(color.LIST)
        self.__animate()
        self.emitter.pool = color.color_particles[id(self.color)]
        self.change_state(Enemy.STATES.ACTIVE)

    def move(self):
        self.__animate()
        self.position[0] += Enemy.velocity[0]
        self.rect.topleft = (self.position[0] + .5, self.position[1] + .5)
        
        if uniform(0, 1) < Enemy.shoot_odds and not enemybullet.EnemyBullet.halt:
        #With Enemy.shoot_odds% of firing...
        #TODO: Use another probability distribution
            b             = enemybullet.get_enemy_bullet()
            b.rect.midtop = self.rect.midbottom
            b.position    = list(b.rect.topleft)
            b.add(enemybullet.EnemyBullet.GROUP)

        if not Enemy.should_flip:
        #If the squadron of enemies is not marked to reverse direction...
            if self.rect.left < 0 or self.rect.right > config.SCREEN_WIDTH:
            #If this enemy touches either end of the screen...
                Enemy.should_flip = True

    def die(self):
        balloflight.get_ball(self.rect.topleft, self.column, self.color).add(Enemy.GROUP)
        _hurt.play()
        self.emitter.burst(20)
        self.kill()
        
        self.position      = [-300.0, -300.0]
        self.rect.topleft  = self.position
        self.change_state(Enemy.STATES.IDLE)
        Enemy.velocity[0] += copysign(0.1, Enemy.velocity[0])
        #^ Increase the enemy squadron's speed (copysign() considers direction)

    def lower(self):
        self.__animate()
        self.amount_lowered += 1
        self.position[1]    += 1
        self.rect.top        = self.position[1]
        if self.amount_lowered == LOWER_INCREMENT:
            self.amount_lowered = 0
            self.change_state(Enemy.STATES.ACTIVE)

    def cheer(self):
        self.__animate()
        self.position[1] -= 2 * sin((Enemy.anim/2) - (pi/4) * self._form_position[0])
        self.rect.top = self.position[1] + .5
    
    def __animate(self):
        self._anim = int(3 - abs(Enemy.anim - 3)) % 4
        self.image = self.current_frame_list[id(self.color)][self._anim]

    actions = {
                STATES.APPEARING: 'appear',
                STATES.LOWERING : 'lower' ,
                STATES.ACTIVE   : 'move'  ,
                STATES.DYING    : 'die'   ,
                STATES.IDLE     : None    ,
                STATES.CHEERING : 'cheer' ,
              }
from functools import partial
from itertools import chain
from math      import sin, cos, pi
from random    import gauss, uniform
from pygame import Rect, Surface
from pygame.constants import *
from core              import color
from core              import config
from core.particles    import ParticleEmitter, ParticlePool, Particle
from game              import gamedata
from game.combocounter import get_combo_counter
from game.gameobject   import GameObject
from game.shipbullet   import ShipBullet
from game.enemy import Enemy
import pygame.key





### Functions ##################################################################
def _burst_appear(self):
    self.acceleration[1] = GRAVITY
    self.velocity        = [gauss(0, 25), uniform(-10, -20)]
    
def _radius_appear(self):
    self.progress = 0
    distance_magnitude = gauss(300, 50)
    angle              = uniform(0, -pi)
    self.position[0]   = START_POS.centerx + distance_magnitude * cos(angle)
    self.position[1]   = START_POS.centery + distance_magnitude * sin(angle)
    self.startpos      = tuple(self.position)
    self.rect.topleft  = (self.position[0] + .5, self.position[1] + .5)
    
def _radius_move(self):
    self.progress += 1
    position = self.position
    percent  = self.progress / 30
    
    if percent == 1:
        #If we've reached our target location...

        self.change_state(Particle.STATES.LEAVING)