Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save shricodev/f63271e95d0039eee5639d7208b37e0a to your computer and use it in GitHub Desktop.

Select an option

Save shricodev/f63271e95d0039eee5639d7208b37e0a to your computer and use it in GitHub Desktop.
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