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 in minigames.py. The update() and handle_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

Checkpoint 2 Readiness

By Thursday April 23 at 3pm:

Helpful Resources