diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..7e614a5 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,10 @@ +{ + "permissions": { + "allow": [ + "Bash(python:*)", + "Bash(pkill:*)", + "mcp__ide__getDiagnostics" + ], + "deny": [] + } +} \ No newline at end of file diff --git a/alien.py b/alien.py index c92933c..591c910 100644 --- a/alien.py +++ b/alien.py @@ -1,9 +1,14 @@ +from __future__ import annotations import pygame from pygame.sprite import Sprite +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from alien_invasion import AlienInvasion class Alien(Sprite): """A class to represent a single alien in the fleet.""" - def __init__(self, ai_game): + def __init__(self, ai_game: 'AlienInvasion') -> None: super().__init__() self.screen = ai_game.screen self.settings = ai_game.settings @@ -13,12 +18,12 @@ class Alien(Sprite): self.rect.y = self.rect.height self.x = float(self.rect.x) - def check_edges(self): + def check_edges(self) -> bool: """Return True if alien is at edge of screen.""" screen_rect = self.screen.get_rect() return self.rect.right >= screen_rect.right or self.rect.left <= 0 - def update(self): + def update(self) -> None: """Move the alien right or left.""" self.x += self.settings.alien_speed * self.settings.fleet_direction - self.rect.x = self.x + self.rect.x = int(self.x) diff --git a/alien_invasion.py b/alien_invasion.py index 43de0af..5eb9629 100644 --- a/alien_invasion.py +++ b/alien_invasion.py @@ -1,3 +1,4 @@ +from __future__ import annotations import sys from time import sleep import pygame @@ -9,12 +10,13 @@ from ship import Ship from bullet import Bullet from alien import Alien from pygame.sprite import Group +from typing import Tuple class AlienInvasion: """Overall class to manage game assets and behavior.""" - def __init__(self): + def __init__(self) -> None: pygame.init() self.clock = pygame.time.Clock() self.settings = Settings() @@ -26,13 +28,13 @@ class AlienInvasion: self.stats = GameStats(self) self.score_board = ScoreBoard(self) self.ship = Ship(self) - self.bullets = Group() - self.aliens = Group() + self.bullets: Group = Group() # type: ignore + self.aliens: Group = Group() # type: ignore self._create_fleet() self.game_active = False self.play_button = Button(self, "Play") - def run_game(self): + def run_game(self) -> None: """Start the main loop for the game.""" while True: # Check for events and update the game state. @@ -46,7 +48,7 @@ class AlienInvasion: self._update_screen() self.clock.tick(60) - def _create_fleet(self): + def _create_fleet(self) -> None: """Create a fleet of aliens.""" alien = Alien(self) alien_width, alien_height = alien.rect.size @@ -58,28 +60,28 @@ class AlienInvasion: current_x = alien_width current_y += 2 * alien_height - def _create_alien(self, x_position, y_position): + def _create_alien(self, x_position: int, y_position: int) -> None: """Create an alien and place it in the fleet.""" alien = Alien(self) alien.x = x_position alien.rect.x = x_position alien.rect.y = y_position - self.aliens.add(alien) + self.aliens.add(alien) # type: ignore - def _check_fleet_edges(self): + def _check_fleet_edges(self) -> None: """Respond appropriately if any aliens have reached an edge.""" - for alien in self.aliens.sprites(): - if alien.check_edges(): + for alien in self.aliens.sprites(): # type: ignore + if alien.check_edges(): # type: ignore self._change_fleet_direction() break - def _change_fleet_direction(self): + def _change_fleet_direction(self) -> None: """Drop the entire fleet and change its direction.""" - for alien in self.aliens.sprites(): - alien.rect.y += self.settings.fleet_drop_speed + for alien in self.aliens.sprites(): # type: ignore + alien.rect.y += self.settings.fleet_drop_speed # type: ignore self.settings.fleet_direction *= -1 - def _check_events(self): + def _check_events(self) -> None: """Respond to keypresses and mouse events.""" for event in pygame.event.get(): if event.type == pygame.QUIT: @@ -92,7 +94,7 @@ class AlienInvasion: mouse_pos = pygame.mouse.get_pos() self._check_play_button(mouse_pos) - def _check_keydown_events(self, event): + def _check_keydown_events(self, event: pygame.event.Event) -> None: """Respond to key presses.""" if event.key == pygame.K_RIGHT: self.ship.moving_right = True @@ -103,14 +105,14 @@ class AlienInvasion: elif event.key == pygame.K_SPACE: self._fire_bullet() - def _check_keyup_events(self, event): + def _check_keyup_events(self, event: pygame.event.Event) -> None: """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 _check_play_button(self, mouse_pos): + def _check_play_button(self, mouse_pos: Tuple[int, int]) -> None: """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: @@ -120,62 +122,62 @@ class AlienInvasion: self.score_board.prep_level() self.score_board.prep_ships() self.game_active = True - self.bullets.empty() - self.aliens.empty() + self.bullets.empty() # type: ignore + self.aliens.empty() # type: ignore self._create_fleet() self.ship.center_ship() pygame.mouse.set_visible(False) - def _fire_bullet(self): + def _fire_bullet(self) -> None: """Fire a bullet if the limit is not reached.""" - if len(self.bullets) < self.settings.bullets_allowed: + if len(self.bullets) < self.settings.bullets_allowed: # type: ignore # Create a new bullet and add it to the bullets group. new_bullet = Bullet(self) - self.bullets.add(new_bullet) + self.bullets.add(new_bullet) # type: ignore - def _update_bullets(self): + def _update_bullets(self) -> None: """Update position of bullets and remove old bullets.""" - self.bullets.update() - for bullet in self.bullets.copy(): - if bullet.rect.bottom <= 0: - self.bullets.remove(bullet) + self.bullets.update() # type: ignore + for bullet in self.bullets.copy(): # type: ignore + if bullet.rect.bottom <= 0: # type: ignore + self.bullets.remove(bullet) # type: ignore self._check_bullet_alien_collisions() - def _check_alien_bottom(self): + def _check_alien_bottom(self) -> None: """Check if any aliens have reached the bottom of the screen.""" - for alien in self.aliens.sprites(): - if alien.rect.bottom >= self.settings.screen_height: + for alien in self.aliens.sprites(): # type: ignore + if alien.rect.bottom >= self.settings.screen_height: # type: ignore self._ship_hit() break - def _check_bullet_alien_collisions(self): + def _check_bullet_alien_collisions(self) -> None: """Respond to bullet-alien collisions.""" - collisions = pygame.sprite.groupcollide(self.bullets, self.aliens, True, True) + collisions = pygame.sprite.groupcollide(self.bullets, self.aliens, True, True) # type: ignore if collisions: - for aliens in collisions.values(): - self.stats.score += self.settings.alien_points * len(aliens) + for aliens in collisions.values(): # type: ignore + self.stats.score += self.settings.alien_points * len(aliens) # type: ignore self.score_board.prep_score() self.score_board.check_high_score() self.score_board.prep_level() self.score_board.prep_ships() - if not self.aliens: + if not self.aliens: # type: ignore # If the fleet is empty, create a new fleet. - self.bullets.empty() + self.bullets.empty() # type: ignore self._create_fleet() self.settings.increase_speed() self.stats.level += 1 self.score_board.prep_level() - def _ship_hit(self): + def _ship_hit(self) -> None: """Respond to the ship being hit by an alien.""" if self.stats.ships_left > 0: # Decrement ships_left and reset the game state. self.stats.ships_left -= 1 self.score_board.prep_ships() - self.bullets.empty() - self.aliens.empty() + self.bullets.empty() # type: ignore + self.aliens.empty() # type: ignore self._create_fleet() self.ship.center_ship() sleep(0.5) @@ -183,22 +185,22 @@ class AlienInvasion: self.game_active = False pygame.mouse.set_visible(True) - def _update_aliens(self): + def _update_aliens(self) -> None: """Update the positions of aliens.""" self._check_fleet_edges() - self.aliens.update() - if pygame.sprite.spritecollideany(self.ship, self.aliens): + self.aliens.update() # type: ignore + if pygame.sprite.spritecollideany(self.ship, self.aliens): # type: ignore self._ship_hit() self._check_bullet_alien_collisions() self._check_alien_bottom() - def _update_screen(self): + def _update_screen(self) -> None: """Update images on the screen and flip to the new screen.""" self.screen.fill(self.bg_color) - for bullet in self.bullets.sprites(): - bullet.draw_bullet() + for bullet in self.bullets.sprites(): # type: ignore + bullet.draw_bullet() # type: ignore self.ship.blitme() - self.aliens.draw(self.screen) + self.aliens.draw(self.screen) # type: ignore self.score_board.show_score() if not self.game_active: self.play_button.draw_button() diff --git a/bullet.py b/bullet.py index cb0c09a..54bba87 100644 --- a/bullet.py +++ b/bullet.py @@ -1,10 +1,15 @@ +from __future__ import annotations import pygame from pygame.sprite import Sprite +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from alien_invasion import AlienInvasion class Bullet(Sprite): """A class to manage bullets fired from the ship.""" - def __init__(self, ai_game): + def __init__(self, ai_game: 'AlienInvasion') -> None: """Create a bullet object at the ship's current position.""" super().__init__() self.screen = ai_game.screen @@ -14,11 +19,11 @@ class Bullet(Sprite): self.rect.midtop = ai_game.ship.rect.midtop self.y = float(self.rect.y) - def update(self): + def update(self) -> None: """Move the bullet up the screen.""" self.y -= self.settings.bullet_speed - self.rect.y = self.y + self.rect.y = int(self.y) - def draw_bullet(self): + def draw_bullet(self) -> None: """Draw the bullet to the screen.""" pygame.draw.rect(self.screen, self.color, self.rect) diff --git a/button.py b/button.py index d8d868c..b85b1ae 100644 --- a/button.py +++ b/button.py @@ -1,7 +1,12 @@ +from __future__ import annotations import pygame.font +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from alien_invasion import AlienInvasion class Button: - def __init__(self, ai_game, msg): + def __init__(self, ai_game: 'AlienInvasion', msg: str) -> None: """Initialize button attributes.""" self.screen = ai_game.screen self.screen_rect = self.screen.get_rect() @@ -19,13 +24,13 @@ class Button: # The button message needs to be prepped only once. self._prep_msg(msg) - def _prep_msg(self, msg): + def _prep_msg(self, msg: str) -> None: """Turn the message 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): + def draw_button(self) -> None: """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) diff --git a/game_stats.py b/game_stats.py index 0a5aee5..86c7564 100644 --- a/game_stats.py +++ b/game_stats.py @@ -1,13 +1,19 @@ +from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from alien_invasion import AlienInvasion + class GameStats: """Track statistics for Alien Invasion.""" - def __init__(self, ai_game): + def __init__(self, ai_game: 'AlienInvasion') -> None: """Initialize statistics.""" self.settings = ai_game.settings self.reset_stats() self.high_score = 0 - def reset_stats(self): + def reset_stats(self) -> None: """Initialize statistics that can change during the game.""" self.ships_left = self.settings.ship_limit self.score = 0 diff --git a/score_board.py b/score_board.py index 0acb1e8..ea8e97f 100644 --- a/score_board.py +++ b/score_board.py @@ -1,11 +1,16 @@ +from __future__ import annotations import pygame.font from pygame.sprite import Group from ship import Ship +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from alien_invasion import AlienInvasion class ScoreBoard: """Class to report scoring information.""" - def __init__(self, ai_game): + def __init__(self, ai_game: 'AlienInvasion') -> None: """Initialize scorekeeping attributes.""" self.ai_game = ai_game self.screen = ai_game.screen @@ -23,7 +28,7 @@ class ScoreBoard: self.prep_level() self.prep_ships() - def prep_score(self): + def prep_score(self) -> None: """Turn the score into a rendered image.""" rounded_score = round(self.stats.score, -1) score_str = f"{rounded_score:,}" @@ -34,7 +39,7 @@ class ScoreBoard: self.score_rect.right = self.screen_rect.right - 20 self.score_rect.top = 20 - def prep_high_score(self): + def prep_high_score(self) -> None: """Turn the high score into a rendered image.""" high_score = round(self.stats.high_score, -1) high_score_str = f"{high_score:,}" @@ -46,7 +51,7 @@ class ScoreBoard: self.high_score_rect.centerx = self.screen_rect.centerx self.high_score_rect.top = self.score_rect.top - def prep_level(self): + def prep_level(self) -> None: """Turn the level into a rendered image.""" level_str = str(self.stats.level) self.level_image = self.font.render(level_str, True, self.text_color, @@ -57,7 +62,7 @@ class ScoreBoard: self.level_rect.right = self.score_rect.right self.level_rect.top = self.score_rect.bottom + 10 - def prep_ships(self): + def prep_ships(self) -> None: """Show how many ships are left.""" self.ships = Group() for ship_number in range(self.stats.ships_left): @@ -66,14 +71,14 @@ class ScoreBoard: ship.rect.y = 10 self.ships.add(ship) - def show_score(self): + def show_score(self) -> None: """Draw the score and high score to the screen.""" self.screen.blit(self.score_image, self.score_rect) self.screen.blit(self.high_score_image, self.high_score_rect) self.screen.blit(self.level_image, self.level_rect) self.ships.draw(self.screen) - def check_high_score(self): + def check_high_score(self) -> None: """Check to see if there's a new high score.""" if self.stats.score > self.stats.high_score: self.stats.high_score = self.stats.score diff --git a/settings.py b/settings.py index 3318751..45387ec 100644 --- a/settings.py +++ b/settings.py @@ -1,7 +1,7 @@ class Settings: """A class to store all settings for Alien Invasion.""" - def __init__(self): + def __init__(self) -> None: """Initialize the game's static settings.""" self.screen_width = 1200 self.screen_height = 800 @@ -27,7 +27,7 @@ class Settings: self.initialize_dynamic_settings() - def initialize_dynamic_settings(self): + def initialize_dynamic_settings(self) -> None: """Initialize settings that change during the game.""" self.ship_speed = 1.5 self.bullet_speed = 3.0 @@ -37,7 +37,7 @@ class Settings: # Scoring self.alien_points = 50 - def increase_speed(self): + def increase_speed(self) -> None: """Increase speed settings and alien point values.""" self.ship_speed *= self.speedup_scale self.bullet_speed *= self.speedup_scale diff --git a/ship.py b/ship.py index bb00655..d7b9df0 100644 --- a/ship.py +++ b/ship.py @@ -1,10 +1,15 @@ +from __future__ import annotations import pygame from pygame.sprite import Sprite +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from alien_invasion import AlienInvasion class Ship(Sprite): """A class to manage the ship.""" - def __init__(self, ai_game): + def __init__(self, ai_game: 'AlienInvasion') -> None: """Initialize the ship and set its starting position.""" super().__init__() self.screen = ai_game.screen @@ -16,20 +21,20 @@ class Ship(Sprite): self.moving_right = False self.moving_left = False - def update(self): + def update(self) -> None: """Update the ship's position.""" # Update the ship's x value, not the rect. if self.moving_right and self.rect.right < self.screen.get_rect().right: self.x += self.settings.ship_speed if self.moving_left and self.rect.left > 0: self.x -= self.settings.ship_speed - self.rect.x = self.x + self.rect.x = int(self.x) - def blitme(self): + def blitme(self) -> None: """Draw the ship at its current location.""" self.screen.blit(self.image, self.rect) - def center_ship(self): + def center_ship(self) -> None: """Center the ship on the screen.""" self.rect.midbottom = self.screen.get_rect().midbottom self.x = float(self.rect.x)