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.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 __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 __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 __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
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' , }
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', }
### Functions ################################################################## def _star_appear(self): ''' Stars appear on the left edge of the screen and travel right at one of five possible speeds. ''' self.velocity[0] = randint(1, 5) def _star_move(self): ''' And this is the part where they actually move. ''' self.position[0] += self.velocity[0] self.rect.left = self.position[0] ################################################################################ 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) EARTH.rect.midbottom = config.SCREEN_RECT.midbottom
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' , }
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, }
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', }
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 , }
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 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, }