Edison's Project Guide
Project: Battle Dragons Category: Game (Pygame) Last updated: April 18
Note: This guide reflects the latest state of your project repo. It may not match the most up-to-date version if you've worked since.
Where You Are
Real Pygame scaffolding is in place — StartScene, NameDragonScene, a dragons.py module, even a tests/ folder. The scene architecture (Scene base class + scene transitions) is clean and on the right track.
Two gaps:
- Your MVP from the spec (hub + 2 minigames + 1 arena) — the hub and 2 minigames are missing or half-done, and the arena isn't there yet.
- Your journal is empty.
This week you'll fill in those three MVP pieces, catch up the journal, and introduce a combat.py file so your fight math lives outside the view code.
Project Structure
Pygame makes this a little different from a Flask app. The split is inside your Scene classes and across a couple of new modules:
- Business logic — you handwrite this. Combat math (damage, HP, turn resolution) in
combat.py. Minigame scoring rules inminigames.py. Theupdate()andhandle_event()methods of every Scene (what changes when input happens). This is "how your game works." - Library / view code — agent-assisted is fine. The
.draw()method of every Scene (fonts, rectangles, colors, positioning). Any sprite/asset loading. Font sizing and screen layout.
A simple rule: inside every Scene class, update() / handle_event() is business logic. draw() is view code. The agent can polish how things look without touching how they behave.
Target layout by Thursday:
final-project-EdisonWright4/
├── main.py ← scene dispatcher — mostly agent-assisted
├── dragons.py ← dragon names + stats — existing (yours)
├── combat.py ← fight math — handwrite (NEW, yours to own)
├── minigames.py ← minigame scoring rules — handwrite (NEW, yours to own)
├── scenes/ ← one file per scene is fine, or keep in main.py
│ ├── start.py
│ ├── hub.py
│ ├── flight.py
│ ├── defense.py
│ └── arena.py
├── pyproject.toml
└── tests/
(If you prefer keeping all scenes in one file, that's fine — just know which methods are business vs view inside each.)
Why the split? From Lecture 1: The MVP — on demo day the interesting question is "how does combat work in your game?" The answer lives in combat.py (pure math, no Pygame imports) — that's testable and explainable. The pretty animations are not the thing.
combat.py and minigames.py should not import pygame. They take inputs (current HP, player action), return outputs (new HP, damage dealt). Pure logic.
Phase 1: Audit What Runs Today
No code — research phase. You need to know your starting point before building onto it.
Objective
Understand what's already playable. Write it down.
Instructions
Sample Notes
StartScene → visible: "Battle Dragons" title + prompt.
Interactive: ENTER → NameDragonScene, ESC → quit.
NameDragonScene → visible: name input. Transitions: ??? (unclear)
HubScene → does not exist yet
FlightMinigame → does not exist yet
ArenaScene → does not exist yet
Optional — get help from your agent:
Run through my game scene by scene. For each Scene class, tell me: what it displays, what inputs it handles, and what transitions out of it. Don't change the code.
Phase 2: Build the Hub Scene
Mixed phase. Input handling (T / F keys → transitions) is business. The draw code (fonts, colors, layout) is view and can be agent-assisted.
Objective
The hub is your "home base" — you return here after every minigame and fight. Simple menu with keys for Train and Fight.
Instructions
Hints
Hub scene skeleton — mark which methods are yours vs agent-ok:
class HubScene(Scene):
def __init__(self, dragon_name, stats):
self.dragon_name = dragon_name
self.stats = stats # {"hp": 10, "gold": 0, "level": 1}
self.title_font = pygame.font.Font(None, 64)
self.font = pygame.font.Font(None, 32)
# handle_event = YOUR logic (what does each key do?)
def handle_event(self, event):
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_t:
return TrainMenuScene(self.dragon_name, self.stats)
if event.key == pygame.K_f:
return FightMenuScene(self.dragon_name, self.stats)
return None
# draw = view (agent can polish this — fonts, layout, colors)
def draw(self, screen):
screen.fill((15, 25, 40))
# draw title, stats, menu prompts
Why pass stats between scenes? Each Scene is a new instance — shared state needs to flow through constructors. Alternative: a single GameState object every Scene references. Pick one approach and stick with it.
Optional — get help from your agent:
Walk me through two options for sharing dragon stats across scenes: (a) pass stats into each Scene's __init__, or (b) have a GameState object every scene references. Pros and cons, no code yet.And separately, once I have handle_event / update written myself:
Here's my HubScene. Help me polish the draw() method — layout the name, stats, and menu keybinds cleanly on a 960x540 screen. Don't change handle_event or update — those are done.
Phase 3: Build minigames.py + Two Playable Minigames
Handwrite this yourself — for the RULES. The game-loop math (gravity, collision, scoring) is yours. The drawing and sprite positioning is agent-ok.
Objective
Pick the two simplest minigames (flight + defense is a good pairing) and get each playable end-to-end.
Instructions
Hints
minigames.py — pure logic, no pygame imports beyond maybe Rect:
# minigames.py
GRAVITY = 400 # tune these yourself — they're YOUR game feel
FLAP_VELOCITY = -200
def apply_gravity(velocity, dt):
return velocity + GRAVITY * dt
def apply_flap(velocity):
return FLAP_VELOCITY
def rects_collide(a, b):
# a, b are (x, y, w, h) tuples
ax, ay, aw, ah = a
bx, by, bw, bh = b
return (ax < bx + bw and ax + aw > bx and
ay < by + bh and ay + ah > by)
FlightMinigame — scene calls into minigames.py:
from minigames import apply_gravity, apply_flap, rects_collide
class FlightMinigame(Scene):
def __init__(self, dragon_name, stats):
self.dragon_name = dragon_name
self.stats = stats
self.state = "intro" # intro → playing → done
self.score = 0
self.dragon_y = 200
self.velocity = 0
# update = YOUR logic (calls minigames.py for math)
def update(self, dt):
if self.state != "playing":
return
self.velocity = apply_gravity(self.velocity, dt)
self.dragon_y += self.velocity * dt
# check obstacles, increment score
# handle_event = YOUR logic
def handle_event(self, event):
if event.type == pygame.KEYDOWN:
if self.state == "intro" and event.key == pygame.K_SPACE:
self.state = "playing"
elif self.state == "playing" and event.key == pygame.K_SPACE:
self.velocity = apply_flap(self.velocity)
elif self.state == "done" and event.key == pygame.K_RETURN:
self.stats["gold"] += self.score
return HubScene(self.dragon_name, self.stats)
return None
# draw = view (agent can help)
def draw(self, screen):
screen.fill((30, 30, 80))
# draw dragon rect + obstacles + score
Keep obstacles tiny for MVP. Three rectangles that scroll left. Don't draw a cave.
Optional — get help from your agent:
Here's my FlightMinigame.update() and handle_event() — they're solid. Help me polish the draw() method: draw the dragon as a yellow rectangle, obstacles as red rectangles, score in the corner. Don't change my update or event logic.
Phase 4: combat.py + One Battle Arena
Handwrite this yourself — for the fight math. Damage, HP, defend mechanics — this is the core of your game. The draw code can be agent-assisted.
Objective
A single arena where your dragon fights one enemy, turn-based, until HP hits zero.
Instructions
Sample Output (in the pygame window)
========================
YOU vs WILD WYVERN
Dragon HP: 8 Enemy HP: 10
[ A ] Attack [ D ] Defend
Your last hit: 4 damage
========================
Hints
combat.py — pure math, no pygame:
# combat.py
import random
def roll_damage(min_dmg=2, max_dmg=5):
return random.randint(min_dmg, max_dmg)
def apply_attack(defender_hp, damage):
return max(defender_hp - damage, 0)
def apply_defended_attack(defender_hp, damage):
reduced = damage // 2
return max(defender_hp - reduced, 0), reduced
ArenaScene — calls into combat.py:
from combat import roll_damage, apply_attack, apply_defended_attack
class ArenaScene(Scene):
def __init__(self, dragon_name, stats):
self.dragon_name = dragon_name
self.stats = stats
self.player_hp = stats["hp"]
self.enemy_hp = 10
self.defending = False
self.last_message = ""
def handle_event(self, event):
if event.type != pygame.KEYDOWN:
return None
if event.key == pygame.K_a:
dmg = roll_damage()
self.enemy_hp = apply_attack(self.enemy_hp, dmg)
self.last_message = f"You hit for {dmg}."
self._enemy_turn()
elif event.key == pygame.K_d:
self.defending = True
self.last_message = "You brace."
self._enemy_turn()
if self.player_hp == 0 or self.enemy_hp == 0:
return self._go_home()
return None
def _enemy_turn(self):
dmg = roll_damage()
if self.defending:
self.player_hp, reduced = apply_defended_attack(self.player_hp, dmg)
self.last_message += f" Enemy attacks — you block {dmg - reduced}."
self.defending = False
else:
self.player_hp = apply_attack(self.player_hp, dmg)
self.last_message += f" Enemy hits for {dmg}."
def _go_home(self):
self.stats["hp"] = self.player_hp
if self.enemy_hp == 0:
self.stats["gold"] += 10
return HubScene(self.dragon_name, self.stats)
Don't animate anything yet. Stats update on each key press. Rectangles, not sprites.
Optional — get help from your agent:
Here's my ArenaScene logic. Help me draw a simple arena in draw(): HP bars at the top, dragon on the left, enemy on the right, action prompts at the bottom, last_message displayed. Use rectangles and text. No sprites. Don't change handle_event.
Phase 5: Journal (Catch-up)
Handwrite this yourself. Your journal is your voice on demo day.
Objective
Two journal entries — Checkpoint 1 (catching up from Phase 1) and Checkpoint 2.
Instructions
Hints
Commit message idea:
checkpoint 2: hub scene + flight/defense minigames + arena MVP + combat.py