Game Dev Lab — Day 2

Enemies & Collision

# INSTRUCTOR — Game Dev Lab Day 2: Enemies & Collision
# -------------------------------------------------------
# TEACH: Build on Day 1. Add enemies spawning at the top, collision with
# pygame.Rect, score, and lives. This is the first fully playable game.
#
# KEY CONCEPT — pygame.Rect:
#   Every object gets a Rect for position + size.
#   Rect.colliderect(other_rect) returns True if they overlap. That's collision.
#   rect = pygame.Rect(x, y, width, height)  — x,y is the TOP-LEFT corner
#
# MILESTONE (show me when done):
#   Enemies fall from the top in waves.
#   Bullets destroy enemies (both disappear) — score goes up.
#   Enemy reaches bottom → lose a life. Game over at 0 lives.
#   Score displayed on screen.
#
# EXTENSION challenges:
#   - Speed enemies up as score increases
#   - Add a power-up item that clears all enemies on screen
#   - Show a "You Win!" screen after reaching score 20

import pygame
import sys
import random

pygame.init()

WIDTH, HEIGHT = 600, 700
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Space Shooter — Day 2")
clock  = pygame.time.Clock()
font   = pygame.font.SysFont("courier", 22, bold=True)

BLACK  = (0,   0,   0)
WHITE  = (255, 255, 255)
CYAN   = (0,   220, 220)
YELLOW = (255, 255, 0)
RED    = (220, 50,  50)

# ── State ────────────────────────────────────────────────────────────────────
ship_rect  = pygame.Rect(WIDTH // 2 - 20, HEIGHT - 70, 40, 30)
SHIP_SPEED = 5

bullets  = []   # list of pygame.Rect
enemies  = []   # list of pygame.Rect
score    = 0
lives    = 3
game_over = False

BULLET_SPEED = 9
ENEMY_SPEED  = 2          # TEACH: try increasing this as score goes up

# ── Spawn helpers ─────────────────────────────────────────────────────────────
def spawn_enemy():
    x = random.randint(20, WIDTH - 60)
    enemies.append(pygame.Rect(x, -40, 40, 40))

# Start with a small wave
for _ in range(5):
    spawn_enemy()

def fire_bullet():
    cx = ship_rect.centerx
    bullets.append(pygame.Rect(cx - 4, ship_rect.top - 18, 8, 18))

# ── Drawing ──────────────────────────────────────────────────────────────────
def draw_ship():
    cx = ship_rect.centerx
    top   = (cx,                  ship_rect.top)
    left  = (ship_rect.left,      ship_rect.bottom)
    right = (ship_rect.right,     ship_rect.bottom)
    pygame.draw.polygon(screen, CYAN, [top, left, right])

def draw_hud():
    # TEACH: font.render returns a Surface; blit pastes it onto the screen
    hud = font.render(f"Score: {score}   Lives: {lives}", True, WHITE)
    screen.blit(hud, (10, 10))

def draw_game_over():
    msg  = font.render("GAME OVER — press R to restart", True, RED)
    rect = msg.get_rect(center=(WIDTH // 2, HEIGHT // 2))
    screen.blit(msg, rect)

def reset():
    global score, lives, game_over
    ship_rect.center = (WIDTH // 2, HEIGHT - 55)
    bullets.clear()
    enemies.clear()
    for _ in range(5):
        spawn_enemy()
    score    = 0
    lives    = 3
    game_over = False

# ── Game loop ─────────────────────────────────────────────────────────────────
SPAWN_EVENT = pygame.USEREVENT + 1
pygame.time.set_timer(SPAWN_EVENT, 1500)   # TEACH: custom event fires every 1.5 s

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit(); sys.exit()
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE and not game_over:
                fire_bullet()
            if event.key == pygame.K_r and game_over:
                reset()
        if event.type == SPAWN_EVENT and not game_over:
            spawn_enemy()

    if not game_over:
        # Move ship
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT]  and ship_rect.left  > 0:
            ship_rect.x -= SHIP_SPEED
        if keys[pygame.K_RIGHT] and ship_rect.right < WIDTH:
            ship_rect.x += SHIP_SPEED

        # Move bullets
        for b in bullets[:]:
            b.y -= BULLET_SPEED
            if b.bottom < 0:
                bullets.remove(b)

        # Move enemies
        for e in enemies[:]:
            e.y += ENEMY_SPEED
            if e.top > HEIGHT:
                enemies.remove(e)
                lives -= 1
                if lives <= 0:
                    game_over = True

        # Collision — bullet hits enemy
        # TEACH: iterate copies because we remove during the loop
        for b in bullets[:]:
            for e in enemies[:]:
                if b.colliderect(e):
                    bullets.remove(b)
                    enemies.remove(e)
                    score += 1
                    break   # this bullet is gone — stop checking other enemies

    # Draw
    screen.fill(BLACK)
    draw_ship()
    for b in bullets:
        pygame.draw.ellipse(screen, YELLOW, b)
    for e in enemies:
        pygame.draw.rect(screen, RED, e, border_radius=4)
    draw_hud()
    if game_over:
        draw_game_over()

    pygame.display.flip()
    clock.tick(60)