diff --git a/.coverage b/.coverage new file mode 100644 index 0000000..6088874 Binary files /dev/null and b/.coverage differ diff --git a/Capture.PNG b/Capture.PNG new file mode 100644 index 0000000..c3b621b Binary files /dev/null and b/Capture.PNG differ diff --git a/README.md b/README.md index 31515f0..c26306f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# 🚀 Galaxy Invaders +# 🚀 Galaxy Invaderss A classic arcade-style space shooter game built using Python and the Pygame library. diff --git a/requirements.txt b/requirements.txt index 183ccce..2e0d1da 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,4 @@ pygame>=2.5.0 +pytest>=7.3.1 +pytest-mock>=3.10.0 +pytest-cov>=4.0.0 diff --git a/space_adventure.py b/space_adventure.py index 7411aa0..e8e11d4 100644 --- a/space_adventure.py +++ b/space_adventure.py @@ -2,7 +2,7 @@ import random import sys import os - +#rate # Initialize pygame pygame.init() @@ -65,6 +65,7 @@ def __init__(self): self.hide_timer = 0 self.power_level = 1 self.power_timer = 0 + self.power_display_timer = 0 # New timer for power-up display def update(self): # Unhide if hidden @@ -77,31 +78,42 @@ def update(self): if self.power_level > 1 and pygame.time.get_ticks() - self.power_timer > 5000: self.power_level -= 1 self.power_timer = pygame.time.get_ticks() + self.power_display_timer = pygame.time.get_ticks() # Set display timer when power decreases # Movement self.speed_x = 0 keystate = pygame.key.get_pressed() if keystate[pygame.K_LEFT]: - self.speed_x = 8 + self.speed_x = -8 # Fixed: Changed to negative for left movement if keystate[pygame.K_RIGHT]: - self.speed_x = -8 + self.speed_x = 8 # Fixed: Changed to positive for right movement self.rect.x += self.speed_x - if self.rect.right > SCREEN_WIDTH + 20: - self.rect.right = SCREEN_WIDTH + 20 - if self.rect.left < -20: - self.rect.left = -20 + # Fix boundary checks to keep player on screen + if self.rect.right > SCREEN_WIDTH: + self.rect.right = SCREEN_WIDTH + if self.rect.left < 0: + self.rect.left = 0 - def shoot(self): + def shoot(self, all_sprites_group, bullets_group): if not self.hidden: + bullets_shot = [] if self.power_level == 1: bullet = Bullet(self.rect.centerx, self.rect.top) - all_sprites.add(bullet) - bullets.add(bullet) + all_sprites_group.add(bullet) + bullets_group.add(bullet) + bullets_shot.append(bullet) elif self.power_level >= 2: - bullet1 = Bullet(self.rect.centerx, self.rect.top) - all_sprites.add(bullet1) - bullets.add(bullet1) + # Enhanced spread pattern for powered-up shots + spread = 15 # Angle of spread between bullets + for i in range(self.power_level): + offset = (i - (self.power_level - 1) / 2) * spread + bullet = Bullet(self.rect.centerx, self.rect.top, offset) + all_sprites_group.add(bullet) + bullets_group.add(bullet) + bullets_shot.append(bullet) + return bullets_shot + return [] def hide(self): self.hidden = True @@ -111,6 +123,7 @@ def hide(self): def powerup(self): self.power_level += 1 self.power_timer = pygame.time.get_ticks() + self.power_display_timer = pygame.time.get_ticks() # Set display timer when power increases # Enemy class class Enemy(pygame.sprite.Sprite): @@ -139,17 +152,19 @@ def update(self): # Bullet class class Bullet(pygame.sprite.Sprite): - def __init__(self, x, y): + def __init__(self, x, y, offset=0): super().__init__() self.image = bullet_img self.rect = self.image.get_rect() self.rect.centerx = x self.rect.bottom = y - self.speedy = 1 + self.speedy = -10 + self.speedx = offset * 0.3 # Scale the spread effect def update(self): self.rect.y += self.speedy - if self.rect.bottom < 0: + self.rect.x += self.speedx + if self.rect.bottom < 0 or self.rect.left < 0 or self.rect.right > SCREEN_WIDTH: self.kill() # Powerup class @@ -261,28 +276,33 @@ def main_game(): sys.exit() elif event.type == pygame.KEYDOWN: if event.key == pygame.K_SPACE: - player.shoot() + player.shoot(all_sprites, bullets) # Update all_sprites.update() # Check bullet-enemy collisions - hits = pygame.sprite.groupcollide(enemies, bullets, False, True) + hits = pygame.sprite.groupcollide(enemies, bullets, True, True) for hit in hits: - score += 10 - # Create explosion + score += 100 explosion = Explosion(hit.rect.center, 30) all_sprites.add(explosion) - # Spawn new enemy new_enemy = Enemy() all_sprites.add(new_enemy) enemies.add(new_enemy) - # Random chance for power-up - if random.random() > 0.5: # 50% chance + if random.random() > 0.9: powerup = Powerup() all_sprites.add(powerup) powerups.add(powerup) + # Check player-powerup collisions + hits = pygame.sprite.spritecollide(player, powerups, True) + for hit in hits: + if hit.type == 'shield': + player.shield = min(100, player.shield + 20) + elif hit.type == 'power': + player.powerup() + # Check player-enemy collisions hits = pygame.sprite.spritecollide(player, enemies, True) for hit in hits: @@ -293,24 +313,16 @@ def main_game(): all_sprites.add(new_enemy) enemies.add(new_enemy) if player.shield <= 0: - # player.lives -= 1 + player.lives -= 1 player.shield = 100 player.hide() - if player.lives == 0: + if player.lives <= 0: game_over = True - # Check player-powerup collisions - hits = pygame.sprite.spritecollide(player, powerups, True) - for hit in hits: - if hit.type == 'shield': - player.shield += 20 - if player.shield > 100: - player.shield = 100 - if hit.type == 'power': - player.powerup() - if game_over: - pass + running = False + pygame.quit() + sys.exit() # Draw / render screen.fill(BLACK) @@ -321,6 +333,10 @@ def main_game(): draw_shield_bar(screen, 5, 5, player.shield) draw_lives(screen, SCREEN_WIDTH - 100, 5, player.lives, player_mini_img) + # Display power level when changed + if pygame.time.get_ticks() - player.power_display_timer < 2000: # Show for 2 seconds + draw_text(screen, f"POWER LEVEL {player.power_level}", 24, SCREEN_WIDTH / 2, 50) + # Flip the display pygame.display.flip() diff --git a/test_space_adventure.py b/test_space_adventure.py new file mode 100644 index 0000000..c72d21c --- /dev/null +++ b/test_space_adventure.py @@ -0,0 +1,128 @@ +import pytest +import pygame +import os +from space_adventure import ( + Player, Enemy, Bullet, Powerup, + SCREEN_WIDTH, SCREEN_HEIGHT, + load_image +) + +# Initialize pygame for testing +pygame.init() +pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) + +@pytest.fixture +def sprite_groups(): + all_sprites = pygame.sprite.Group() + bullets = pygame.sprite.Group() + enemies = pygame.sprite.Group() + powerups = pygame.sprite.Group() + return { + 'all_sprites': all_sprites, + 'bullets': bullets, + 'enemies': enemies, + 'powerups': powerups + } + +@pytest.fixture +def player(sprite_groups): + player = Player() + sprite_groups['all_sprites'].add(player) + return player + +@pytest.fixture +def enemy(sprite_groups): + enemy = Enemy() + sprite_groups['all_sprites'].add(enemy) + sprite_groups['enemies'].add(enemy) + return enemy + +@pytest.fixture +def bullet(player, sprite_groups): + bullet = Bullet(player.rect.centerx, player.rect.top) + sprite_groups['all_sprites'].add(bullet) + sprite_groups['bullets'].add(bullet) + return bullet + +@pytest.fixture +def powerup(sprite_groups): + powerup = Powerup() + sprite_groups['all_sprites'].add(powerup) + sprite_groups['powerups'].add(powerup) + return powerup + +def test_player_initialization(player): + assert player.rect.centerx == SCREEN_WIDTH // 2 + assert player.rect.bottom == SCREEN_HEIGHT - 10 + assert player.shield == 100 + assert player.lives == 3 + assert not player.hidden + assert player.power_level == 1 + +def test_player_movement(player, monkeypatch): + # Test right movement + keys = {pygame.K_RIGHT: True, pygame.K_LEFT: False} + monkeypatch.setattr(pygame.key, 'get_pressed', lambda: keys) + initial_x = player.rect.x + player.update() + assert player.rect.x > initial_x + + # Test left movement + keys = {pygame.K_RIGHT: False, pygame.K_LEFT: True} + monkeypatch.setattr(pygame.key, 'get_pressed', lambda: keys) + initial_x = player.rect.x + player.update() + assert player.rect.x < initial_x + +def test_player_screen_bounds(player): + # Test right boundary + player.rect.right = SCREEN_WIDTH + 100 + player.update() + assert player.rect.right == SCREEN_WIDTH + + # Test left boundary + player.rect.left = -100 + player.update() + assert player.rect.left == 0 + +def test_enemy_initialization(enemy): + assert 0 <= enemy.rect.x <= SCREEN_WIDTH - enemy.rect.width + assert -150 <= enemy.rect.y <= -100 + assert 1 <= enemy.speedy <= 8 + assert -3 <= enemy.speedx <= 3 + +def test_enemy_movement(enemy): + initial_pos = enemy.rect.y + enemy.update() + assert enemy.rect.y > initial_pos + +def test_bullet_movement(bullet): + initial_y = bullet.rect.y + bullet.update() + assert bullet.rect.y < initial_y + assert bullet.speedy == -10 + +def test_powerup_movement(powerup): + initial_y = powerup.rect.y + powerup.update() + assert powerup.rect.y > initial_y + assert powerup.speedy == 4 + assert powerup.type in ['shield', 'power'] + +def test_player_power_up(player): + initial_power = player.power_level + player.powerup() + assert player.power_level == initial_power + 1 + +def test_player_shooting(player, sprite_groups): + # Test normal shot + player.power_level = 1 + bullets = player.shoot(sprite_groups['all_sprites'], sprite_groups['bullets']) + assert len(bullets) == 1 + assert len(sprite_groups['bullets']) == 1 + + # Test powered up shot + player.power_level = 2 + bullets = player.shoot(sprite_groups['all_sprites'], sprite_groups['bullets']) + assert len(bullets) == 2 + assert len(sprite_groups['bullets']) == 3 # 1 from previous + 2 new ones \ No newline at end of file