Created
April 20, 2025 14:00
-
-
Save shricodev/fe396931474cae6df1af36f6f752b0b3 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 | |
# -- Configuration --------------------------------------------------------- | |
WIDTH, HEIGHT = 480, 640 | |
FPS = 60 | |
PLAYER_SPEED = 5 | |
PLAYER_SIZE = 40 | |
BULLET_SPEED = 7 | |
BULLET_W, BULLET_H = 4, 10 | |
SHOOT_COOLDOWN = 300 # ms between player shots | |
ENEMY_SPEED = 2 | |
ENEMY_SIZE = 40 | |
MAX_ENEMIES = 6 | |
SPAWN_DELAY = 1000 # ms between enemy spawns | |
ENEMY_SHOOT_DELAY = 1200 # ms between enemy shots | |
ENEMY_BULLET_SPEED = 4 | |
ENEMY_BULLET_W, ENEMY_BULLET_H = 4, 10 | |
STARTING_LIVES = 3 | |
STAR_COUNT = 80 | |
# -- Pygame Setup --------------------------------------------------------- | |
pygame.init() | |
screen = pygame.display.set_mode((WIDTH, HEIGHT)) | |
pygame.display.set_caption("Galaga‐MVP w/ Explosions") | |
clock = pygame.time.Clock() | |
font = pygame.font.SysFont(None, 32) | |
# -- Classes -------------------------------------------------------------- | |
class Star: | |
def __init__(self): | |
self.x = random.randrange(0, WIDTH) | |
self.y = random.randrange(0, HEIGHT) | |
self.speed = random.uniform(1, 3) | |
self.size = random.randint(1, 3) | |
def update(self): | |
self.y += self.speed | |
if self.y > HEIGHT: | |
self.y = 0 | |
self.x = random.randrange(0, WIDTH) | |
def draw(self, surf): | |
pygame.draw.circle(surf, (200, 200, 200), (int(self.x), int(self.y)), self.size) | |
class Player: | |
def __init__(self): | |
self.rect = pygame.Rect( | |
(WIDTH - PLAYER_SIZE) // 2, | |
HEIGHT - PLAYER_SIZE - 10, | |
PLAYER_SIZE, | |
PLAYER_SIZE, | |
) | |
self.last_shot = 0 | |
self.lives = STARTING_LIVES | |
def move(self, dx): | |
self.rect.x = max(0, min(WIDTH - PLAYER_SIZE, self.rect.x + dx)) | |
def can_shoot(self): | |
return pygame.time.get_ticks() - self.last_shot >= SHOOT_COOLDOWN | |
def shoot(self): | |
self.last_shot = pygame.time.get_ticks() | |
bx = self.rect.centerx - BULLET_W // 2 | |
by = self.rect.top - BULLET_H | |
return Bullet(bx, by, -BULLET_SPEED, (255, 255, 0), BULLET_W, BULLET_H) | |
def draw(self, surf): | |
x, y, s = self.rect.x, self.rect.y, self.rect.width | |
pts = [(x + s // 2, y), (x, y + s), (x + s, y + s)] | |
pygame.draw.polygon(surf, (0, 255, 0), pts) | |
class Bullet: | |
def __init__(self, x, y, dy, color, w, h): | |
self.rect = pygame.Rect(x, y, w, h) | |
self.dy = dy | |
self.color = color | |
def update(self): | |
self.rect.y += self.dy | |
def off_screen(self): | |
return self.rect.bottom < 0 or self.rect.top > HEIGHT | |
def draw(self, surf): | |
pygame.draw.rect(surf, self.color, self.rect) | |
class Enemy: | |
def __init__(self): | |
x = random.randrange(0, WIDTH - ENEMY_SIZE) | |
self.rect = pygame.Rect(x, -ENEMY_SIZE, ENEMY_SIZE, ENEMY_SIZE) | |
def update(self): | |
self.rect.y += ENEMY_SPEED | |
def off_screen(self): | |
return self.rect.top > HEIGHT | |
def draw(self, surf): | |
x, y, s = self.rect.x, self.rect.y, self.rect.width | |
pts = [(x, y), (x + s, y), (x + s // 2, y + s)] | |
pygame.draw.polygon(surf, (255, 0, 0), pts) | |
class Explosion: | |
def __init__(self, x, y): | |
self.x = x | |
self.y = y | |
self.start = pygame.time.get_ticks() | |
self.duration = 500 # ms | |
self.max_radius = 30 | |
def draw(self, surf, now): | |
elapsed = now - self.start | |
if elapsed > self.duration: | |
return False | |
ratio = elapsed / self.duration | |
r = int(ratio * self.max_radius) | |
# outer glow | |
pygame.draw.circle(surf, (255, 200, 0), (self.x, self.y), r) | |
# inner core | |
pygame.draw.circle(surf, (255, 80, 0), (self.x, self.y), int(r * 0.6)) | |
return True | |
# -- Helpers ------------------------------------------------------------- | |
def draw_text(surf, text, x, y, color=(255, 255, 255)): | |
img = font.render(text, True, color) | |
surf.blit(img, (x, y)) | |
def reset_game(): | |
player = Player() | |
bullets = [] | |
enemies = [] | |
e_bullets = [] | |
explosions = [] | |
stars = [Star() for _ in range(STAR_COUNT)] | |
score = 0 | |
last_spawn = pygame.time.get_ticks() | |
last_enemy_shot = pygame.time.get_ticks() | |
return ( | |
player, | |
bullets, | |
enemies, | |
e_bullets, | |
explosions, | |
stars, | |
score, | |
last_spawn, | |
last_enemy_shot, | |
) | |
# -- Main Loop ----------------------------------------------------------- | |
def main(): | |
( | |
player, | |
bullets, | |
enemies, | |
e_bullets, | |
explosions, | |
stars, | |
score, | |
last_spawn, | |
last_enemy_shot, | |
) = reset_game() | |
game_over = False | |
while True: | |
dt = clock.tick(FPS) | |
now = pygame.time.get_ticks() | |
# --- Events --- | |
for ev in pygame.event.get(): | |
if ev.type == pygame.QUIT: | |
pygame.quit() | |
sys.exit() | |
keys = pygame.key.get_pressed() | |
# --- Game Over Screen --- | |
if game_over: | |
screen.fill((0, 0, 0)) | |
draw_text(screen, "GAME OVER", WIDTH // 2 - 90, HEIGHT // 2 - 50) | |
draw_text(screen, f"Score: {score}", WIDTH // 2 - 60, HEIGHT // 2) | |
draw_text( | |
screen, "Press Enter to Restart", WIDTH // 2 - 140, HEIGHT // 2 + 50 | |
) | |
pygame.display.flip() | |
if keys[pygame.K_RETURN]: | |
( | |
player, | |
bullets, | |
enemies, | |
e_bullets, | |
explosions, | |
stars, | |
score, | |
last_spawn, | |
last_enemy_shot, | |
) = reset_game() | |
game_over = False | |
continue | |
# --- Player Input --- | |
dx = 0 | |
if keys[pygame.K_LEFT]: | |
dx -= PLAYER_SPEED | |
if keys[pygame.K_RIGHT]: | |
dx += PLAYER_SPEED | |
player.move(dx) | |
if keys[pygame.K_SPACE] and player.can_shoot(): | |
bullets.append(player.shoot()) | |
# --- Spawn Enemies --- | |
if now - last_spawn >= SPAWN_DELAY and len(enemies) < MAX_ENEMIES: | |
enemies.append(Enemy()) | |
last_spawn = now | |
# --- Enemy Shooting --- | |
if now - last_enemy_shot >= ENEMY_SHOOT_DELAY and enemies: | |
shooter = random.choice(enemies) | |
bx = shooter.rect.centerx - ENEMY_BULLET_W // 2 | |
by = shooter.rect.bottom | |
e_bullets.append( | |
Bullet( | |
bx, | |
by, | |
ENEMY_BULLET_SPEED, | |
(255, 100, 100), | |
ENEMY_BULLET_W, | |
ENEMY_BULLET_H, | |
) | |
) | |
last_enemy_shot = now | |
# --- Update Entities --- | |
for s in stars: | |
s.update() | |
for b in bullets: | |
b.update() | |
for e in enemies: | |
e.update() | |
for eb in e_bullets: | |
eb.update() | |
# --- Off‑screen cleanup --- | |
bullets = [b for b in bullets if not b.off_screen()] | |
enemies = [e for e in enemies if not e.off_screen()] | |
e_bullets = [eb for eb in e_bullets if not eb.off_screen()] | |
# --- Collisions: Player bullets → Enemies --- | |
new_b = [] | |
for b in bullets: | |
hit = False | |
for e in enemies: | |
if b.rect.colliderect(e.rect): | |
explosions.append(Explosion(e.rect.centerx, e.rect.centery)) | |
enemies.remove(e) | |
score += 100 | |
hit = True | |
break | |
if not hit: | |
new_b.append(b) | |
bullets = new_b | |
# --- Collisions: Enemy bullets → Player --- | |
for eb in e_bullets[:]: | |
if eb.rect.colliderect(player.rect): | |
e_bullets.remove(eb) | |
explosions.append(Explosion(player.rect.centerx, player.rect.centery)) | |
player.lives -= 1 | |
if player.lives <= 0: | |
game_over = True | |
break | |
# --- Collisions: Enemy → Player --- | |
for e in enemies[:]: | |
if e.rect.colliderect(player.rect): | |
enemies.remove(e) | |
explosions.append(Explosion(player.rect.centerx, player.rect.centery)) | |
player.lives -= 1 | |
if player.lives <= 0: | |
game_over = True | |
break | |
# --- Draw --- | |
screen.fill((0, 0, 0)) | |
for s in stars: | |
s.draw(screen) | |
player.draw(screen) | |
for b in bullets: | |
b.draw(screen) | |
for eb in e_bullets: | |
eb.draw(screen) | |
for e in enemies: | |
e.draw(screen) | |
# draw & clean explosions | |
explosions = [ex for ex in explosions if ex.draw(screen, now)] | |
# UI | |
draw_text(screen, f"Score: {score}", 10, 10) | |
draw_text(screen, f"Lives: {player.lives}", WIDTH - 130, 10) | |
pygame.display.flip() | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment