> Source URL: /unit-3/project-paths/edison-w/edison-w-2026-04-18.guide
# 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](../../lectures/01-the-mvp/01-the-mvp.lecture.md) — 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

- [ ] Run `uv run python main.py` and play through whatever's there
- [ ] For each Scene you can reach, note: what's visible, what's interactive, where does it transition to?
- [ ] Write a short note at the top of your journal (your Checkpoint 1 catch-up entry)

### 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:**
>
> ```text
> 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

- [ ] Create a `HubScene` class
- [ ] Display the dragon's name + stats (HP, Gold, Level)
- [ ] Show two keybinds: "T — Train" and "F — Fight"
- [ ] On T, transition to `TrainMenuScene`; on F, transition to `FightMenuScene`
- [ ] Wire `NameDragonScene` to transition to `HubScene` after name confirmation

### Hints

**Hub scene skeleton — mark which methods are yours vs agent-ok:**

```python
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:**
>
> ```text
> 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:
>
> ```text
> 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

- [ ] Create `minigames.py` at the project root — it holds scoring and game-rule functions (NOT drawing)
- [ ] In `minigames.py`, write helpers like `apply_gravity(velocity, dt)`, `apply_flap(velocity)`, `is_collision(dragon_rect, obstacle_rect)`
- [ ] Build `FlightMinigame` scene. The scene's `update()` calls your `minigames.py` functions. The scene's `draw()` renders the result
- [ ] Once flight works, copy-paste the scene structure for `DefenseMinigame` with different mechanics

### Hints

**`minigames.py` — pure logic, no pygame imports beyond maybe `Rect`:**

```python
# 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`:**

```python
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:**
>
> ```text
> 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

- [ ] Create `combat.py` at the project root
- [ ] Write `roll_damage()` (random 2–5), `apply_attack(defender_hp, damage)`, `apply_defended_attack(defender_hp, damage)` (halves damage)
- [ ] Create `ArenaScene` — its `update()`/`handle_event()` call `combat.py`, its `draw()` renders HP bars + prompts
- [ ] When one HP hits zero, transition to a result screen → back to hub

### 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:**

```python
# 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`:**

```python
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:**
>
> ```text
> 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

- [ ] Fill Checkpoint 1 using your Phase 1 audit notes
- [ ] Add Checkpoint 2: hub + minigames + arena progress, what's in `combat.py` / `minigames.py` (your handwritten core) vs what's in the Scene `draw()` methods (agent-assisted), anything still rough
- [ ] Commit and push

### Hints

**Commit message idea:**

```
checkpoint 2: hub scene + flight/defense minigames + arena MVP + combat.py
```

---

## Checkpoint 2 Readiness

By Thursday April 23 at 3pm:

- [ ] `combat.py` exists with fight math (no `pygame` imports)
- [ ] `minigames.py` exists with minigame rules (no `pygame` imports)
- [ ] Hub scene where you can pick train or fight
- [ ] Two playable minigames that return to the hub
- [ ] One battle arena where someone's HP reaches zero
- [ ] No crashes navigating between scenes
- [ ] Checkpoint 1 + Checkpoint 2 entries in `project.journal.md`
- [ ] Committed and pushed

## Helpful Resources

- [Checkpoint 2 Instructions](../../projects/final-project-checkpoint-2.project.md)
- [Lecture 1: The MVP](../../lectures/01-the-mvp/01-the-mvp.lecture.md)
- [Pygame Setup Guide](../../resources/pygame-setup.guide.md)


---

## Backlinks

The following sources link to this document:

- [April 18 — Checkpoint 2](/unit-3/project-paths/projects.path.llm.md)
- [April 18 -- Checkpoint 2 (Working MVP)](/unit-3/project-paths/edison-w/edison-w.path.llm.md)
- [April 18 - Checkpoint 2](/unit-3/projects/showcase/edison-w.project.llm.md)
