forked from ehmatthes/pcc_3e
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Eric Matthes
authored and
Eric Matthes
committed
Feb 12, 2023
1 parent
3358651
commit 879ca97
Showing
12 changed files
with
597 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import pygame | ||
from pygame.sprite import Sprite | ||
|
||
class Alien(Sprite): | ||
"""A class to represent a single alien in the fleet.""" | ||
|
||
def __init__(self, ai_game): | ||
"""Initialize the alien and set its starting position.""" | ||
super().__init__() | ||
self.screen = ai_game.screen | ||
self.settings = ai_game.settings | ||
|
||
# Load the alien image and set its rect attribute. | ||
self.image = pygame.image.load('images/alien.bmp') | ||
self.rect = self.image.get_rect() | ||
|
||
# Start each new alien near the top left of the screen. | ||
self.rect.x = self.rect.width | ||
self.rect.y = self.rect.height | ||
|
||
# Store the alien's exact horizontal position. | ||
self.x = float(self.rect.x) | ||
|
||
def check_edges(self): | ||
"""Return True if alien is at edge of screen.""" | ||
screen_rect = self.screen.get_rect() | ||
if self.rect.right >= screen_rect.right or self.rect.left <= 0: | ||
return True | ||
|
||
def update(self): | ||
"""Move the alien right or left.""" | ||
self.x += (self.settings.alien_speed * | ||
self.settings.fleet_direction) | ||
self.rect.x = self.x |
284 changes: 284 additions & 0 deletions
284
solution_files/chapter_14/ex_14_6_refactoring/alien_invasion.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,284 @@ | ||
import sys | ||
from time import sleep | ||
import json | ||
from pathlib import Path | ||
|
||
import pygame | ||
|
||
from settings import Settings | ||
from game_stats import GameStats | ||
from scoreboard import Scoreboard | ||
from button import Button | ||
from ship import Ship | ||
from bullet import Bullet | ||
from alien import Alien | ||
|
||
|
||
class AlienInvasion: | ||
"""Overall class to manage game assets and behavior.""" | ||
|
||
def __init__(self): | ||
"""Initialize the game, and create game resources.""" | ||
pygame.init() | ||
self.clock = pygame.time.Clock() | ||
self.settings = Settings() | ||
|
||
self.screen = pygame.display.set_mode( | ||
(self.settings.screen_width, self.settings.screen_height)) | ||
pygame.display.set_caption("Alien Invasion") | ||
|
||
# Create an instance to store game statistics, | ||
# and create a scoreboard. | ||
self.stats = GameStats(self) | ||
self.sb = Scoreboard(self) | ||
|
||
self.ship = Ship(self) | ||
self.bullets = pygame.sprite.Group() | ||
self.aliens = pygame.sprite.Group() | ||
|
||
self._create_fleet() | ||
|
||
# Make the Play button. | ||
self.play_button = Button(self, "Play") | ||
|
||
# Start game in an inactive state. | ||
self.game_active = False | ||
|
||
def run_game(self): | ||
"""Start the main loop for the game.""" | ||
while True: | ||
self._check_events() | ||
|
||
if self.game_active: | ||
self.ship.update() | ||
self._update_bullets() | ||
self._update_aliens() | ||
|
||
self._update_screen() | ||
self.clock.tick(60) | ||
|
||
def _check_events(self): | ||
"""Respond to keypresses and mouse events.""" | ||
for event in pygame.event.get(): | ||
if event.type == pygame.QUIT: | ||
self._close_game() | ||
elif event.type == pygame.KEYDOWN: | ||
self._check_keydown_events(event) | ||
elif event.type == pygame.KEYUP: | ||
self._check_keyup_events(event) | ||
elif event.type == pygame.MOUSEBUTTONDOWN: | ||
mouse_pos = pygame.mouse.get_pos() | ||
self._check_play_button(mouse_pos) | ||
|
||
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.game_active: | ||
self._start_game() | ||
|
||
def _start_game(self): | ||
"""Start a new game.""" | ||
# Reset the game settings. | ||
self.settings.initialize_dynamic_settings() | ||
|
||
# Reset the game statistics. | ||
self.stats.reset_stats() | ||
self.game_active = True | ||
self.sb.prep_score() | ||
self.sb.prep_level() | ||
self.sb.prep_ships() | ||
|
||
# Get rid of any remaining aliens and bullets. | ||
self.aliens.empty() | ||
self.bullets.empty() | ||
|
||
# Create a new fleet and center the ship. | ||
self._create_fleet() | ||
self.ship.center_ship() | ||
|
||
# Hide the mouse cursor. | ||
pygame.mouse.set_visible(False) | ||
|
||
def _check_keydown_events(self, event): | ||
"""Respond to keypresses.""" | ||
if event.key == pygame.K_RIGHT: | ||
self.ship.moving_right = True | ||
elif event.key == pygame.K_LEFT: | ||
self.ship.moving_left = True | ||
elif event.key == pygame.K_q: | ||
self._close_game() | ||
elif event.key == pygame.K_SPACE: | ||
self._fire_bullet() | ||
elif event.key == pygame.K_p and not self.game_active: | ||
self._start_game() | ||
|
||
def _check_keyup_events(self, event): | ||
"""Respond to key releases.""" | ||
if event.key == pygame.K_RIGHT: | ||
self.ship.moving_right = False | ||
elif event.key == pygame.K_LEFT: | ||
self.ship.moving_left = False | ||
|
||
def _fire_bullet(self): | ||
"""Create a new bullet and add it to the bullets group.""" | ||
if len(self.bullets) < self.settings.bullets_allowed: | ||
new_bullet = Bullet(self) | ||
self.bullets.add(new_bullet) | ||
|
||
def _update_bullets(self): | ||
"""Update position of bullets and get rid of old bullets.""" | ||
# Update bullet positions. | ||
self.bullets.update() | ||
|
||
# Get rid of bullets that have disappeared. | ||
for bullet in self.bullets.copy(): | ||
if bullet.rect.bottom <= 0: | ||
self.bullets.remove(bullet) | ||
|
||
self._check_bullet_alien_collisions() | ||
|
||
def _check_bullet_alien_collisions(self): | ||
"""Respond to bullet-alien collisions.""" | ||
# Remove any bullets and aliens that have collided. | ||
collisions = pygame.sprite.groupcollide( | ||
self.bullets, self.aliens, True, True) | ||
|
||
if collisions: | ||
for aliens in collisions.values(): | ||
self.stats.score += self.settings.alien_points * len(aliens) | ||
self.sb.prep_score() | ||
self.sb.check_high_score() | ||
|
||
if not self.aliens: | ||
self._start_new_level() | ||
|
||
def _start_new_level(self): | ||
"""Start a new level, after the fleet has been cleared.""" | ||
# Destroy existing bullets and create new fleet. | ||
self.bullets.empty() | ||
self._create_fleet() | ||
self.settings.increase_speed() | ||
|
||
# Increase level. | ||
self.stats.level += 1 | ||
self.sb.prep_level() | ||
|
||
def _update_aliens(self): | ||
""" | ||
Check if the fleet is at an edge, | ||
then update the positions of all aliens in the fleet. | ||
""" | ||
self._check_fleet_edges() | ||
self.aliens.update() | ||
|
||
# Look for alien-ship collisions. | ||
if pygame.sprite.spritecollideany(self.ship, self.aliens): | ||
self._ship_hit() | ||
|
||
# Look for aliens hitting the bottom of the screen. | ||
self._check_aliens_bottom() | ||
|
||
def _check_aliens_bottom(self): | ||
"""Check if any aliens have reached the bottom of the screen.""" | ||
screen_rect = self.screen.get_rect() | ||
for alien in self.aliens.sprites(): | ||
if alien.rect.bottom >= screen_rect.bottom: | ||
# Treat this the same as if the ship got hit. | ||
self._ship_hit() | ||
break | ||
|
||
def _ship_hit(self): | ||
"""Respond to the ship being hit by an alien.""" | ||
if self.stats.ships_left > 0: | ||
# Decrement ships_left, and update scoreboard. | ||
self.stats.ships_left -= 1 | ||
self.sb.prep_ships() | ||
|
||
# Get rid of any remaining aliens and bullets. | ||
self.aliens.empty() | ||
self.bullets.empty() | ||
|
||
# Create a new fleet and center the ship. | ||
self._create_fleet() | ||
self.ship.center_ship() | ||
|
||
# Pause. | ||
sleep(0.5) | ||
else: | ||
self.game_active = False | ||
pygame.mouse.set_visible(True) | ||
|
||
def _create_fleet(self): | ||
"""Create the fleet of aliens.""" | ||
# Create an alien and find the number of aliens in a row. | ||
# Spacing between each alien is equal to one alien width. | ||
alien = Alien(self) | ||
alien_width, alien_height = alien.rect.size | ||
available_space_x = self.settings.screen_width - (2 * alien_width) | ||
number_aliens_x = available_space_x // (2 * alien_width) | ||
|
||
# Determine the number of rows of aliens that fit on the screen. | ||
ship_height = self.ship.rect.height | ||
available_space_y = (self.settings.screen_height - | ||
(3 * alien_height) - ship_height) | ||
number_rows = available_space_y // (2 * alien_height) | ||
|
||
# Create the full fleet of aliens. | ||
for row_number in range(number_rows): | ||
for alien_number in range(number_aliens_x): | ||
self._create_alien(alien_number, row_number) | ||
|
||
def _create_alien(self, alien_number, row_number): | ||
"""Create an alien and place it in the row.""" | ||
alien = Alien(self) | ||
alien_width, alien_height = alien.rect.size | ||
alien.x = alien_width + 2 * alien_width * alien_number | ||
alien.rect.x = alien.x | ||
alien.rect.y = alien.rect.height + 2 * alien.rect.height * row_number | ||
self.aliens.add(alien) | ||
|
||
def _check_fleet_edges(self): | ||
"""Respond appropriately if any aliens have reached an edge.""" | ||
for alien in self.aliens.sprites(): | ||
if alien.check_edges(): | ||
self._change_fleet_direction() | ||
break | ||
|
||
def _change_fleet_direction(self): | ||
"""Drop the entire fleet and change the fleet's direction.""" | ||
for alien in self.aliens.sprites(): | ||
alien.rect.y += self.settings.fleet_drop_speed | ||
self.settings.fleet_direction *= -1 | ||
|
||
def _update_screen(self): | ||
"""Update images on the screen, and flip to the new screen.""" | ||
self.screen.fill(self.settings.bg_color) | ||
self.ship.blitme() | ||
for bullet in self.bullets.sprites(): | ||
bullet.draw_bullet() | ||
self.aliens.draw(self.screen) | ||
|
||
# Draw the score information. | ||
self.sb.show_score() | ||
|
||
# Draw the play button if the game is inactive. | ||
if not self.game_active: | ||
self.play_button.draw_button() | ||
|
||
pygame.display.flip() | ||
|
||
def _close_game(self): | ||
"""Save high score and exit.""" | ||
saved_high_score = self.stats.get_saved_high_score() | ||
if self.stats.high_score > saved_high_score: | ||
path = Path('high_score.json') | ||
contents = json.dumps(self.stats.high_score) | ||
path.write_text(contents) | ||
|
||
sys.exit() | ||
|
||
|
||
if __name__ == '__main__': | ||
# Make a game instance, and run the game. | ||
ai = AlienInvasion() | ||
ai.run_game() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import pygame | ||
from pygame.sprite import Sprite | ||
|
||
class Bullet(Sprite): | ||
"""A class to manage bullets fired from the ship""" | ||
|
||
def __init__(self, ai_game): | ||
"""Create a bullet object at the ship's current position.""" | ||
super().__init__() | ||
self.screen = ai_game.screen | ||
self.settings = ai_game.settings | ||
self.color = self.settings.bullet_color | ||
|
||
# Create a bullet rect at (0, 0) and then set correct position. | ||
self.rect = pygame.Rect(0, 0, self.settings.bullet_width, | ||
self.settings.bullet_height) | ||
self.rect.midtop = ai_game.ship.rect.midtop | ||
|
||
# Store the bullet's position as a decimal value. | ||
self.y = float(self.rect.y) | ||
|
||
def update(self): | ||
"""Move the bullet up the screen.""" | ||
# Update the decimal position of the bullet. | ||
self.y -= self.settings.bullet_speed | ||
# Update the rect position. | ||
self.rect.y = self.y | ||
|
||
def draw_bullet(self): | ||
"""Draw the bullet to the screen.""" | ||
pygame.draw.rect(self.screen, self.color, self.rect) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import pygame.font | ||
|
||
class Button: | ||
|
||
def __init__(self, ai_game, msg): | ||
"""Initialize button attributes.""" | ||
self.screen = ai_game.screen | ||
self.screen_rect = self.screen.get_rect() | ||
|
||
# Set the dimensions and properties of the button. | ||
self.width, self.height = 200, 50 | ||
self.button_color = (0, 135, 0) | ||
self.text_color = (255, 255, 255) | ||
self.font = pygame.font.SysFont(None, 48) | ||
|
||
# Build the button's rect object and center it. | ||
self.rect = pygame.Rect(0, 0, self.width, self.height) | ||
self.rect.center = self.screen_rect.center | ||
|
||
# The button message needs to be prepped only once. | ||
self._prep_msg(msg) | ||
|
||
def _prep_msg(self, msg): | ||
"""Turn msg into a rendered image and center text on the button.""" | ||
self.msg_image = self.font.render(msg, True, self.text_color, | ||
self.button_color) | ||
self.msg_image_rect = self.msg_image.get_rect() | ||
self.msg_image_rect.center = self.rect.center | ||
|
||
def draw_button(self): | ||
# Draw blank button and then draw message. | ||
self.screen.fill(self.button_color, self.rect) | ||
self.screen.blit(self.msg_image, self.msg_image_rect) |
Oops, something went wrong.