Lesson 7: Add Enemies

Build on Lesson 6 (player + bullets). In this lesson you’ll:

Controls: Left/Right to move, Space to shoot.

Step 1 — Enemy formation (add near top)

# === ENEMIES ===
enemies = []
ENEMY_COLS, ENEMY_ROWS = 8, 3
ENEMY_SPACING_X, ENEMY_SPACING_Y = 70, 50
ENEMY_START_X, ENEMY_START_Y = 80, 60
ENEMY_W, ENEMY_H = 36, 20
enemy_dx = 1.2             # horizontal speed (pixels/frame)
ENEMY_DROP_Y = 18

def create_enemies():
    global enemies
    enemies = []
    for r in range(ENEMY_ROWS):
        for c in range(ENEMY_COLS):
            enemies.append({
                "x": ENEMY_START_X + c * ENEMY_SPACING_X,
                "y": ENEMY_START_Y + r * ENEMY_SPACING_Y,
                "w": ENEMY_W, "h": ENEMY_H,
                "alive": True,
                "points": 30 if r == 0 else (20 if r == 1 else 10)
            })

Step 2 — Group movement

def update_enemies(screen_w):
    global enemy_dx
    hit_left, hit_right = False, False
    for e in enemies:
        if not e["alive"]: continue
        nx = e["x"] + enemy_dx
        if nx < 10: hit_left = True
        if nx + e["w"] > screen_w - 10: hit_right = True
    if hit_left or hit_right:
        for e in enemies:
            if e["alive"]:
                e["y"] += ENEMY_DROP_Y
        enemy_dx = -enemy_dx
    for e in enemies:
        if e["alive"]:
            e["x"] += enemy_dx

Step 3 — Draw enemies

def draw_enemies(screen):
    import pygame
    ENEMY_COLOR = (123, 211, 255)
    EYE = (11, 18, 38)
    for e in enemies:
        if not e["alive"]: continue
        pygame.draw.rect(screen, ENEMY_COLOR, (e["x"], e["y"], e["w"], e["h"]))
        pygame.draw.rect(screen, EYE, (e["x"]+6, e["y"]+6, 6, 6))
        pygame.draw.rect(screen, EYE, (e["x"]+e["w"]-12, e["y"]+6, 6, 6))

Step 4 — Bullet ↔ enemy collisions + score + win

score = 0

def rects_overlap(a, b):
    return (a["x"] < b["x"] + b["w"] and a["x"] + a["w"] > b["x"] and
            a["y"] < b["y"] + b["h"] and a["y"] + a["h"] > b["y"])

def handle_bullet_enemy_collisions(BULLET_W, BULLET_H, bullets):
    global score
    for e in enemies:
        if not e["alive"]: continue
        for b in bullets:
            if not b.get("active", True): continue
            bullet_rect = {"x": b["x"], "y": b["y"], "w": BULLET_W, "h": BULLET_H}
            if rects_overlap(bullet_rect, e):
                e["alive"] = False
                b["active"] = False
                score += e.get("points", 10)
                break
    # keep only active bullets
    return [b for b in bullets if b.get("active", True)]

Win check:

def all_enemies_defeated():
    return not any(e["alive"] for e in enemies)

Full example (save as lesson7.py)

import sys, pygame
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Space Invader — Lesson 7")
clock = pygame.time.Clock()

SPACE = (10, 15, 30)
PLAYER_COLOR = (154, 180, 255)
BULLET_COLOR = (255, 211, 105)
ENEMY_COLOR = (123, 211, 255)
EYE = (11, 18, 38)

# Player
player_w, player_h = 40, 20
player_x = 800//2 - player_w//2
player_y = 600 - 60
player_speed = 0

# Bullets
bullets = []
BULLET_W, BULLET_H = 4, 10
BULLET_SPEED = -6
FIRE_COOLDOWN_MS = 250
last_shot_at = 0

def fire_bullet(now_ms):
    global last_shot_at, bullets
    if now_ms - last_shot_at < FIRE_COOLDOWN_MS: return
    bx = player_x + player_w//2 - BULLET_W//2
    by = player_y - BULLET_H
    bullets.append({"x": bx, "y": by, "vy": BULLET_SPEED, "active": True})
    last_shot_at = now_ms

def update_bullets():
    global bullets
    for b in bullets:
        if not b["active"]: continue
        b["y"] += b["vy"]
        if b["y"] + BULLET_H < 0: b["active"] = False
    bullets = [b for b in bullets if b["active"]]

def draw_bullets():
    for b in bullets:
        pygame.draw.rect(screen, BULLET_COLOR, (b["x"], b["y"], BULLET_W, BULLET_H))

# Enemies
enemies = []
ENEMY_COLS, ENEMY_ROWS = 8, 3
ENEMY_SPACING_X, ENEMY_SPACING_Y = 70, 50
ENEMY_START_X, ENEMY_START_Y = 80, 60
ENEMY_W, ENEMY_H = 36, 20
enemy_dx = 1.2
ENEMY_DROP_Y = 18

def create_enemies():
    global enemies
    enemies = []
    for r in range(ENEMY_ROWS):
        for c in range(ENEMY_COLS):
            enemies.append({
                "x": ENEMY_START_X + c*ENEMY_SPACING_X,
                "y": ENEMY_START_Y + r*ENEMY_SPACING_Y,
                "w": ENEMY_W, "h": ENEMY_H,
                "alive": True,
                "points": 30 if r==0 else (20 if r==1 else 10)
            })

def update_enemies():
    global enemy_dx
    hit_left, hit_right = False, False
    for e in enemies:
        if not e["alive"]: continue
        nx = e["x"] + enemy_dx
        if nx < 10: hit_left = True
        if nx + e["w"] > 800 - 10: hit_right = True
    if hit_left or hit_right:
        for e in enemies:
            if e["alive"]: e["y"] += ENEMY_DROP_Y
        enemy_dx = -enemy_dx
    for e in enemies:
        if e["alive"]: e["x"] += enemy_dx

def draw_enemies():
    for e in enemies:
        if not e["alive"]: continue
        pygame.draw.rect(screen, ENEMY_COLOR, (e["x"], e["y"], e["w"], e["h"]))
        pygame.draw.rect(screen, EYE, (e["x"]+6, e["y"]+6, 6, 6))
        pygame.draw.rect(screen, EYE, (e["x"]+e["w"]-12, e["y"]+6, 6, 6))

score = 0
def rects_overlap(a, b):
    return (a["x"] < b["x"] + b["w"] and a["x"] + a["w"] > b["x"] and
            a["y"] < b["y"] + b["h"] and a["y"] + a["h"] > b["y"])

def handle_bullet_enemy_collisions():
    global bullets, score
    for e in enemies:
        if not e["alive"]: continue
        for b in bullets:
            if not b["active"]: continue
            bullet_rect = {"x": b["x"], "y": b["y"], "w": BULLET_W, "h": BULLET_H}
            if rects_overlap(bullet_rect, e):
                e["alive"] = False
                b["active"] = False
                score += e.get("points", 10)
                break
    bullets = [b for b in bullets if b["active"]]

def draw_hud(surface):
    font = pygame.font.SysFont(None, 28)
    surface.blit(font.render(f"Score: {score}", True, (209,231,255)), (10,10))

running = True
create_enemies()

while running:
    now = pygame.time.get_ticks()
    for event in pygame.event.get():
        if event.type == pygame.QUIT: running = False
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT:  player_speed = -4
            if event.key == pygame.K_RIGHT: player_speed = 4
            if event.key == pygame.K_SPACE: fire_bullet(now)
        if event.type == pygame.KEYUP:
            if event.key in (pygame.K_LEFT, pygame.K_RIGHT): player_speed = 0

    # move player + clamp
    pad = 10
    player_x = max(pad, min(player_x + player_speed, 800 - pad - player_w))

    update_bullets()
    update_enemies()
    handle_bullet_enemy_collisions()

    screen.fill(SPACE)
    pygame.draw.rect(screen, PLAYER_COLOR, (player_x, player_y, player_w, player_h))
    pygame.draw.polygon(screen, (207,225,255), [
        (player_x + player_w//2, player_y - 6),
        (player_x + player_w//2 - 6, player_y),
        (player_x + player_w//2 + 6, player_y)
    ])
    draw_bullets()
    draw_enemies()
    draw_hud(screen)

    if not any(e["alive"] for e in enemies):
        font = pygame.font.SysFont(None, 40)
        screen.blit(font.render("You win!", True, (134,239,172)), (340, 280))

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

pygame.quit()
sys.exit()