Created
April 20, 2025 14:05
-
-
Save shricodev/f63271e95d0039eee5639d7208b37e0a to your computer and use it in GitHub Desktop.
This file contains hidden or 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
| import random | |
| import sys | |
| import pygame | |
| # --- Constants --- | |
| SCREEN_WIDTH = 800 | |
| SCREEN_HEIGHT = 600 | |
| BLACK = (0, 0, 0) | |
| WHITE = (255, 255, 255) | |
| RED = (255, 0, 0) | |
| GREEN = (0, 255, 0) | |
| BLUE = (0, 0, 255) | |
| YELLOW = (255, 255, 0) # For explosions | |
| DARK_GREY = (50, 50, 50) | |
| LIGHT_GREY = (150, 150, 150) | |
| PLAYER_WIDTH = 50 | |
| PLAYER_HEIGHT = 40 | |
| PLAYER_SPEED = 7 | |
| PLAYER_INITIAL_LIVES = 3 | |
| ENEMY_WIDTH = 40 | |
| ENEMY_HEIGHT = 30 | |
| ENEMY_SPEED = 2 | |
| ENEMY_SPAWN_RATE = 60 # Lower number means more frequent spawns | |
| ENEMY_SHOOT_PROBABILITY = 0.003 # Chance per frame per enemy to shoot | |
| BULLET_WIDTH = 5 | |
| BULLET_HEIGHT = 15 | |
| BULLET_SPEED = 10 | |
| ENEMY_BULLET_WIDTH = 6 | |
| ENEMY_BULLET_HEIGHT = 12 | |
| ENEMY_BULLET_SPEED = 6 | |
| ENEMY_BULLET_COLOR = (255, 100, 100) # Light red/pink | |
| EXPLOSION_DURATION = 15 # Frames the explosion lasts | |
| EXPLOSION_SIZE = 30 | |
| FPS = 60 | |
| # --- Starfield Constants --- | |
| NUM_STARS_NEAR = 50 | |
| NUM_STARS_MID = 100 | |
| NUM_STARS_FAR = 150 | |
| STAR_SPEED_NEAR = 2 | |
| STAR_SPEED_MID = 1 | |
| STAR_SPEED_FAR = 0.5 | |
| STAR_COLOR_NEAR = WHITE | |
| STAR_COLOR_MID = LIGHT_GREY | |
| STAR_COLOR_FAR = DARK_GREY | |
| STAR_RADIUS_NEAR = 2 | |
| STAR_RADIUS_MID = 1 | |
| STAR_RADIUS_FAR = 1 | |
| # --- Player Class --- | |
| class Player(pygame.sprite.Sprite): | |
| def __init__(self): | |
| super().__init__() | |
| self.image = pygame.Surface([PLAYER_WIDTH, PLAYER_HEIGHT]) | |
| self.image.fill(GREEN) | |
| self.rect = self.image.get_rect() | |
| self.rect.centerx = SCREEN_WIDTH // 2 | |
| self.rect.bottom = SCREEN_HEIGHT - 10 | |
| self.speed_x = 0 | |
| self.lives = PLAYER_INITIAL_LIVES | |
| def update(self): | |
| self.speed_x = 0 | |
| keystate = pygame.key.get_pressed() | |
| if keystate[pygame.K_LEFT]: | |
| self.speed_x = -PLAYER_SPEED | |
| if keystate[pygame.K_RIGHT]: | |
| self.speed_x = PLAYER_SPEED | |
| self.rect.x += self.speed_x | |
| if self.rect.right > SCREEN_WIDTH: | |
| self.rect.right = SCREEN_WIDTH | |
| if self.rect.left < 0: | |
| self.rect.left = 0 | |
| def shoot(self, all_sprites, bullets): | |
| bullet = Bullet(self.rect.centerx, self.rect.top) | |
| all_sprites.add(bullet) | |
| bullets.add(bullet) | |
| def get_hit(self): | |
| self.lives -= 1 | |
| # Optional: Add brief invulnerability or flashing effect here later | |
| if self.lives <= 0: | |
| return True # Return True if player should die | |
| return False | |
| # --- Enemy Class --- | |
| class Enemy(pygame.sprite.Sprite): | |
| def __init__(self, all_sprites, enemy_bullets): | |
| super().__init__() | |
| self.all_sprites = all_sprites # Need access to add bullets | |
| self.enemy_bullets = enemy_bullets | |
| self.image = pygame.Surface([ENEMY_WIDTH, ENEMY_HEIGHT]) | |
| self.image.fill(RED) | |
| self.rect = self.image.get_rect() | |
| self.rect.x = random.randrange(SCREEN_WIDTH - self.rect.width) | |
| self.rect.y = random.randrange(-100, -40) | |
| self.speed_y = ENEMY_SPEED | |
| def update(self): | |
| self.rect.y += self.speed_y | |
| if self.rect.top > SCREEN_HEIGHT + 10: | |
| self.kill() | |
| # Randomly decide to shoot | |
| if random.random() < ENEMY_SHOOT_PROBABILITY: | |
| self.shoot() | |
| def shoot(self): | |
| # Only shoot if on screen | |
| if self.rect.bottom > 0 and self.rect.top < SCREEN_HEIGHT: | |
| bullet = EnemyBullet(self.rect.centerx, self.rect.bottom) | |
| self.all_sprites.add(bullet) | |
| self.enemy_bullets.add(bullet) | |
| # --- Bullet Class (Player's) --- | |
| class Bullet(pygame.sprite.Sprite): | |
| def __init__(self, x, y): | |
| super().__init__() | |
| self.image = pygame.Surface([BULLET_WIDTH, BULLET_HEIGHT]) | |
| self.image.fill(WHITE) | |
| self.rect = self.image.get_rect() | |
| self.rect.centerx = x | |
| self.rect.bottom = y | |
| self.speed_y = -BULLET_SPEED | |
| def update(self): | |
| self.rect.y += self.speed_y | |
| if self.rect.bottom < 0: | |
| self.kill() | |
| # --- Enemy Bullet Class --- | |
| class EnemyBullet(pygame.sprite.Sprite): | |
| def __init__(self, x, y): | |
| super().__init__() | |
| self.image = pygame.Surface([ENEMY_BULLET_WIDTH, ENEMY_BULLET_HEIGHT]) | |
| self.image.fill(ENEMY_BULLET_COLOR) | |
| self.rect = self.image.get_rect() | |
| self.rect.centerx = x | |
| self.rect.top = y | |
| self.speed_y = ENEMY_BULLET_SPEED | |
| def update(self): | |
| self.rect.y += self.speed_y | |
| if self.rect.top > SCREEN_HEIGHT: | |
| self.kill() | |
| # --- Explosion Class --- | |
| class Explosion(pygame.sprite.Sprite): | |
| def __init__(self, center): | |
| super().__init__() | |
| self.image = pygame.Surface([EXPLOSION_SIZE, EXPLOSION_SIZE]) | |
| self.image.fill(YELLOW) | |
| pygame.draw.circle( | |
| self.image, | |
| RED, | |
| (EXPLOSION_SIZE // 2, EXPLOSION_SIZE // 2), | |
| EXPLOSION_SIZE // 2, | |
| ) # Simple circle effect | |
| self.image.set_colorkey(BLACK) # Make background transparent if needed | |
| self.rect = self.image.get_rect() | |
| self.rect.center = center | |
| self.lifetime = EXPLOSION_DURATION # How long the explosion lasts | |
| self.spawn_time = pygame.time.get_ticks() # Store creation time | |
| def update(self): | |
| # Decrement lifetime based on frames passed | |
| self.lifetime -= 1 | |
| if self.lifetime <= 0: | |
| self.kill() | |
| # # Alternative: time based removal | |
| # now = pygame.time.get_ticks() | |
| # if now - self.spawn_time > self.lifetime * (1000 / FPS): # Convert frames to ms approx | |
| # self.kill() | |
| # --- Star Function --- | |
| def create_stars(number, speed, color, radius): | |
| stars = [] | |
| for _ in range(number): | |
| x = random.randrange(0, SCREEN_WIDTH) | |
| y = random.randrange(0, SCREEN_HEIGHT) | |
| stars.append([x, y, speed, color, radius]) | |
| return stars | |
| def update_and_draw_stars(screen, stars): | |
| for star in stars: | |
| star[1] += star[2] | |
| if star[1] > SCREEN_HEIGHT: | |
| star[1] = random.randrange(-20, 0) | |
| star[0] = random.randrange(0, SCREEN_WIDTH) | |
| pygame.draw.circle(screen, star[3], (int(star[0]), int(star[1])), star[4]) | |
| # --- Game Initialization --- | |
| pygame.init() | |
| pygame.mixer.init() | |
| screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) | |
| pygame.display.set_caption("Galaga MVP++") | |
| clock = pygame.time.Clock() | |
| font = pygame.font.Font(None, 36) | |
| # --- Create Star Lists --- | |
| stars_far = create_stars(NUM_STARS_FAR, STAR_SPEED_FAR, STAR_COLOR_FAR, STAR_RADIUS_FAR) | |
| stars_mid = create_stars(NUM_STARS_MID, STAR_SPEED_MID, STAR_COLOR_MID, STAR_RADIUS_MID) | |
| stars_near = create_stars( | |
| NUM_STARS_NEAR, STAR_SPEED_NEAR, STAR_COLOR_NEAR, STAR_RADIUS_NEAR | |
| ) | |
| # --- Sprite Groups --- | |
| all_sprites = pygame.sprite.Group() | |
| enemies = pygame.sprite.Group() | |
| bullets = pygame.sprite.Group() # Player bullets | |
| enemy_bullets = pygame.sprite.Group() # Enemy bullets | |
| # --- Create Player --- | |
| player = Player() | |
| all_sprites.add(player) | |
| # --- Game Variables --- | |
| score = 0 | |
| enemy_spawn_timer = 0 | |
| game_over = False | |
| show_game_over_screen = False # Separate flag for displaying the screen | |
| # --- Utility Function for UI Text --- | |
| def draw_text(surf, text, size, x, y, color): | |
| font = pygame.font.Font( | |
| pygame.font.match_font("arial"), size | |
| ) # Or use your loaded font | |
| text_surface = font.render(text, True, color) | |
| text_rect = text_surface.get_rect() | |
| text_rect.topleft = (x, y) | |
| surf.blit(text_surface, text_rect) | |
| def draw_lives(surf, x, y, lives, img): | |
| for i in range(lives): | |
| img_rect = img.get_rect() | |
| # Draw small icons for lives, offset horizontally | |
| img_rect.x = x + (PLAYER_WIDTH // 2 + 5) * i | |
| img_rect.y = y | |
| surf.blit(img, img_rect) | |
| # --- Game Loop --- | |
| running = True | |
| while running: | |
| clock.tick(FPS) | |
| # --- Event Handling --- | |
| for event in pygame.event.get(): | |
| if event.type == pygame.QUIT: | |
| running = False | |
| elif event.type == pygame.KEYDOWN: | |
| if not game_over and event.key == pygame.K_SPACE: | |
| player.shoot(all_sprites, bullets) | |
| elif show_game_over_screen and event.key == pygame.K_r: # Restart game | |
| # Reset everything | |
| all_sprites.empty() | |
| enemies.empty() | |
| bullets.empty() | |
| enemy_bullets.empty() | |
| player = Player() # Creates new player with full lives | |
| all_sprites.add(player) | |
| score = 0 | |
| enemy_spawn_timer = 0 | |
| game_over = False | |
| show_game_over_screen = False | |
| # --- Update Game Logic --- | |
| if not game_over: | |
| # Enemy Spawning | |
| enemy_spawn_timer += 1 | |
| if enemy_spawn_timer >= ENEMY_SPAWN_RATE: | |
| if random.random() > 0.3: | |
| # Pass necessary groups to enemy constructor | |
| enemy = Enemy(all_sprites, enemy_bullets) | |
| all_sprites.add(enemy) | |
| enemies.add(enemy) | |
| enemy_spawn_timer = 0 | |
| # Update Sprites | |
| all_sprites.update() | |
| # --- Collision Detection --- | |
| # 1. Player Bullet vs Enemy | |
| hits = pygame.sprite.groupcollide( | |
| bullets, enemies, True, True | |
| ) # Kill bullet and enemy | |
| for bullet, enemies_hit in hits.items(): | |
| for enemy in enemies_hit: # Iterate through enemies hit by this bullet | |
| score += 10 | |
| expl = Explosion(enemy.rect.center) | |
| all_sprites.add(expl) | |
| # 2. Player vs Enemy Bullet | |
| hits = pygame.sprite.spritecollide( | |
| player, enemy_bullets, True | |
| ) # Kill the bullet | |
| for hit in hits: | |
| expl = Explosion(player.rect.center) # Explosion at player center | |
| all_sprites.add(expl) | |
| if player.get_hit(): # Returns True if lives are 0 or less | |
| game_over = True | |
| # player.kill() # Optional: remove player sprite immediately | |
| # 3. Player vs Enemy Ship | |
| hits = pygame.sprite.spritecollide(player, enemies, True) # Kill the enemy ship | |
| for hit in hits: | |
| expl = Explosion(player.rect.center) # Explosion at player center | |
| all_sprites.add(expl) | |
| # Also create explosion where enemy was, as it's destroyed | |
| expl_enemy = Explosion(hit.rect.center) | |
| all_sprites.add(expl_enemy) | |
| if player.get_hit(): # Player loses a life | |
| game_over = True | |
| # player.kill() # Optional | |
| # --- Draw / Render --- | |
| screen.fill(BLACK) | |
| # Update and Draw Stars | |
| update_and_draw_stars(screen, stars_far) | |
| update_and_draw_stars(screen, stars_mid) | |
| update_and_draw_stars(screen, stars_near) | |
| # Draw Game Sprites (Player, Enemies, Bullets, Explosions) | |
| all_sprites.draw(screen) | |
| # Draw Score & Lives | |
| draw_text(screen, f"Score: {score}", 24, 10, 10, WHITE) | |
| # Draw player lives icons (using a smaller version of the player image) | |
| player_mini_img = pygame.transform.scale( | |
| player.image, (PLAYER_WIDTH // 2, PLAYER_HEIGHT // 2) | |
| ) | |
| player_mini_img.set_colorkey( | |
| BLACK | |
| ) # Assuming player image bg is black if not transparent | |
| draw_lives(screen, SCREEN_WIDTH - 100, 10, player.lives, player_mini_img) | |
| # Draw Game Over screen ONLY if game_over is true | |
| if game_over and not show_game_over_screen: | |
| # This check ensures the game over screen logic runs only once when game_over becomes True | |
| if player.lives <= 0: # Only show if player is actually dead | |
| show_game_over_screen = True | |
| # We keep player sprite alive until restart for now unless killed above | |
| # If player was killed, we might need to handle drawing differently | |
| if show_game_over_screen: | |
| draw_text( | |
| screen, "GAME OVER", 64, SCREEN_WIDTH / 2 - 200, SCREEN_HEIGHT / 4, RED | |
| ) | |
| draw_text( | |
| screen, | |
| f"Final Score: {score}", | |
| 28, | |
| SCREEN_WIDTH / 2 - 100, | |
| SCREEN_HEIGHT / 2, | |
| WHITE, | |
| ) | |
| draw_text( | |
| screen, | |
| "Press R to Restart", | |
| 22, | |
| SCREEN_WIDTH / 2 - 100, | |
| SCREEN_HEIGHT * 3 / 4, | |
| WHITE, | |
| ) | |
| pygame.display.flip() | |
| # --- Quit Pygame --- | |
| pygame.quit() | |
| sys.exit() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment