コード例 #1
0
class PirateInvasion:
    """Class to manage game assets and behavior"""
    def __init__(self):
        """Initialize the game and create game resources"""
        pygame.init()
        self.settings = Settings()
        self.screen = pygame.display.set_mode(
            (self.settings.screen_width, self.settings.screen_height))
        self.background = self.settings.background_image
        pygame.display.set_caption("Pirate Invasion")

        #Create an instance to store game statistics
        #and create a score
        # board
        self.cannon = Cannon(self)
        self.stats = GameStats(self)
        self.sb = Scoreboard(self)
        self.cannonballs = pygame.sprite.Group()
        self.pirates = pygame.sprite.Group()

        self._create_fleet()

        #Make the Play button
        self.play_button = Button(self, "Start Fight!")

        #Set the background color
        self.bg_color = (62, 164, 236)

        #Set background song
        mixer.music.load(
            'C:/Users/Khyr/Desktop/CIT228/Sideways_Shooter/music/music.wav')
        mixer.music.play(-1)
        mixer.music.set_volume(0.2)

    def run_game(self):
        """Start the main loop for game"""
        while True:
            self._check_events()

            if self.stats.game_active:
                self.cannon.update()
                self._update_cannonballs()
                self._update_pirates()
            self._update_screen()

    def _check_events(self):
        """Respond to keypresses & mouse events"""
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
            elif event.type == pygame.KEYDOWN or event.type == ord('s'):
                self._check_keydown_events(event)
            elif event.type == pygame.KEYUP or event.type == ord('w'):
                self._check_keyup_events(event)
            elif event.type == pygame.MOUSEBUTTONDOWN:
                mouse_pos = pygame.mouse.get_pos()
                self._check_play_button(mouse_pos)

    def _check_keydown_events(self, event):
        """Respond to keypresses"""
        #Set the cannonball sound
        cannonballSound = pygame.mixer.Sound(
            'C:/Users/Khyr/Desktop/CIT228/Sideways_Shooter/music/sound.wav')
        mixer.music.set_volume(0.2)

        if event.key == pygame.K_UP or event.key == ord('w'):
            self.cannon.moving_up = True
        elif event.key == pygame.K_DOWN or event.key == ord('s'):
            self.cannon.moving_down = True
        elif event.key == pygame.K_q:
            sys.exit()
        elif event.key == pygame.K_SPACE:
            self._fire_cannonball()
            cannonballSound.play()

    def _check_keyup_events(self, event):
        if event.key == pygame.K_UP or event.key == ord('w'):
            self.cannon.moving_up = False
        elif event.key == pygame.K_DOWN or event.key == ord('s'):
            self.cannon.moving_down = False

    def _check_play_button(self, mouse_pos):
        """Start a new game when the player clicks Play"""
        button_clicked = self.play_button.rect.collidepoint(mouse_pos)
        if button_clicked and not self.stats.game_active:
            #Reset the game settings
            self.settings.intitalize_dynamic_settings()

            #Reset the game statistics
            self.stats.reset_stats()
            self.stats.game_active = True
            self.sb.prep_score()
            self.sb.prep_level()
            self.sb.prep_cannons()

            #Get rid of any remaining pirates and cannonballs
            self.pirates.empty()
            self.cannonballs.empty()

            #Create a new fleet and center the cannon
            self._create_fleet()
            self.cannon.center_cannon()

            #Hide the mouse cursor
            pygame.mouse.set_visible(False)

    def _fire_cannonball(self):
        """Create a new cannonball and add to the cannonball group"""
        if len(self.cannonballs) < self.settings.cannonballs_allowed:
            new_cannonball = Cannonball(self)
            self.cannonballs.add(new_cannonball)

    def _update_cannonballs(self):
        """Update the position of cannonballs and get rid of old cannonballs"""
        #Update cannonball positions
        self.cannonballs.update()

        #Get rid of cannonballs off screen
        for cannonball in self.cannonballs.copy():
            if cannonball.rect.bottom <= 0:
                self.cannonballs.remove(cannonball)
        self._check_cannonball_pirate_collisions()

    def _check_cannonball_pirate_collisions(self):
        """Respond to cannonball-pirate collisions"""
        #Remove any cannonballs and pirates that have collided
        collisions = pygame.sprite.groupcollide(self.cannonballs, self.pirates,
                                                True, True)

        if collisions:
            for pirates in collisions.values():
                self.stats.score += self.settings.pirate_points * len(pirates)
            self.sb.prep_score()
            self.sb.check_high_score()

        if not self.pirates:
            #Destroy existing cannonballs and create new fleet
            self.cannonballs.empty()
            self._create_fleet()
            self.settings.increase_speed()

            #Increase level
            self.stats.level += 1
            self.sb.prep_level()

    def _update_pirates(self):
        """Check if the fleet is at an edge, then update the positions of all the pirates in fleet"""
        self._check_fleet_edges()
        self.pirates.update()

        #Look for pirate-cannon collisions
        if pygame.sprite.spritecollideany(self.cannon, self.pirates):
            self._cannon_hit()

        #Look for pirates hitting the bottom of the screen
        self._check_pirates_bottom()

    def _cannon_hit(self):
        """Respond to cannon being hit by a pirate"""
        if self.stats.cannons_left > 0:
            #Decrement cannons_left, and update scoreboard
            self.stats.cannons_left -= 1
            self.sb.prep_cannons()

            #Get rid of any remaining pirates and cannonballs
            self.pirates.empty()
            self.cannonballs.empty()

            #Create a new fleet and center the cannon
            self._create_fleet()
            self.cannon.center_cannon()

            #Pause
            sleep(0.5)
        else:
            self.stats.game_active = False
            pygame.mouse.set_visible(True)

    def _check_pirates_bottom(self):
        """Check if any pirates have reached the bottom of the screen"""
        screen_rect = self.screen.get_rect()
        for pirate in self.pirates.sprites():
            if pirate.rect.bottom >= screen_rect.bottom:
                #Treat this the same as if teh cannon got hit
                self._cannon_hit()
                break

    def _update_screen(self):
        """Updates images on the screen, flip to the new screen"""
        self.screen.blit(self.background, [0, 0])
        self.cannon.blitme()
        for cannonball in self.cannonballs.sprites():
            cannonball.draw_cannonball()
        self.pirates.draw(self.screen)

        #Draw the score information
        self.sb.show_score()

        #Draw the play button if the game is inactive

        if not self.stats.game_active:
            self.play_button.draw_button()

        #Make the most recent drawn screen visible
        pygame.display.flip()

    def _create_fleet(self):
        """Make a fleet of pirates"""
        #Create a pirate ship and find the number of pirates in a row
        #Spacing between each pirate is equal to one pirate width
        pirate = Pirate(self)
        pirate_width, pirate_height = pirate.rect.size
        available_space_x = self.settings.screen_width - (2 * pirate_width)
        number_pirates_x = available_space_x // (2 * pirate_width)

        #Determine the number of rows of pirates that fit on screen
        cannon_height = self.cannon.rect.height
        available_space_y = (self.settings.screen_height -
                             (3 * pirate_height) - cannon_height)
        number_rows = available_space_y // (2 * pirate_height)

        #Create a full fleet of pirates
        for row_number in range(number_rows):
            for pirate_number in range(number_pirates_x):
                self._create_alien(pirate_number, row_number)

    def _create_alien(self, pirate_number, row_number):
        #Create pirate and place in row
        pirate = Pirate(self)
        pirate_width, pirate_height = pirate.rect.size
        pirate.x = pirate_width + 2 * pirate_width * pirate_number
        pirate.rect.x = pirate.x
        pirate.rect.y = pirate.rect.height + 2 * pirate.rect.height * row_number
        self.pirates.add(pirate)

    def _check_fleet_edges(self):
        """Respond if any pirates have reached an edge"""
        for pirate in self.pirates.sprites():
            if pirate.check_edges():
                self._change_fleet_direction()
                break

    def _change_fleet_direction(self):
        """Drop the entire fleet and change the fleet direction"""
        for pirate in self.pirates.sprites():
            pirate.rect.y += self.settings.fleet_drop_speed
        self.settings.fleet_direction *= -1
コード例 #2
0
class Level(GameState):
    """Level is a GameState with behavior for one full game level.

    Contains Sprites player, enemy, shield, hbullet, cannon, and Group player_bullets
    """
    def __init__(self, manager):
        """manager is required to be a child of GameManager with these functions:
        -kill_player()
        -next_level()
        -add_score(amount)
        -give_energy(amount)
        -spend_energy(amount)
        -give_life()
        """

        GameState.__init__(self, manager)

        self.player = Ship(*opt.player_args)
        self.enemy = EnemyBase(opt.mover_args, opt.spinner_args,
                               opt.shooter_args, self.player)
        self.shield = EnemyShield(self.enemy, opt.shield_filename, formation,
                                  formation_center)
        self.hbullet = HomingBullet(opt.homer_filename, self.player,
                                    opt.homer_speed)
        self.cannon = Cannon(opt.deactivated_cannon_args,
                             opt.standby_cannon_args, opt.firing_cannon_args,
                             self.player)
        self.ion_field = IonField(*opt.ion_field_args)
        self.player_bullets = Group()

        self.reset_positions()

    def update(self):
        self.player.update()
        self.enemy.update()
        self.shield.update()
        self.hbullet.update()
        self.cannon.update()
        self.player_bullets.update()
        self.ion_field.update()

        self.collisions()

    def handle_events(self, events, keys):
        return (event_handlers.check_quit(events, keys)
                and event_handlers.check_shoot_button(events, keys, self.shoot)
                and event_handlers.move_player(events, keys, self.player))

    def draw(self, screen):
        self.ion_field.draw(screen)
        self.enemy.draw(screen)
        self.shield.draw(screen)
        self.player.draw(screen)
        self.hbullet.draw(screen)
        self.cannon.draw(screen)
        self.player_bullets.draw(screen)

    def collisions(self):
        """Handles collisions
        """

        player = self.player
        enemy = self.enemy
        shield = self.shield
        hbullet = self.hbullet
        cannon = self.cannon
        ion_field = self.ion_field
        player_bullets = self.player_bullets

        #player with homing bullet
        if collide_mask(player,
                        hbullet) and not collide_mask(player, ion_field):
            self.kill_player()

        #player with enemy base
        if collide_mask(player, enemy):
            #if base in moving phase, give player energy
            if enemy.get_state_number() == EnemyBase.MOVING:
                self.manager.give_energy(opt.energy_from_enemy)
            #if base in spinning or shooting phase, kill player
            elif enemy.get_state_number() == EnemyBase.SPINNING:
                self.kill_player()
            elif enemy.get_state_number() == EnemyBase.SHOOTING:
                self.kill_player()

        #player with cell
        #-hitting a cell will bounce the player a bit to the left
        #-if the player hit the cell twice in a short enough span,
        # the cell is eaten and the player gets energy
        #-in case of multiple collisions, deal with cell closest to player's center
        #
        #TODO: This still isn't quite right.
        #Should be able to eat top/bottom rows with diagonal movement.
        #(vertical movement should still move player all the way left)
        pc_collides = spritecollide(player, shield, False, collide_mask)
        center_cell = self.find_centermost_cell(pc_collides)
        if center_cell is not None:
            player.rect.right = center_cell.rect.left - opt.cell_bounceback

            if not center_cell.marked:
                center_cell.mark()
            elif shield.can_eat():
                center_cell.kill()
                self.manager.give_energy(opt.energy_from_cell)
                self.manager.add_score(opt.score_cell_eat)
                shield.start_delay(opt.frames_to_eat_cell)

        #player with cannon
        if collide_mask(player, cannon):
            #if in deactivated phase, try spending required energy to activate
            if (cannon.get_state_number() == Cannon.DEACTIVATED
                    and self.manager.spend_energy(opt.cannon_energy_cost)):
                cannon.start_standby()
            #if in firing phase, kill player
            if cannon.get_state_number() == Cannon.FIRING:
                self.kill_player()
            #if in returning phase, give energy and deactivate cannon
            if cannon.get_state_number() == Cannon.RETURNING:
                cannon.start_transition(Cannon.DEACTIVATED)
                self.manager.give_energy(opt.energy_from_cannon)

        #cannon with cell
        #kill one cell and reverse cannon direction
        #assuming this is only possible if cannon in firing state
        if cannon.get_state_number() == Cannon.FIRING:
            cannon_collides = spritecollide(cannon, shield, False,
                                            collide_mask)
            if len(cannon_collides) > 0:
                cannon_collides[0].kill()
                self.manager.add_score(opt.score_cell_shoot)
                cannon.start_transition(Cannon.RETURNING)

        #cannon with enemy base -- only if cannon in firing state
        #give points corresponding to enemy state and end level
        #if enemy base is in shooting state, player also gets a life
        if cannon.get_state_number() == Cannon.FIRING and collide_mask(
                cannon, enemy):
            if enemy.get_state_number() == EnemyBase.MOVING:
                self.manager.add_score(opt.score_mover_destroy)
            elif enemy.get_state_number() == EnemyBase.SPINNING:
                self.manager.add_score(opt.score_spinner_destroy)
            elif enemy.get_state_number() == EnemyBase.SHOOTING:
                self.manager.add_score(opt.score_shooter_destroy)
                self.manager.give_life()
            self.end_level()

        #player's bullet with cell
        #kill player bullet but remove cells in a cross pattern
        #if somehow one bullet hits multiple cells one is arbitrarily selected
        bc_collides = groupcollide(player_bullets, shield, True, False,
                                   collide_mask)
        for current_bullet in bc_collides.keys():
            self.manager.add_score(opt.score_cell_shoot)
            shield.remove_cross(bc_collides[current_bullet][0])

    def find_centermost_cell(self, cells):
        """Given a list of Cell sprites, 
        returns the one whose rect.centery is closest to the player's rect.centery
        
        Returns None if list is empty
        """

        closest_cell = None

        for current_cell in cells:
            current_dist = abs(current_cell.rect.centery -
                               self.player.rect.centery)
            if closest_cell is None or current_dist < closest_dist:
                closest_cell = current_cell
                closest_dist = current_dist

        return closest_cell

    def shoot(self):
        """If cannon can be fired, fires cannon.
        
        Otherwise creates bullet moving from player's center along player's direction
        as long as options.max_player_bullets won't be exeeded
        """

        if collide_mask(self.player, self.ion_field):
            return

        if self.cannon.start_transition(Cannon.FIRING):
            return

        if len(self.player_bullets) < opt.max_player_bullets:
            new_bullet = Bullet(opt.bullet_filename, opt.bullet_speed,
                                self.player.get_rect().center,
                                self.player.get_direction())
            self.player_bullets.add(new_bullet)

    def reset_positions(self):
        """Moves sprites to their initial locations:
        player starts in left center facing south and with 0 energy
        player bullets are removed
        enemy bullet starts on enemy base
        enemy base is in moving state
        cannon is in deactivated state

        Note: shield configuration is not reset
        """

        self.player.rect.midleft = (20, int(opt.height / 2))
        self.player.set_direction(vector.SOUTH)
        self.player_bullets.empty()
        self.enemy.resume_mover_state()
        self.cannon.start_deactivated()
        self.hbullet.rect.center = self.enemy.rect.center

    def kill_player(self):
        death_animation = DeathAnimation(self.manager, self.player,
                                         (self.enemy, self.shield), self,
                                         opt.death_animation_delay,
                                         opt.death_animation_total_runtime)
        self.manager.change_state(death_animation)

    def end_level(self):
        win_animation = WinAnimation(self.manager, self.player,
                                     opt.win_animation_total_runtime,
                                     opt.exp_field_args)
        self.manager.change_state(win_animation)
コード例 #3
0
def main():

    fps = 30
    clock = pygame.time.Clock()

    screen = pygame.display.set_mode((config.WIDTH, config.HEIGHT))

    pygame.font.init()
    font = pygame.font.Font("DejaVuSans.ttf", 24)

    if TYPE == 'HOST':
        s, game_map, conn = hostOptions(screen, font)
    elif TYPE == 'JOIN':
        s, game_map = joinOptions(screen, font)
    else:
        textOnMiddle(screen, 'Generating map...', font)
        pygame.display.flip()
        game_map = GameMap().generate(config.WIDTH, config.HEIGHT, 2500)

    #icon = pygame.image.load("pygame-icon.png")
    #icon = icon.convert_alpha()
    #icon_w, icon_h = icon.get_size()

    shooting_force = -1

    bullets = []

    cannon1 = Cannon(screen, game_map, 100, (0, 255, 0), 0)
    cannon2 = Cannon(screen, game_map, config.WIDTH - 100, (255, 0, 0), 180)

    gameEnded = False

    while True:

        msg = {"cannon": {}}
        recv_msg = {}

        shooting_force, quitGame = getInputEvents(screen, cannon1, cannon2,
                                                  bullets, game_map,
                                                  shooting_force, msg)

        if quitGame:
            if TYPE == 'HOST':
                conn.close()

            if TYPE == 'JOIN' or TYPE == 'HOST':
                print('Closing Server...')
                return s

            return

        ping = time.time()
        if TYPE == 'HOST':
            try:
                conn.send(encodeDict(msg))
                recv_msg = conn.recv(1024)
                recv_msg = decodeDict(recv_msg)
            except ConnectionResetError:
                print('Connection closed by client')
                conn.close()
                return s
            except json.decoder.JSONDecodeError:
                print('Connection closed by client')
                conn.close()
                return s

        elif TYPE == 'JOIN':
            try:
                recv_msg = s.recv(1024)
                s.send(encodeDict(msg))
                recv_msg = decodeDict(recv_msg)
            except json.decoder.JSONDecodeError:
                print('Connection closed by host')
                return s
            except ConnectionResetError:
                print('Connection closed by host')
                return s

        if 'shoot' in recv_msg:
            if TYPE == 'JOIN':
                rot = cannon1.rot
                center = cannon1.base.rect.center
            else:
                rot = cannon2.rot
                center = cannon2.base.rect.center

            bullets.append(
                Bullet(screen, game_map, center[0], center[1],
                       recv_msg['shoot']['shooting_force'], rot))

        if 'cannon' in recv_msg:
            if 'rotation' in recv_msg['cannon']:
                rotation = recv_msg['cannon']['rotation']
                if TYPE == 'JOIN':
                    cannon1.rotating = rotation
                if TYPE == 'HOST':
                    cannon2.rotating = rotation

        ping = time.time() - ping

        #reset drawing
        screen.fill((0, 0, 0))

        #draw background
        game_map.printBackground(screen)

        #draw top info
        text = font.render('10uv >>', True, (255, 255, 255))
        fps_text = font.render(
            str(round(clock.get_fps(), 2)) + ' fps', True, (255, 255, 255))
        ping_text = font.render('ping ' + str(int(ping * 1000)) + 'ms', True,
                                (255, 255, 255))
        text_w, text_h = fps_text.get_size()
        ping_text_w, _ = ping_text.get_size()
        screen.blit(text, (0, 0))
        screen.blit(fps_text, (config.WIDTH - text_w, 0))
        screen.blit(ping_text, (config.WIDTH - ping_text_w, text_h))

        #draw map ground
        game_map.printGround(screen)

        #draw shoot bar
        if shooting_force != -1:
            if shooting_force < 50:
                shooting_force += 1

            hud.drawShootForce(screen, shooting_force)

        #draw shoot bar ruler
        hud.drawShootForceRuler(screen)

        #update and draw bullets
        for bullet in bullets[:]:
            if not bullet.update():
                if bullet.collide:
                    x = int(bullet.x)
                    collision_range = range(x - 15, x + 15)
                    for i in collision_range:  #TODO: send this to game map
                        if len(game_map.map_curve) > i >= 0:
                            game_map.map_curve[i] += 10

                    if not cannon1.isAlive(collision_range):
                        winnerText = font.render('Cannon 2 won.', True,
                                                 (255, 255, 255))
                        gameEnded = True
                        break
                    if not cannon2.isAlive(collision_range):
                        winnerText = font.render('Cannon 1 won.', True,
                                                 (255, 255, 255))
                        gameEnded = True
                        break

                bullets.remove(bullet)
                del bullet
            else:
                bullet.draw()

        #update and draw cannons
        cannon1.update()
        cannon1.draw(screen)
        cannon2.update()
        cannon2.draw(screen)

        if gameEnded:
            while len(bullets) > 0:
                bullets.pop()
            winnerText_w, winnerText_h = winnerText.get_size()
            screen.blit(winnerText, (config.WIDTH / 2 - winnerText_w / 2,
                                     config.HEIGHT / 2 - winnerText_h / 2))

        #end drawing
        pygame.display.flip()

        clock.tick(fps)
コード例 #4
0
ファイル: level.py プロジェクト: rubiximus/yars-revenge
class Level(GameState):
    """Level is a GameState with behavior for one full game level.

    Contains Sprites player, enemy, shield, hbullet, cannon, and Group player_bullets
    """
    
    def __init__(self, manager):
        """manager is required to be a child of GameManager with these functions:
        -kill_player()
        -next_level()
        -add_score(amount)
        -give_energy(amount)
        -spend_energy(amount)
        -give_life()
        """

        GameState.__init__(self, manager)

        self.player = Ship(*opt.player_args)
        self.enemy = EnemyBase(opt.mover_args, opt.spinner_args, opt.shooter_args, self.player)
        self.shield = EnemyShield(self.enemy, opt.shield_filename, formation, formation_center)
        self.hbullet = HomingBullet(opt.homer_filename, self.player, opt.homer_speed)
        self.cannon = Cannon(opt.deactivated_cannon_args, opt.standby_cannon_args,
                             opt.firing_cannon_args, self.player)
        self.ion_field = IonField(*opt.ion_field_args)
        self.player_bullets = Group()

        self.reset_positions()
        

    def update(self):
        self.player.update()
        self.enemy.update()
        self.shield.update()
        self.hbullet.update()
        self.cannon.update()
        self.player_bullets.update()
        self.ion_field.update()
        
        self.collisions()


    def handle_events(self, events, keys):
        return (event_handlers.check_quit(events, keys) and
                event_handlers.check_shoot_button(events, keys, self.shoot) and
                event_handlers.move_player(events, keys, self.player))


    def draw(self, screen):
        self.ion_field.draw(screen)
        self.enemy.draw(screen)
        self.shield.draw(screen)
        self.player.draw(screen)
        self.hbullet.draw(screen)
        self.cannon.draw(screen)
        self.player_bullets.draw(screen)


    def collisions(self):
        """Handles collisions
        """

        player = self.player
        enemy = self.enemy
        shield = self.shield
        hbullet = self.hbullet
        cannon = self.cannon
        ion_field = self.ion_field
        player_bullets = self.player_bullets
        
        #player with homing bullet
        if collide_mask(player, hbullet) and not collide_mask(player, ion_field):
            self.kill_player()
        
        #player with enemy base
        if collide_mask(player, enemy):
            #if base in moving phase, give player energy
            if enemy.get_state_number() == EnemyBase.MOVING:
                self.manager.give_energy(opt.energy_from_enemy)
            #if base in spinning or shooting phase, kill player
            elif enemy.get_state_number() == EnemyBase.SPINNING:
                self.kill_player()
            elif enemy.get_state_number() == EnemyBase.SHOOTING:
                self.kill_player()
                
        #player with cell
        #-hitting a cell will bounce the player a bit to the left
        #-if the player hit the cell twice in a short enough span,
        # the cell is eaten and the player gets energy
        #-in case of multiple collisions, deal with cell closest to player's center
        #
        #TODO: This still isn't quite right.
        #Should be able to eat top/bottom rows with diagonal movement.
        #(vertical movement should still move player all the way left)
        pc_collides = spritecollide(player, shield, False, collide_mask)
        center_cell = self.find_centermost_cell(pc_collides)
        if center_cell is not None:
            player.rect.right = center_cell.rect.left - opt.cell_bounceback
            
            if not center_cell.marked:
                center_cell.mark()
            elif shield.can_eat():
                center_cell.kill()
                self.manager.give_energy(opt.energy_from_cell)
                self.manager.add_score(opt.score_cell_eat)
                shield.start_delay(opt.frames_to_eat_cell)
            
        #player with cannon
        if collide_mask(player, cannon):
            #if in deactivated phase, try spending required energy to activate
            if (cannon.get_state_number() == Cannon.DEACTIVATED and 
                self.manager.spend_energy(opt.cannon_energy_cost)):
                cannon.start_standby()
            #if in firing phase, kill player
            if cannon.get_state_number() == Cannon.FIRING:
                self.kill_player()
            #if in returning phase, give energy and deactivate cannon
            if cannon.get_state_number() == Cannon.RETURNING:
                cannon.start_transition(Cannon.DEACTIVATED)
                self.manager.give_energy(opt.energy_from_cannon)
                
        #cannon with cell
        #kill one cell and reverse cannon direction
        #assuming this is only possible if cannon in firing state
        if cannon.get_state_number() == Cannon.FIRING:
            cannon_collides = spritecollide(cannon, shield, False, collide_mask)
            if len(cannon_collides) > 0:
                cannon_collides[0].kill()
                self.manager.add_score(opt.score_cell_shoot)
                cannon.start_transition(Cannon.RETURNING)
            
        #cannon with enemy base -- only if cannon in firing state
        #give points corresponding to enemy state and end level
        #if enemy base is in shooting state, player also gets a life
        if cannon.get_state_number() == Cannon.FIRING and collide_mask(cannon, enemy):
            if enemy.get_state_number() == EnemyBase.MOVING:
                self.manager.add_score(opt.score_mover_destroy)
            elif enemy.get_state_number() == EnemyBase.SPINNING:
                self.manager.add_score(opt.score_spinner_destroy)
            elif enemy.get_state_number() == EnemyBase.SHOOTING:
                self.manager.add_score(opt.score_shooter_destroy)
                self.manager.give_life()
            self.end_level()
        
        #player's bullet with cell
        #kill player bullet but remove cells in a cross pattern
        #if somehow one bullet hits multiple cells one is arbitrarily selected
        bc_collides = groupcollide(player_bullets, shield, True, False, collide_mask)
        for current_bullet in bc_collides.keys():
            self.manager.add_score(opt.score_cell_shoot)
            shield.remove_cross(bc_collides[current_bullet][0])


    def find_centermost_cell(self, cells):
        """Given a list of Cell sprites, 
        returns the one whose rect.centery is closest to the player's rect.centery
        
        Returns None if list is empty
        """
        
        closest_cell = None
        
        for current_cell in cells:
            current_dist = abs(current_cell.rect.centery - self.player.rect.centery)
            if closest_cell is None or current_dist < closest_dist:
                closest_cell = current_cell
                closest_dist = current_dist

        return closest_cell


    def shoot(self):
        """If cannon can be fired, fires cannon.
        
        Otherwise creates bullet moving from player's center along player's direction
        as long as options.max_player_bullets won't be exeeded
        """
        
        if collide_mask(self.player, self.ion_field):
            return

        if self.cannon.start_transition(Cannon.FIRING):
            return        
        
        if len(self.player_bullets) < opt.max_player_bullets:
            new_bullet = Bullet(opt.bullet_filename, opt.bullet_speed, 
                                self.player.get_rect().center, self.player.get_direction())
            self.player_bullets.add(new_bullet)


    def reset_positions(self):
        """Moves sprites to their initial locations:
        player starts in left center facing south and with 0 energy
        player bullets are removed
        enemy bullet starts on enemy base
        enemy base is in moving state
        cannon is in deactivated state

        Note: shield configuration is not reset
        """

        self.player.rect.midleft = (20, int(opt.height/2))
        self.player.set_direction(vector.SOUTH)
        self.player_bullets.empty()
        self.enemy.resume_mover_state()
        self.cannon.start_deactivated()
        self.hbullet.rect.center = self.enemy.rect.center


    def kill_player(self):
        death_animation = DeathAnimation(self.manager, self.player, (self.enemy, self.shield), self,
                opt.death_animation_delay, opt.death_animation_total_runtime)
        self.manager.change_state(death_animation)


    def end_level(self):
        win_animation = WinAnimation(self.manager, self.player, opt.win_animation_total_runtime, opt.exp_field_args)
        self.manager.change_state(win_animation)