def dont_check_type(*types):
def dont_check_pair(*pairs):
    def __init__(self, width, height, layer, group_list):
    def update(self):
    def handle_collisions(self):
    def __init__(self, rect, grid):
    def remove_exiting(self):
    def add_entering(self):  #TODO: Optimize!
    def check_collisions(self):
    def __init__(self):
    def update(self, obj1, obj2):
    def reset(self):
    def __lt__(self, other):
class CollisionGrid:
class GridCell:
class Collision: class CollisionGrid:
    '''
    CollisionGrid is a grid meant to be used to easily determine whether or
    not objects within are colliding; only objects within the same cell are
    compared against each other.
    
    CollisionsGrids themselves are not aware of the space they take up on-screen,
    that's the job of the GridCells they hold.
    '''
    def __init__(self, width, height, layer, group_list):
        '''
        @param width: Width of this CollisionGrid in cells
        @param height: Height of this CollisionGrid, also in cells
        @param layer: Only objects whose layer attribute == self.layer can touch
        @param group_list: List of all Pygame groups to check for collisions in
        
        @ivar collisions: List of all collisions that occurred in the last frame
        @ivar grid: The cells of this grid
        @ivar group_list: List of all Pygame groups to check for collisions in
        @ivar layer: Only objects of the same layer may collide
        @ivar spare_collisions: Spare Collisions, so we don't keep creating them
        '''
        ### Local Variables ####################################################
        size      = pygame.display.get_surface().get_size()
        cell_size = (size[0] / width, size[1] / height)
        get_rect  = lambda i, j: Rect((i * cell_size[0], j * cell_size[1]), cell_size)
        ########################################################################
        
        ### Object Attributes ##################################################
        self.collisions = []
        self.grid       = tuple(
                            tuple(GridCell(get_rect(i, j), self) for i in range(width))
                            for j in range(height)
                          )
        self.group_list       = group_list
        self.layer            = layer
        self.spare_collisions = []
        ########################################################################

    def update(self):
        '''
        Removes objects no longer in this cell, and adds ones that just
        entered.  Called every frame.
        '''
        for i in chain.from_iterable(self.grid):
            i.remove_exiting()
            i.add_entering()
            i.check_collisions()

        self.handle_collisions()

    def handle_collisions(self):
        '''
        Handles all collisions, from first to last.
        '''
        collisions = self.collisions
        collisions.sort()

        for i in collisions:
        #For all collisions that have occurred...
            i.obj1.on_collide(i.obj2)
            i.obj2.on_collide(i.obj1)
            i.reset()

        self.spare_collisions += collisions
        del collisions[:] 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 GameState:
    '''
    Consider GameState to be abstract; that is, do not instantiate it!

    GameState is meant to be derived from to represent different screens with
    different objects (e.g. the in-game state would have the player, the blocks,
    etc. while the main menu would have the buttons.

    @ivar fps_timer: Regulates the game's framerate
    @ivar group_list: List of game object groups to process
    @ivar next_state: If not None, the gsm switches to this state
    '''
    group_list = []
    next_state = None

    def __init__(self, *args, **kwargs):
        '''
        Initialization logic is normally executed once here.  We can pass in
        arguments so our game state can run a certain way.  Thus, rather than
        creating a new object or subclass for a state that's slightly different
        than another, we can just pass in an argument.
        '''
        pass
    
    def __del__(self):
        for i in self.group_list: i.empty()

    def events(self, events):
        '''
        Handles external input (mostly from the keyboard).
        
        @param events: List of events to process (filtered if necessary)
        '''
        for e in events:
        #For all input we've received...
            if e.type == KEYDOWN and e.key in self.key_actions:
            #If a key was pressed...
                self.key_actions[e.key]()

    def logic(self):
        '''
        Executes the next step in the game logic (collisions, etc.).
        '''
        gameobject.update()
        for i in self.group_list:
            i.update()

    def render(self):
        '''
        Blits all Sprites (or derived) to the screen.

        Remember, Group.blit() renders Sprites in arbitrary order, so we must
        assign Sprites to groups by layer, then call Group.blit() sequentially.
        '''
        screen.fill(color.BLACK)
        STARS.emit()
        
        for i in self.group_list:
        #For all sprite groups this GameState holds...
            i.draw(screen)

        assert not config.show_fps()
        #^ So this statement is stripped in Release mode.

        config.fps_timer.tick(60 * config._limit_frame)

    def change_state(self, state_type, *args, **kwargs):
        '''
        Changes the game screen to the given type, with the given arguments.
        Primarily meant to be used with functools.partial.
        '''
        self.next_state = state_type(*args, **kwargs) 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' ,
              } 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 LANGUAGES          = (     'en',      'es')
LANGUAGE_NAMES     = ("English", "Español")
LANGUAGE_DICT      = dict(zip(LANGUAGES, LANGUAGE_NAMES))
SETTINGS_KEYS      = (
    'resolution'  ,
    'fullscreen'  ,
    'color_blind' ,
    'language_id'    ,
    'sound_volume',
    'music_volume',
)
SETTINGS_FILE_NAME = 'settings.wtf'
SETTINGS = {
            'color_blind'  : False     ,
            'fullscreen'   : False     ,
            'language_id'  : 0         ,
            'music_volume' : 1.0       ,
            'resolution'   : (640, 480),
            'sound_volume' : 1.0       ,
            }
def toggle_color_blind_mode():
def save_settings(path):
def load_settings(path):
def set_language(lang):
def get_language_code():
def get_language_name(): BALL_STATES  = ('IDLE', 'APPEARING', 'MOVING', 'DYING', 'RESET')
FRAMES       = tuple(Rect(32 * i, 96, 32, 32) for i in range(5))
TIME_TO_MOVE = 30  #In frames; remember, our target is 60FPS
    STATES      = config.Enum(*BALL_STATES)
    BLOCK_GROUP = None
    ENEMY_GROUP = None
def clean_up():
def get_ball(startpos, target, newcolor):
    def __init__(self, startpos=(-300.0, -300.0), newcolor=choice(color.LIST)):
    def appear(self):
    def move(self):
    def vanish(self):
class BallOfLight(GameObject): class BallOfLight(GameObject):
    STATES      = config.Enum(*BALL_STATES)
    BLOCK_GROUP = None
    block_mod   = None
    ENEMY_GROUP = None

    def __init__(self, startpos=(-300.0, -300.0), newcolor=choice(color.LIST)):
        GameObject.__init__(self)

        self._anim              = 0
        self.color              = newcolor
        self.current_frame_list = _ball_frames_color_blind if settings.SETTINGS['color_blind'] else _ball_frames
        self.image              = self.current_frame_list[id(newcolor)][0]
        size                    = self.image.get_size()
        self.rect               = Rect(startpos, size)
        self.position           = list(self.rect.topleft)
        self.progress           = 0
        self._target            = [None, 0]
        self.startpos           = startpos
        self.state              = BallOfLight.STATES.IDLE

        del self.acceleration, self.velocity

    def appear(self):
        self.image        = self.current_frame_list[id(self.color)][0]
        self.position     = list(self.startpos)
        self.progress     = -1
        self.rect.topleft = (self.startpos[0] + .5, self.startpos[1] + .5) 
        self.change_state(BallOfLight.STATES.MOVING)
        
        assert config.SCREEN_RECT.collidepoint(self._target), \
        "BallOfLight's target should be on-screen, but it's %s" % self._target

    def move(self):
        position = self.position
        startpos = self.startpos
        target   = self._target
        
        self.progress += 1
        percent        = self.progress / TIME_TO_MOVE

        if self._anim < len(FRAMES) - 1:
        #If we haven't finished animating...
            self._anim += 1
            self.image  = self.current_frame_list[id(self.color)][self._anim]

        if percent >= 1:
        #If we've reached our target location...
            self.change_state(BallOfLight.STATES.DYING)
        else:
            dx                = (percent * percent) * (3 - 2 * percent)
            dp                = 1 - dx
            position[0]       = (startpos[0] * dx) + (target[0] * self.image.get_width() * dp)
            position[1]       = (startpos[1] * dp) + (target[1] * dx)
            self.rect.topleft = (position[0] + .5, position[1] + .5)

        assert self.rect.colliderect(config.SCREEN_RECT), \
        "A BallOfLight at %s is trying to move off-screen!" % position

    def vanish(self):
        _balls.add(self)
        BallOfLight.BLOCK_GROUP.add(BallOfLight.block_mod.get_block([self._target[0] * 32, 8.0], self.color))
        self.kill()
        self._anim        = 0
        self.position     = [-300.0, -300.0]
        self.rect.topleft = (self.position[0] + .5, self.position[1] + .5)
        
        self.change_state(BallOfLight.STATES.IDLE)

    actions = {
                STATES.IDLE     : None    ,
                STATES.APPEARING: 'appear',
                STATES.MOVING   : 'move'  ,
                STATES.DYING    : 'vanish',
              } def _star_appear(self):
def _star_move(self):
STARS_GROUP = pygame.sprite.RenderUpdates()
_STAR_IMAGE = config.get_sprite(Rect(4, 170, 2, 2))
EARTH = HudObject(config.EARTH  , (0, 0))
GRID  = HudObject(config.GRID_BG, (0, 0))
STARS = ParticleEmitter(
                        ParticlePool(_STAR_IMAGE, _star_move, _star_appear),
                        Rect(0, 0, 0, config.SCREEN_HEIGHT),
                        8,
                        STARS_GROUP
                        ) BLOCK_STATES = ('IDLE', 'APPEARING', 'ACTIVE', 'START_FALLING', 'FALLING', 'IMPACT', 'DYING')
FRAMES       = tuple(pygame.Rect(32 * i, 160, 32, 32) for i in range(8))
GRAVITY      =  0.5
MAX_SPEED    = 12.0
UFO_BLOCK    = config.load_sound('ufo_block.wav')
    STATES         = config.Enum(*BLOCK_STATES)
    GROUP          = None
def clean_up():
def get_block(position, newcolor=choice(color.LIST), special=False):
    def __init__(self, position, newcolor=choice(color.LIST), special=False):
    def __str__(self):
    def __repr__(self):
    def appear(self):
    def start_falling(self):
    def fall(self):
    def wait(self):
    def stop(self):
    def vanish(self):
    def __replace(self):
    def __animate(self):
    def __get_snap(self):
class Block(GameObject): class Block(GameObject):
    '''
    Blocks are left by enemies when they're killed.  Match three of the same
    color, and they'll disappear.
    '''
    STATES         = config.Enum(*BLOCK_STATES)
    block_full     = False
    GROUP          = None
    particle_group = None

    def __init__(self, position, newcolor=choice(color.LIST), special=False):
        GameObject.__init__(self)
        self._anim      = 0
        self.color      = newcolor
        self.temp_color = self.color
        self.current_frame_list = _block_frames_color_blind if settings.SETTINGS['color_blind'] else _block_frames
        self.image              = self.current_frame_list[id(self.color)][0]
        self.position = position
        self.rect     = pygame.Rect(position, self.image.get_size()) #(x, y)
        self._special = special
        self.state    = Block.STATES.IDLE

    def __str__(self):
        return ("<Block - color: %s, cell: %s, position: %s, rect: %s, state: %i>" %
               (self.color, self.gridcell, self.position, self.rect, self.state))
        
    def __repr__(self):
        return self.__str__()

    def appear(self):
        self.position     = self.__get_snap()
        self.rect.topleft = self.position
        self.image        = self.current_frame_list[id(self.color)][0]
        self.gridcell     = [
                             self.rect.centerx // self.rect.width,
                             self.rect.centery // self.rect.height,
                            ] #(x, y)
        self.emitter      = ParticleEmitter(color.color_particles[id(self.color)], self.rect, 5, Block.particle_group)
        self.change_state(Block.STATES.START_FALLING)
        self.add(Block.GROUP)

    def start_falling(self):
        '''
        Starts the Block falling down.  Only called once before this Block's
        state switches to STATES.FALLING.  Blocks that are falling must not be
        part of matches.
        '''
        self.acceleration[1] = GRAVITY
        blockgrid.check_block(self, False)

        if self.gridcell[1]:
        #If we're not at the top of the grid...
            block_above = blockgrid.blocks[self.gridcell[0]][self.gridcell[1] - 1]
            if block_above:
            #If there's at least one block above us...
                assert isinstance(block_above, Block), \
                "%s expected a Block, got a %s" % (self, block_above)

                for i in blockgrid.blocks[self.gridcell[0]]:
                #For all grid cells above us...
                    if i and not i.velocity[1]:
                    #If this is a block that's not moving...
                        i.change_state(Block.STATES.START_FALLING)
                    else:
                        break

        self.change_state(Block.STATES.FALLING)

    def fall(self):
        '''
        Falls down.  For the sake of efficiency, blocks work independently of
        the collision detection system, since they're only going to move
        vertically, and only depend on other blocks for collisions.
        '''
        gridcell = self.gridcell
        position = self.position
        rect     = self.rect
        
        self.velocity[1] = min(MAX_SPEED, self.velocity[1] + self.acceleration[1])
        position[1]     += self.velocity[1]
        rect.top         = position[1] + 0.5 #Round to the nearest integer
        gridcell[1]      = self.rect.centery // self.rect.height
        self.emitter.rect.topleft = rect.topleft

        self.__animate()
        
        if rect.bottom >= blockgrid.RECT.bottom:
        #If we've hit the bottom of the grid...
            rect.bottom     = blockgrid.RECT.bottom
            position[1]     = rect.top
            self.change_state(Block.STATES.IMPACT)
        elif self.gridcell[1] + 1 < blockgrid.SIZE[1]:
        #Else if it was another block...
            below = blockgrid.blocks[gridcell[0]][gridcell[1] + 1]
            if below and rect.bottom >= below.rect.top:
            #If we've gone past the block below...
                rect.bottom     = below.rect.top
                position[1]     = rect.top
                self.change_state(Block.STATES.IMPACT)
            assert isinstance(below, Block) or below is None, \
            "A %s is trying to collide with a stray %s!" % (self, below)
        
        assert self.state == Block.STATES.FALLING \
        and rect.colliderect(blockgrid.RECT) \
        and blockgrid.RECT.collidepoint(position), \
        "An active %s has somehow left the field!" % self

    def wait(self):
        '''
        Constantly checks to see if this block can fall.
        '''
        gridcell = self.gridcell
        if self.rect.bottom < blockgrid.RECT.bottom:
        #If we're not at the bottom of the grid...
            block_below = blockgrid.blocks[gridcell[0]][gridcell[1] + 1]
            if not block_below or block_below.velocity[1]:
            #If there's no block directly below...
                blockgrid.check_block(self, False)
                self.acceleration[1] = GRAVITY
                self.change_state(Block.STATES.START_FALLING)
                
        if __debug__ and self.rect.collidepoint(pygame.mouse.get_pos()):
            print(self)

    def stop(self):
        '''
        Handles the Block when it hits the bottom of the grid or another block.
        Changes velocity, plays sounds, etc.
        '''
        self.acceleration[1] = 0.0
        self.velocity[1]     = 0.0
        self.position        = self.__get_snap()
        self.rect.topleft    = self.position
        self.gridcell[1]     = self.rect.centery // self.rect.height #(row, col)
        self.state           = Block.STATES.ACTIVE
        blockgrid.blocks[self.gridcell[0]][self.gridcell[1]] = self
            
        blockgrid.check_block(self, True)
        _bump.play()
        #blockgrid.update()
        
        if self._special:
        #If this is a special block...
            UFO_BLOCK.play()
            self.emitter.pool = color.random_color_particles
            if self.gridcell[1] < blockgrid.SIZE[1] - 1:
            #If we're not at the bottom of the grid...
                self.color = blockgrid.blocks[self.gridcell[0]][self.gridcell[1]+ 1].color
                blockgrid.clear_color(self.color)
            else:
                blockgrid.clear_row(self.gridcell[1])

        elif not self.gridcell[1]:
        #If we go past the the playing field...
            self._anim = len(FRAMES) - 2  #Bring us to the second-to-last frame
            self.__animate()              #And let the animation system finish
            gamedata.lives = 0

    def vanish(self):
        blockgrid.check_block(self, False)
        self.emitter.burst(20)
        self.kill()
        
        blockgrid.blocks[self.gridcell[0]][self.gridcell[1]] = None
        self._anim                                 = 0
        self.position                              = [-300.0, -300.0]
        self.rect.topleft                          = self.position
        self.gridcell                              = None
        self.change_state(Block.STATES.IDLE)
        self.__replace()
        
    def __replace(self):
        '''
        Puts this Block back in the set of spares.
        
        @postcondition: This Block is ready to be recycled.
        '''
        _blocks_set.add(self)
        
    def __animate(self):
        if self._anim < len(FRAMES) - 1:
        #If we haven't hit the last frame of animation...
            self._anim += 1
            self.image  = self.current_frame_list[id(self.color)][self._anim]

        if self._special:
        #If this is a _special block...
            self.image = self.current_frame_list[id(choice(color.LIST))][self._anim]
    
    def __get_snap(self):
        '''
        Returns a position list that snaps this block to the grid.
        '''
        size = self.image.get_size()
        return [
                round(self.position[0] // size[0]) * size[0],
                round(self.position[1] // size[1]) * size[1],
               ]
        
    actions = {
                STATES.IDLE         : None           ,
                STATES.APPEARING    : 'appear'       ,
                STATES.ACTIVE       : 'wait'         ,
                STATES.FALLING      : 'fall'         ,
                STATES.START_FALLING: 'start_falling',
                STATES.IMPACT       : 'stop'         ,
                STATES.DYING        : 'vanish'       ,
              } ALARM_LINE = 2
BLOCK_TYPE = None
CELL_SIZE  = (32, 32)
COMBOS     = tuple(config.load_sound('combo%d.wav' % i) for i in range(1, 6))
SIZE       = (20, 12) #(width, height)
RECT       = Rect(0, 0, get_surface().get_width(), SIZE[1] * CELL_SIZE[1])
def any_active():
def cache_block_image():
def check_block(block, should_check=True):
def clean_up():
def clear_color(color):
def clear_row(row):
def get_empty_block_array():
def is_above_threshold():
def update(): class ComboCounter(HudObject):
    STATES = config.Enum(*STATE_NAMES)
    
    def __init__(self, num, pos):
        super().__init__(hudobject.make_text(str(num), pos=pos, surfaces=True), pos)
        self.next_state = None
        self.position   = list(pos)
        self.rect       = Rect(pos, self.image.get_size())
        self.time_left  = 0
        self.change_state(ComboCounter.STATES.IDLE)
        
    def update(self):
        GameObject.update(self)
        
    def move(self):
        self.position[1] -= SPEED
        self.rect.top     = self.position[1] + .5
        self.time_left -= 1
        if not self.time_left:
            self.change_state(ComboCounter.STATES.STANDING)
            self.time_left = TIME_STANDING
            
    def stand(self):
        self.time_left -= 1
        if not self.time_left:
            self.change_state(ComboCounter.STATES.LEAVING)
            self.time_left = 0
            
    def vanish(self):
        self.kill()
        self.change_state(ComboCounter.STATES.IDLE)
        
    def appear(self):
        self.time_left = TIME_MOVING
        self.change_state(ComboCounter.STATES.MOVING)
        
    actions = {
               STATES.IDLE      : None    ,
               STATES.APPEARING : 'appear',
               STATES.MOVING    : 'move'  ,
               STATES.STANDING  : 'stand' ,
               STATES.LEAVING   : 'vanish',
               } 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' ,
              } BULLET_STATES = ('IDLE', 'FIRED', 'MOVING', 'DYING', 'RESET')
    SPEED     = 2
    START_POS = Rect(30, config.screen.get_height() * 2, 5, 5)
    STATES    = config.Enum(*BULLET_STATES)
    FRAME     = Rect(262, 6, 20, 19)
    GROUP     = None
def clean_up():
def all_disappear():
def get_enemy_bullet():
    def __init__(self):
    def move(self):
    def reset(self):
    def blink(self):
    def kill_player(self, other):
class EnemyBullet(Bullet): class EnemyBullet(Bullet):
    SPEED     = 2
    START_POS = Rect(30, config.screen.get_height() * 2, 5, 5)
    STATES    = config.Enum(*BULLET_STATES)
    FRAME     = Rect(262, 6, 20, 19)
    GROUP     = None
    halt      = False

    def __init__(self):
        super().__init__()
        self.blink_timer = 3 * 60
        self.image = config.SPRITES.subsurface(self.__class__.FRAME)
        self.image.set_colorkey(color.COLOR_KEY, config.BLIT_FLAGS)

    def move(self):
        '''
        Moves down the screen.
        '''
        super().move()

        if self.rect.top > config.SCREEN_HEIGHT:
        #If below the bottom of the screen...
            self.change_state(self.__class__.STATES.RESET)

    def reset(self):
        '''
        Remove this EnemyBullet from the game screen, but not from memory.
        '''
        super().reset()
        _enemy_bullets.add(self)
        self.blink_timer = 3 * 60
        
    def blink(self):
        self.blink_timer -= 1
        
        self.image.set_alpha(256 * (sin(self.blink_timer // 4) > 0))
            
        if not self.blink_timer:
        #If we're done animating...
            self.image.set_alpha(256)
            EnemyBullet.halt = False
            self.change_state(EnemyBullet.STATES.RESET)

    def kill_player(self, other):
        '''
        Kills the player.  Called if this EnemyBullet collides with the player.
        '''
        if not other.invincible and other.state == Ship.STATES.ACTIVE:
        #If the player is not invincible...
            gamedata.lives  -= 1
            all_disappear()
            EnemyBullet.halt = True
            other.change_state(Ship.STATES.DYING)
            self.change_state(self.__class__.STATES.DYING)
            
            
            
    actions  = {
                STATES.IDLE   : None          ,
                STATES.FIRED  : 'start_moving',
                STATES.MOVING : 'move'        ,
                STATES.DYING  : 'blink'       ,
                STATES.RESET  : 'reset'       ,
                }
    collisions = {Ship: kill_player} ROW_SIZE       = 8
COL_SIZE       = 4
SPEED_INCREASE = .15 #In pixels per frame
ENEMY_GROUP = None
def clean_up():
def celebrate():
def move_down():
def reset():
def start():
def update():
def increase_difficulty(): DEFAULT_LIVES = 3
MAX_COMBO_TIME = 60  #In frames
def clean_up(): class HighScoreState(MenuState):
    def __init__(self, *args, **kwargs):
        super().__init__()
        ### Local Variables ####################################################
        char_up     = partial(self.__char_move   ,  1)
        char_down   = partial(self.__char_move   , -1)
        table_left  = partial(self.__switch_table, -1)
        table_right = partial(self.__switch_table,  1)
        langcode  = settings.get_language_code()
        text      = config.load_text('highscore', langcode)
        titles    = config.load_text('menu', langcode)[2:5]
        titles   += tuple(map(lambda x: " ".join((x, text[3])), titles))
        ########################################################################
        
        ### Object Attributes ##################################################
        self._mode         = kwargs['mode'] if 'mode' in kwargs else -1
        self.current_table = score_table_dict[self._mode]
        self.entering_name = False
        self.key_actions = {
                            K_LEFT  : table_left        ,
                            K_a     : table_left        ,
                            K_RIGHT : table_right       ,
                            K_d     : table_right       ,
                            K_UP    : char_up           ,
                            K_w     : char_up           ,
                            K_DOWN  : char_down         ,
                            K_s     : char_down         ,
                            K_RETURN: self.__enter_char ,
                            K_SPACE : self.__sync_scores,
                            K_ESCAPE: partial(self.change_state, kwargs['next']),
                           }
        self.group_list    = (bg.STARS_GROUP, GRID_BG, MENU)
        self.hud_titles    = tuple(make_text(i, SCORE_TABLE_X).center() for i in titles)
        self.hud_scores    = _make_tables()
        self.online        = ONLINE_TEXT(*make_text(text[4:10], INSTRUCTIONS_X, vspace=0))
        ########################################################################

        ### Preparation ########################################################
        for i in self.online: i.center()
             
        if 'score' in kwargs:
        #If we just finished a game...
            config.play_music('score.ogg')
            #Whether or not we got a high score, play the special music.
            self.key_actions[K_SPACE] = self.__enter_char
            
            if kwargs['score'] > score_tables[score_table_dict[self._mode]].lowest_score():
            #If we just got a high score...
                self.instructions = make_text(
                                              text[:3],
                                              pos=INSTRUCTIONS_X,
                                              vspace=INSTRUCT_DIST_APART
                                             )
                self._score          = kwargs['score']
                self.alphanum_index  = 0
                self.entering_name   = True
                self.entry_name      = ['A']
                self.hud_name        = make_text(''.join(self.entry_name), ENTRY_NAME_POS)
                self.last_entry_name = self.entry_name
                self.name_index      = 0
                MENU.add(self.instructions, self.hud_name)

        m = score_table_dict[self._mode]
        MENU.add(self.hud_scores[m], self.hud_titles[m])
        GRID_BG.add(bg.EARTH, bg.GRID)
        ########################################################################

    def render(self):  
        if self.entering_name and (self.entry_name != self.last_entry_name):
        #If we're entering our name for a high score...
            self.hud_name.image = make_text(''.join(self.entry_name), surfaces=True)
        super().render()

    def __char_move(self, index):
        if self.entering_name:
        #If we're entering our name for a high score...
            config.CURSOR_BEEP.play()
            self.last_entry_name = list(self.entry_name)
            self.alphanum_index += index
            self.alphanum_index %= len(OK_CHARS)
            self.entry_name[self.name_index] = OK_CHARS[self.alphanum_index]

    def __switch_table(self, index):
        '''
        Move between the three tables, but not if you're entering a high score.
        '''
        if not self.entering_name:
        #If we're not currently entering our name...
            config.CURSOR_BEEP.play()
            scores, titles = self.hud_scores, self.hud_titles
            MENU.remove(scores[self.current_table], titles[self.current_table])
            self.current_table += index
            self.current_table %= len(score_tables)
            MENU.add(scores[self.current_table], titles[self.current_table])
            MENU.remove(self.online)
            if self.current_table >= len(score_tables) / 2:
                MENU.add(self.online.prompt)

    def __enter_char(self):
        if self.entering_name and self.name_index < CHAR_LIMIT:
        #If we're entering our name for a high score and it's not too long...
            config.CURSOR_SELECT.play()
            if OK_CHARS[self.alphanum_index] == BACK_CHAR:
            #If we're erasing a character...
                if len(self.entry_name) > 1:
                #If there are any characters to erase...           
                    self.entry_name.pop()
                self.entry_name.pop()
                self.name_index  = max(self.name_index - 1, 0)
                self.entry_name += [BACK_CHAR]
                return
            elif OK_CHARS[self.alphanum_index] == BLANK_CHAR:
            #Else if we want to mark down a space...
                self.entry_name[self.name_index] = ' '
                self.name_index = min(self.name_index + 1, CHAR_LIMIT - 1)
            elif OK_CHARS[self.alphanum_index] == DONE_CHAR:
            #Else if we're done...
                if len(self.entry_name) > 1:   
                    self.entry_name.pop() #Pops off the end character      
                else:
                    self.entry_name = list(choice(NAMES))
                self.__enter_score()
                return
            else:
            #Else if this character is an ordinary letter...
                self.name_index = min(self.name_index + 1, CHAR_LIMIT - 1)
                
            self.alphanum_index = 0
            self.entry_name    += ['A'] if len(self.entry_name) < CHAR_LIMIT else []
        elif self.entering_name:
        #If we've finished entering our name...
            self.__enter_score()
            
    def __enter_score(self):
        config.CURSOR_SELECT.play()
        score = [HighScoreEntry(''.join(self.entry_name), self._score, self._mode)]
        self.entering_name = False
        self.key_actions[K_SPACE] = self.__sync_scores
        self.hud_name.kill()  #Get rid of the name entry characters
        score_tables[self.current_table].add_scores(score)#add the entry to the leaderboard
        MENU.remove(self.hud_scores, self.instructions)
        self.hud_scores = _make_tables()
        MENU.add(self.hud_scores[self.current_table])#add the menu back to the screen with the updated entry
        
    def __sync_scores(self):
        '''
        Synchronizes the leaderboard.
        '''
        if self.current_table >= len(score_tables) / 2:
        #If we're in the half of the table list that's online...
            langcode = settings.get_language_code()
            text     = config.load_text('highscore', langcode)
            error = None
            try:
                MENU.remove(self.online)
                self.__get_online()
                self.__submit_online()
            except HTTPError as e:
                error = "%s (%d)" % (e.reason, e.getcode())
            except URLError as e:
                error = text[10]
            except (SyntaxError, timeout) as e:
                error = text[11]
            except (ConnectionError, ValueError) as e:
                error = text[12]
            
            MENU.remove(self.online)
            if error is not None:
                ERROR.play()
                self.online.fail.image = make_text(' '.join([text[9], error]), surfaces=True)
                self.online.fail.rect.size  = self.online.fail.image.get_size()
                MENU.add(self.online.fail.center())
            else:
                MENU.remove(self.hud_scores[self.current_table])
                self.hud_scores = _make_tables()
                MENU.add(self.online.ok, self.hud_scores[self.current_table])

    def __submit_online(self):
        '''
        Submits the highest score to the server.
        '''
        MENU.remove(self.online.prompt)
        MENU.add(self.online.send)
        self.render()  #Quick hack; make this asynchronous later
        entry = score_tables[self.current_table - len(score_tables) // 2].scores[0]
        post_params = urllib.parse.urlencode({
            b'name'  : entry.name.encode()          ,
            b'score' : str(entry.score).encode()    ,
            b'mode'  : str(entry.mode * 10).encode(),
        }).encode()
        
        a = urlopen(URL, post_params, timeout=TIME_OUT).read().decode()
        response = json.loads(a)
        MENU.remove(self.online.send)
        if 'success' not in response:
            raise ConnectionError()
    
    def __get_online(self):
        '''
        Retrieves all scores from the server and places them in the online
        score tables.  Alerts the user if it fails.
        '''
        MENU.remove(self.online.prompt)
        MENU.add(self.online.get)
        self.render()
        
        a = urlopen(Request(URL, method=METHOD), timeout=TIME_OUT)
        
        c = literal_eval(a.read().decode())
        table = score_tables[self.current_table]
        scores = [HighScoreEntry(i['name'], i['score'], i['mode']) for i in filter(lambda x: x['mode'] == table.mode, c)]
        table.set_scores(scores)
        MENU.remove(self.online.get) class HudObject(GameObject):
    '''
    HudObject is meant for displays like lives, score, etc.

    It is its own type so it can be in collisions._do_not_check, because we
    don't intend for these to collide with anything.
    '''
    STATES = config.Enum('IDLE')
    fonts  = (config.FONT,)
    
    def __init__(self, image, pos):
        super().__init__()
        self.image = image
        self.rect  = pygame.Rect(pos, image.get_size())
        del self.velocity, self.acceleration, self.position, self.next_state
        
    def center(self):
        self.rect.centerx = config.SCREEN_RECT.centerx
        return self

    def update(self):
        '''
        Since HudObjects have no functionality, let's make update() do nothing!
        '''
        pass @lru_cache(maxsize=64)
def make_text(text, pos=(0, 0), col=color.WHITE, font=HudObject.fonts[0], vspace=8, surfaces=False):
        '''
        @param text: The text we want visible to the user. If a list, one element per line
        @param pos: The topleft corner of the position rect
        @param col: The color of the text
        @param font: The font used; it defaults to the first font entry
        @param vspace: Space between lines in pixels. Ignored if only one line.
        @param surfaces: True if we want Surfaces instead of HudObjects (pos is then ignored)

        @return: list(pygame.Surface) if surfaces else list(HudObject)
        '''

        if isinstance(text, str):
        #If we were given a single string...
            hud = font.render(text, False, col).convert(config.DEPTH, config.BLIT_FLAGS)
            hud.set_colorkey(color.BLACK)
            return hud if surfaces else HudObject(hud, pos)
        else:
            def d(t):
                hud = font.render(text[t], False, col).convert(config.DEPTH, config.BLIT_FLAGS)
                hud.set_colorkey(color.BLACK)
                return hud if surfaces else HudObject(hud, (pos[0], pos[1] + vspace * t))

            return [d(i) for i in range(len(text))] class Ship(GameObject):
    '''
    The Ship is the player character.  There's only going to be one instance of
    it, but it has to inherit from pygame.sprite.Sprite, so we can't make it a
    true Python singleton (i.e. a module).
    '''
    
    STATES = config.Enum(*SHIP_STATES)
    GROUP  = None

    def __init__(self):
        '''
        @ivar anim: A counter for ship animation
        @ivar image: The graphic
        @ivar invincible: How many frames of invincibility the player has if any
        @ivar my_bullet: The single bullet this ship may fire
        '''
        super().__init__()

        self.anim           = 0.0
        self.appear_emitter = ParticleEmitter(APPEAR_POOL, START_POS.copy(), 2)
        self.emitter        = ParticleEmitter(DEATH_POOL, START_POS.copy(), 2)
        self.flames         = FlameTrail()
        self.image          = FRAMES[0]
        self.invincible     = 0
        self.light_column   = LightColumn()
        self.my_bullet      = ShipBullet()
        self.position       = list(START_POS.topleft)
        self.rect           = START_POS.copy()
        self.respawn_time   = 3 * 60  # In frames
        self.change_state(Ship.STATES.RESPAWN)

    def on_fire_bullet(self):
        bul = self.my_bullet
        if bul.state == ShipBullet.STATES.IDLE and self.state == Ship.STATES.ACTIVE:
        #If our bullet is not already on-screen...
            bul.add(Ship.GROUP)
            self.anim       = 1
            self.image      = FRAMES[self.anim]
            bul.rect.center = self.rect.center
            bul.position    = list(self.rect.topleft)
            bul.change_state(ShipBullet.STATES.FIRED)

    def respawn(self):
        self.appear_emitter.burst(200)
        APPEAR.stop()
        APPEAR.play()
        for i in chain(FRAMES, FlameTrail.FRAMES, {self.light_column.image}): i.set_alpha(128)
        self.invincible   = 250
        self.light_column.rect.midbottom = self.rect.midtop
        self.position     = list(START_POS.topleft)
        self.rect         = START_POS.copy()
        self.respawn_time = 3 * 60
        
        self.change_state(Ship.STATES.ACTIVE)

    def move(self):
        keys  = pygame.key.get_pressed() #Shorthand for which keys are pressed
        rect  = self.rect
        width = self.image.get_width()
        
        if self.state not in {Ship.STATES.DYING, Ship.STATES.DEAD, Ship.STATES.IDLE}:
            if (keys[K_LEFT] or keys[K_a]) and rect.left > 0:
            #If we're pressing left and not at the left edge of the screen...
                self.position[0] -= SPEED
            elif (keys[K_RIGHT] or keys[K_d]) and rect.right < config.SCREEN_RECT.right:
            #If we're pressing right and not at the right edge of the screen...
                self.position[0] += SPEED

        rect.left               = self.position[0] + 0.5
        self.flames.rect.midtop = (rect.midbottom[0], rect.midbottom[1] - 1)
        #Compensate for the gap in the flames                           ^^^
        self.light_column.position[0] = self.position[0]
        self.light_column.rect.left   = round(self.light_column.position[0] / width) * width
        
        if self.invincible:
        #If we're invincible...
            self.invincible -= 1
        elif self.image.get_alpha() == 128:
            for i in chain(FRAMES, FlameTrail.FRAMES): i.set_alpha(255)
            
        self.anim = self.anim + (0 < self.anim < len(FRAMES) - 1) / 3 if self.anim != 4 else 0.0
        self.image = FRAMES[int(self.anim)]
        
        if gamedata.combo_time == gamedata.MAX_COMBO_TIME and gamedata.combo > 1:
            counter                = get_combo_counter(gamedata.combo, self.rect.topleft)
            counter.rect.midbottom = self.rect.midtop
            counter.position       = list(counter.rect.topleft)
            counter.change_state(counter.__class__.STATES.APPEARING)
            Ship.GROUP.add(counter)
            

    def die(self):
        DEATH.play()
        for i in chain(FRAMES, FlameTrail.FRAMES, (self.light_column.image,)): i.set_alpha(0)
        self.emitter.rect = self.rect
        self.emitter.burst(100)
        self.change_state(Ship.STATES.DEAD)
        
    def instadie(self, other):
        if gamedata.lives:
        #If we have any lives...
            gamedata.lives = 0
            self.die()
            
    def wait_to_respawn(self):
        self.respawn_time -= 1
        if not self.respawn_time:
        #If we're done waiting to respawn...
            self.change_state(Ship.STATES.RESPAWN)
            
    actions = {
               STATES.IDLE      : None             ,
               STATES.SPAWNING  : 'respawn'        ,
               STATES.ACTIVE    : 'move'           ,
               STATES.DYING     : 'die'            ,
               STATES.DEAD      : 'wait_to_respawn',
               STATES.RESPAWN   : 'respawn'        ,
              }
    
    collisions = {
                  Enemy: instadie,
                  } class FlameTrail(GameObject):
    '''
    FlameTrail is the jet left by the Ship's engines.  This is purely a
    graphical effect.
    '''
    FRAMES = tuple(config.get_sprite(Rect(32*i, 0, 32, 32)) for i in range(6))
    GROUP  = None

    def __init__(self):
        super().__init__()
        self.anim     = 0.0
        self.image    = FlameTrail.FRAMES[0]
        self.position = [-300.0, -300.0]
        self.rect     = Rect(self.position, self.image.get_size())
        self.state    = 1
        del self.acceleration, self.velocity
        for i in self.__class__.FRAMES: i.set_colorkey(color.COLOR_KEY, config.BLIT_FLAGS)

    def animate(self):
        self.anim += 1/3
        self.image = FlameTrail.FRAMES[int(3 * sin(self.anim / 2)) + 3]

    actions = {1 : 'animate'} class LightColumn(GameObject):
    '''
    This class exists to let the player know where exactly he's aiming.
    '''
    SIZE = Rect(0, 0, 32, config.SCREEN_HEIGHT - 32 * 3)
    
    def __init__(self):
        super().__init__()
        self.image    = Surface(self.__class__.SIZE.size, config.BLIT_FLAGS)
        self.position = [-300.0, -300.0]
        self.rect     = Rect(self.position, self.__class__.SIZE.size)
        self.state    = 1
        
        self.image.fill(color.WHITE)
        self.image.set_alpha(128)
        del self.acceleration, self.velocity
        
    actions = {1 : None} 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 UFO(GameObject):
    STATES      = config.Enum(*UFO_STATES)
    GROUP       = None
    BLOCK_GROUP = None

    def __init__(self):
        super().__init__()
        self._anim    = 0.0
        self.column   = None
        self.current_frame_list = UFO_FRAMES
        self.image    = config.get_sprite(FRAMES[0])
        self.odds     = expovariate(AVG_WAIT)
        self.position = list(START_POS)
        self.rect     = Rect(START_POS, self.image.get_size())
        self.state    = UFO.STATES.IDLE
        self.emitter  = ParticleEmitter(color.random_color_particles, self.rect)

        del self.acceleration

    def appear(self):
        '''
        Appear on-screen, but not for very long!
        '''
        INVADE.play(-1)
        self.position     = list(START_POS)
        self.rect.topleft = list(START_POS)
        self.change_state(UFO.STATES.ACTIVE)
        self.velocity[0]  = -2.0

    def move(self):
        '''
        Move left on the screen, and oscillate up and down.
        '''
        position = self.position
        rect     = self.rect
            
        self._anim += 0.5
        self.image  = UFO_FRAMES[id(choice(color.LIST))       ] \
                                [int(self._anim) % len(FRAMES)]
        position[0] += self.velocity[0]
        position[1] += sin(self._anim/4)
        rect.topleft = (position[0] + .5, position[1] + .5)

        if rect.right < 0:
        #If we've gone past the left edge of the screen...
            self.change_state(UFO.STATES.LEAVING)

    def die(self):
        '''
        Vanish and release a special Block that clears lots of other Blocks.
        '''
        self.emitter.rect = self.rect
        self.emitter.burst(30)
        DEATH.play()
        UFO.BLOCK_GROUP.add(get_block((self.rect.centerx, 0), special=True))
        gamedata.score += 90
        self.change_state(UFO.STATES.LEAVING)

    def leave(self):
        INVADE.stop()
        self.velocity[0]  = 0
        self.position     = list(START_POS)
        self.rect.topleft = START_POS
        self.change_state(UFO.STATES.IDLE)

    def wait(self):
        '''
        Wait off-screen, and only come back with a specific probability.
        '''
        if uniform(0, 1) < self.odds:
        #With a certain probability...
            self.odds = expovariate(AVG_WAIT)
            self.change_state(UFO.STATES.APPEARING)

    actions = {
                STATES.IDLE     : 'wait'  ,
                STATES.APPEARING: 'appear',
                STATES.ACTIVE   : 'move'  ,
                STATES.DYING    : 'die'   ,
                STATES.LEAVING  : 'leave' ,
                STATES.GAMEOVER : None    ,
              }
from collections import namedtuple
from functools   import partial
from itertools import chain
from math        import log1p
from random      import choice
from pygame.constants import *
from pygame.sprite import Group, OrderedUpdates
from core             import collisions
from core.collisions  import CollisionGrid
from core             import color
from core             import config
from core.gamestate   import GameState
from core.particles   import Particle, ParticleEmitter
from core import settings
from game             import balloflight
from game.balloflight import BallOfLight
from game             import bg
from game             import block
from game.block       import Block
from game             import blockgrid
from game.combocounter import ComboCounter
from game.enemy       import Enemy
from game             import enemybullet
from game.enemybullet import EnemyBullet
from game             import enemysquadron
from game             import gamedata
from game.highscore   import HighScoreState
from game.hudobject   import HudObject, make_text
from game.player      import Ship, FlameTrail, LightColumn
from game.shipbullet  import ShipBullet
from game.ufo         import UFO
        from game.mainmenu import MainMenu
        from game.mainmenu  import MainMenu
import pygame.mixer



#if config.DEBUG: import core.vartracker

### Groups #####################################################################
BG            = OrderedUpdates()
BLOCKS        = Group()
ENEMIES       = Group()
ENEMY_BULLETS = Group()
HUD           = Group()
PARTICLES     = Group()
PLAYER        = Group()
UFO_GROUP     = Group()
################################################################################

### Constants ##################################################################
rect = config.SCREEN_RECT
ALARM          = config.load_sound('alarm.wav')
DEBUG_KEYS     = (K_u, K_c, K_f, K_F1, K_e, K_k, K_i)
FADE_TIME      = 2500  #In milliseconds
FIRE_LOCATION  = (rect.centerx - 192, rect.centery     )
GAME_OVER_LOC  = (rect.centerx - 64 , rect.centery - 64)
HUD_TEXT       = namedtuple('Hud', 'score lives time game_over press_fire wave pause')
LIVES_LOCATION = (rect.width - 160, 16)
MODULE_CLEANUP = (balloflight, blockgrid, block, enemybullet, enemysquadron, gamedata)
MOUSE_ACTIONS  = {1:color.RED, 2:color.YELLOW, 3:color.BLUE}
MUSIC_PATHS    = {-1:'music.ogg', 120:'2.ogg', 300:'5.ogg'}
NULL_FUNC      = lambda: None
GAME_TEXT      = None
GAME_OVER_PATH = 'gameover.ogg'
SCORE_LOCATION = (16, 16)
TIME_FORMAT    = "{}:{:0>2}"
TIME_LOCATION  = (rect.centerx, 32)
TYPES_IGNORED  = (Block, BallOfLight, ComboCounter, FlameTrail, LightColumn, HudObject, Particle, ParticleEmitter)
TYPE_PAIRS_IGNORED = (
                      (Enemy      , EnemyBullet),
                      (EnemyBullet,       Enemy),
                      (Ship       ,  ShipBullet),
                      (ShipBullet ,        Ship),

                      (Ship       ,         UFO),