Lesson 8: Enemy Bullets, Lives, and Game Over
This final lesson makes the game complete. You’ll add:
- Enemy bullets (random enemies fire downward)
- Player lives (start at 3)
- Lose conditions: lives reach 0, or enemies reach the player line
- “Game Over” and “You win!” messages
Start from your Lesson 7 code.
Step 1 — Enemy bullets
# === ENEMY BULLETS ===
enemy_bullets = []
E_BULLET_SPEED = 3
def enemies_fire():
import random
shooters = [e for e in enemies if e["alive"]]
if shooters and random.random() < 0.02: # ~2% chance per frame
e = random.choice(shooters)
enemy_bullets.append({
"x": e["x"] + e["w"]//2 - 3,
"y": e["y"] + e["h"],
"w": 6, "h": 10,
"vy": E_BULLET_SPEED,
"active": True
})
def update_enemy_bullets(screen_h):
global enemy_bullets
for b in enemy_bullets:
if not b["active"]: continue
b["y"] += b["vy"]
if b["y"] > screen_h: b["active"] = False
enemy_bullets = [b for b in enemy_bullets if b["active"]]
def draw_enemy_bullets(screen):
import pygame
for b in enemy_bullets:
pygame.draw.rect(screen, (255,107,107), (b["x"], b["y"], b["w"], b["h"]))
Step 2 — Lives, player hits, lose/win checks
lives = 3
def handle_player_hits():
global lives, enemy_bullets
player_rect = {"x": player_x, "y": player_y, "w": player_w, "h": player_h}
for b in enemy_bullets:
if not b["active"]: continue
if (b["x"] < player_rect["x"] + player_rect["w"] and
b["x"] + b["w"] > player_rect["x"] and
b["y"] < player_rect["y"] + player_rect["h"] and
b["y"] + b["h"] > player_rect["y"]):
b["active"] = False
lives -= 1
if lives <= 0:
return "out_of_lives"
enemy_bullets = [b for b in enemy_bullets if b["active"]]
return None
def enemies_reached_player():
return any(e["alive"] and e["y"] + e["h"] >= player_y for e in enemies)
Full example (save as lesson8.py)
import sys, random, pygame
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Space Invader — Lesson 8 (Final)")
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)
E_BULLET_COLOR = (255,107,107)
INK = (209,231,255)
GOOD = (134,239,172)
BAD = (255,107,107)
# Player
player_w, player_h = 40, 20
player_x = 800//2 - player_w//2
player_y = 600 - 60
player_speed = 0
# Player bullets
bullets = []
BULLET_W, BULLET_H = 4, 10
BULLET_SPEED = -6
FIRE_COOLDOWN_MS = 250
last_shot_at = 0
# 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
# Enemy bullets
enemy_bullets = []
E_BULLET_SPEED = 3
score = 0
lives = 3
# -------- helpers --------
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))
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))
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 enemies_fire():
shooters = [e for e in enemies if e["alive"]]
if shooters and random.random() < 0.02:
e = random.choice(shooters)
enemy_bullets.append({
"x": e["x"] + e["w"]//2 - 3,
"y": e["y"] + e["h"],
"w": 6, "h": 10,
"vy": E_BULLET_SPEED,
"active": True
})
def update_enemy_bullets():
global enemy_bullets
for b in enemy_bullets:
if not b["active"]: continue
b["y"] += b["vy"]
if b["y"] > 600: b["active"] = False
enemy_bullets = [b for b in enemy_bullets if b["active"]]
def draw_enemy_bullets():
for b in enemy_bullets:
pygame.draw.rect(screen, E_BULLET_COLOR, (b["x"], b["y"], b["w"], b["h"]))
def handle_player_hits():
global lives, enemy_bullets
player_rect = {"x": player_x, "y": player_y, "w": player_w, "h": player_h}
for b in enemy_bullets:
if not b["active"]: continue
if rects_overlap(b, player_rect):
b["active"] = False
lives -= 1
if lives <= 0: return "out_of_lives"
enemy_bullets = [b for b in enemy_bullets if b["active"]]
return None
def enemies_reached_player():
return any(e["alive"] and e["y"] + e["h"] >= player_y for e in enemies)
def draw_hud(surface):
font = pygame.font.SysFont(None, 28)
surface.blit(font.render(f"Score: {score}", True, INK), (10,10))
surface.blit(font.render(f"Lives: {lives}", True, INK), (680,10))
# -------- main --------
create_enemies()
running = True
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
player_x = max(10, min(player_x + player_speed, 800 - 10 - player_w))
# update world
update_bullets()
update_enemies()
enemies_fire()
update_enemy_bullets()
handle_bullet_enemy_collisions()
# lose checks
lose_reason = handle_player_hits()
if lose_reason == "out_of_lives" or enemies_reached_player():
screen.fill(SPACE)
draw_enemies(); draw_bullets(); draw_enemy_bullets(); draw_hud(screen)
font = pygame.font.SysFont(None, 40)
screen.blit(font.render("Game Over", True, BAD), (330, 280))
pygame.display.flip()
pygame.time.wait(1500)
break
# win check
if not any(e["alive"] for e in enemies):
screen.fill(SPACE)
draw_enemies(); draw_bullets(); draw_enemy_bullets(); draw_hud(screen)
font = pygame.font.SysFont(None, 40)
screen.blit(font.render("You win!", True, GOOD), (350, 280))
pygame.display.flip()
pygame.time.wait(1500)
break
# draw
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_enemy_bullets(); draw_hud(screen)
pygame.display.flip()
clock.tick(60)
pygame.quit()
sys.exit()