diff --git a/alien_invasion.py b/alien_invasion.py index d45d10d..bc7b822 100644 --- a/alien_invasion.py +++ b/alien_invasion.py @@ -6,6 +6,8 @@ from alien import Alien from time import sleep from game_stats import GameStats +from button import Button +from scoreboard import Scoreboard class AlienInvasion: @@ -23,7 +25,9 @@ def __init__(self) -> None: pygame.display.set_caption("Alien Invasion") # Create an instance to store game stats + # and create a scoreboard self.stats = GameStats(self) + self.sb = Scoreboard(self) self.ship = Ship(self) self.bullets = pygame.sprite.Group() @@ -34,13 +38,21 @@ def __init__(self) -> None: # Set the background color. self.bg_color = (230, 230, 230) + # Start game in an inactive state. + self.game_active = False + + # Make the Play button + self.play_button = Button(self, "Play") + def run_game(self): """Start the main loop for the game.""" while True: self._check_events() - self.ship.update() - self._update_bullets() - self._update_aliens() + + if self.game_active: + self.ship.update() + self._update_bullets() + self._update_aliens() self._update_screen() self.clock.tick(60) @@ -54,6 +66,32 @@ def _check_events(self): 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 when player clicks on Play button""" + button_clicked = self.play_button.rect.collidepoint(mouse_pos) + if button_clicked and not self.game_active: + # Reset game speed settings + self.settings.initialize_dynamic_settings() + + # Reset game statistics. + self.stats.reset_stats() + self.sb.prep_score() + self.game_active = True + + # Empty screen of aliens & bullets. + self.bullets.empty() + self.aliens.empty() + + # Create a new fleet & recenter 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""" @@ -83,24 +121,30 @@ def _fire_bullet(self): def _update_bullets(self): """Update position of bullets & delete old bullets.""" # Update bullet positions. - self.bullets.update() + self.bullets.update() # Delete bullets at top of screen for bullet in self.bullets.copy(): if bullet.rect.bottom <= 0: self.bullets.remove(bullet) - self._check_bullet_alien_collisions() + self._check_bullet_alien_collisions() def _check_bullet_alien_collisions(self): """Respond to bullet-alien collisions""" # Remove any bullets & aliens that have collided - collisions = pygame.sprite.groupcollide( - self.bullets, self.aliens, True, True) + 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() + if not self.aliens: - #destroy existing bullets & make new fleet + # destroy existing bullets & make new fleet self.bullets.empty() self._create_fleet() + self.settings.increase_speed() def _update_aliens(self): """Check if the fleet is at an edge, then update positions""" @@ -111,23 +155,26 @@ def _update_aliens(self): 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 _create_fleet(self): """Create the fleet of aliens""" # Create an alien and keep adding aliens until there's no room left. # Spacing between aliens is one alien width and one alien height. alien = Alien(self) - alien_width, alien_height = alien.rect.size + alien_width, alien_height = alien.rect.size current_x, current_y = alien_width, alien_height while current_y < (self.settings.screen_height - 3 * alien_height): while current_x < (self.settings.screen_width - 2 * alien_width): self._create_alien(current_x, current_y) current_x += 2 * alien_width - + # Finish a row: reset x value, increment y value current_x = alien_width current_y += 2 * alien_height - + def _create_alien(self, x_position, y_position): """Create an alien and place it in the row""" new_alien = Alien(self) @@ -151,17 +198,21 @@ def _change_fleet_direction(self): def _ship_hit(self): """Actions when alien hits ship""" - # Decrement ships left - self.stats.ships_left -= 1 + if self.stats.ships_left > 0: + # Decrement ships left + self.stats.ships_left -= 1 - # Reset the game board - self.bullets.empty() - self.aliens.empty() - self._create_fleet() - self.ship.center_ship() + # Reset the game board + self.bullets.empty() + self.aliens.empty() + self._create_fleet() + self.ship.center_ship() - # Pause - sleep(0.5) + # Pause + sleep(0.5) + else: + self.game_active = False + pygame.mouse.set_visible(True) def _check_aliens_bottom(self): """Check if any aliens have reached the bottom of the screen""" @@ -171,7 +222,6 @@ def _check_aliens_bottom(self): self._ship_hit() break - def _update_screen(self): """Update images on the screen, and flip to the new screen.""" self.screen.fill(self.settings.bg_color) @@ -180,8 +230,16 @@ def _update_screen(self): self.ship.blitme() self.aliens.draw(self.screen) + # Draw the score info. + 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() + if __name__ == "__main__": # Make a game instance & run the game. ai = AlienInvasion() diff --git a/button.py b/button.py new file mode 100644 index 0000000..db50593 --- /dev/null +++ b/button.py @@ -0,0 +1,33 @@ +import pygame.font + +class Button: + """A class to build buttons for a game""" + def __init__(self, ai_game, msg) -> None: + """Init button attribs""" + self.screen = ai_game.screen + self.screen_rect = self.screen.get_rect() + + # Set dimensions & properties of 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 & place it. + self.rect = pygame.Rect(0, 0, self.width, self.height) + self.rect.center = self.screen_rect.center + + # Button msg needs to be prepped once. + self._prep_msg(msg) + + def _prep_msg(self, msg): + """Turn msg into a rendered image & center it 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) + \ No newline at end of file diff --git a/game_stats.py b/game_stats.py index 17d8b62..e6beafc 100644 --- a/game_stats.py +++ b/game_stats.py @@ -9,3 +9,5 @@ def __init__(self, ai_game) -> None: def reset_stats(self): """Init stats that can change during the game""" self.ships_left = self.settings.ship_limit + self.score = 0 + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5988648 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +pygame==2.5.2 diff --git a/scoreboard.py b/scoreboard.py new file mode 100644 index 0000000..be46b1b --- /dev/null +++ b/scoreboard.py @@ -0,0 +1,34 @@ +import pygame.font + +class Scoreboard: + """A class to report scoring info""" + + def __init__(self, ai_game,): + """Init scorekeeping attribs""" + self.screen = ai_game.screen + self.screen_rect = self.screen.get_rect() + self.settings = ai_game.settings + self.stats = ai_game.stats + + # Font settings for scoring + self.text_color = (30, 30, 30) + self.font = pygame.font.SysFont(None, 48) + + # Prep the score image + self.prep_score() + + def prep_score(self): + """Turn the score into an image""" + rounded_score = round(self.stats.score, -1) + score_str = f"{rounded_score:,}" + score_str = str(self.stats.score) + self.score_image = self.font.render(score_str, True, self.text_color, self.settings.bg_color) + + # Display score at top right of screen. + self.score_rect = self.score_image.get_rect() + self.score_rect.right = self.screen_rect.right - 20 + self.score_rect.top = 20 + + def show_score(self): + """Draw score to the screen""" + self.screen.blit(self.score_image, self.score_rect) \ No newline at end of file diff --git a/settings.py b/settings.py index db89884..1a4c43d 100644 --- a/settings.py +++ b/settings.py @@ -2,25 +2,48 @@ class Settings: """A class to store all settings for Alien Invasion""" def __init__(self) -> None: - """Initialize the game's settings""" + """Initialize the game's static settings""" # Screen settings self.screen_width = 1200 self.screen_height = 800 self.bg_color = (230, 230, 230) # Ship settings - self.ship_speed = 1.5 self.ship_limit = 5 # Bullet settings - self.bullet_speed = 4.0 - self.bullet_width = 3 + + self.bullet_width = 300 self.bullet_height = 15 self.bullet_color = (60, 60, 60) self.bullets_allowed = 5 #Alien settings - self.alien_speed = 1.0 self.fleet_drop_speed = 10 + + + # How quickly the game speeds up. + self.speedup_scale = 1.1 + # Increase alien point value for new levels + self.score_scale = 1.5 + + self.initialize_dynamic_settings() + + def initialize_dynamic_settings(self): + """Init settings that change throughout the game""" + self.ship_speed = 1.5 + self.bullet_speed = 4.0 + self.alien_speed = 1.0 # fleet direction 1 = right, -1 = left self.fleet_direction = 1 + + # Scoring settings + self.alien_points = 50 + + def increase_speed(self): + """Increase speed settings and alien point values""" + self.ship_speed *= self.speedup_scale + self.bullet_speed *= self.speedup_scale + self.alien_speed *= self.speedup_scale + + self.alien_points = int(self.alien_points * self.score_scale) \ No newline at end of file