Maze Muncher
Build a Ms. Pac-Man–style maze-chase game with a team of AI agents
The second practice project in the series. You'll rebuild the design that made Ms. Pac-Man great — a tile maze, pellets, power pellets, and four enemies with genuinely different personalities — with your own name, art, and mazes, and you'll build the hardest part (the enemy AI) with a four-teammate agent team where each personality is spec'd, owned, and tested.
Read this before you publish anything. Ms. Pac-Man is Bandai Namco's intellectual property: the name, the characters, the sprites, and the original maze layouts are protected. What's not protectable is game mechanics — tile-based maze movement, pellet eating, energizers, and the four-enemy AI design are ideas, and ideas are free to learn from and reimplement. This tutorial therefore builds Maze Muncher: original name, original character designs, original mazes, and the classic mechanics underneath. Don't rename it back, don't copy the original maze tile-for-tile, and don't use ripped sprites, especially on a business website. Your own dot-muncher and four "wisps" are legally yours; hers aren't.
- Why this game is the perfect second project
- Prerequisites (short — you've done the long version)
- The design: tiles, cornering, and four personalities
- Stage 1 — Scaffold: maze, muncher, pellets
- Stage 2 — Subagents: one wisp, lives, and power pellets
- Stage 3 — Review team: tune the chase
- Stage 4 — Agent team builds the four personalities
- Stage 5 — Second maze pack in a worktree (two-project stack)
- Troubleshooting specific to this project
- Credits
Why this game is the perfect second project
Brick Break taught you the workflow. Maze Muncher teaches you why the workflow matters, because this game has something Breakout doesn't: four AI-controlled characters whose behaviors interact. The original's ghosts are one of the most celebrated designs in game history precisely because each has a distinct, simple, testable targeting rule — and distinct, simple, testable is the exact shape of work that splits across an agent team. One teammate per personality would be too fine; one teammate owning all four behaviors, gated by a spec and a QA teammate who tests each rule in isolation, is just right.
It's also a step up in a second way: movement is tile-based, not free. Breakout's ball floats in continuous space; the muncher and the wisps live on a grid, turn only at intersections, and get cornering assistance so turns feel crisp. That grid logic is pure, deterministic, and perfect for unit tests, which means your TaskCompleted hook has real teeth here.
What carries over from Brick Break unchanged: the folder-ownership discipline, the three subagent roles, the hooks, plan approval on the shared-contract piece, and you on merges.
Prerequisites
The short list, since your machine is already set up from the Brick Break guide:
- Claude Code CLI v2.1.240 or later, signed in; agent teams flag on in
~/.claude/settings.json. - Your three subagent roles in
~/.claude/agents/(the versions withSendMessagein the implementer and reviewer tools lines). - Your settings template with the
:*permission syntax and the two team hooks. - A GitHub account and the
ghCLI.
Create the repo the same way as before:
mkdir -p ~/code/maze-muncher && cd ~/code/maze-muncher git init -b main gh repo create maze-muncher --private --source=. --remote=origin mkdir -p .claude docs/specs cp ~/claude-templates/settings.json .claude/settings.json code .
Remember the two trip-wires from last time: role files load at session start (restart after creating or changing them), and don't leave .claude/settings.json open with unsaved edits while Claude writes to it.
The design: tiles, cornering, and four personalities
The world is a grid of tiles
The maze is a text grid: # wall, . pellet, o power pellet, - empty corridor, M muncher spawn, 1 2 3 4 wisp spawns, = the den door, T tunnel mouths (wrap to the matching T on the other side). Everything — movement, collision, wisp pathing — happens in tile coordinates, with pixel positions derived only at render time. A character occupies a tile and a fractional offset along its direction of travel; it may only change direction when centered on a tile, except for cornering: a turn buffered a few pixels early is remembered and executed at the center, which is what makes the controls feel good instead of sticky.
The four personalities (the famous part, genericized)
Each wisp picks a target tile and, at every intersection, chooses the legal direction that minimizes straight-line distance to that target — no pathfinding, no search, and they may never reverse except on a mode change. All the personality lives in how the target is chosen:
| Wisp | Nickname | Target rule (chase mode) | Personality that emerges |
|---|---|---|---|
| Rush | the chaser | The muncher's current tile. | Direct pursuit; always on your tail. |
| Cutoff | the ambusher | Four tiles ahead of the muncher's current direction. | Gets in front of you; punishes straight lines. |
| Pincer | the flanker | Take the tile two ahead of the muncher, draw the vector from Rush's tile to it, and double it. | Unpredictable sweeps; teams up with Rush without either knowing it. |
| Wander | the shy one | The muncher's tile when more than 8 tiles away; its own scatter corner when closer. | Approaches, loses nerve, retreats; keeps a lane open that skilled players exploit. |
Wisps cycle globally between scatter (each targets its own home corner — the pack disperses) and chase on a timer (7s scatter / 20s chase, repeating, scatter phases shrinking per level). Eating a power pellet flips everyone to frightened: they slow down, pick random directions at intersections, and can be eaten, returning to the den as "eyes." In the Ms. Pac-Man tradition, the first few seconds of a level use semi-random targeting so patterns can't be memorized — you'll implement that as a per-wisp random target during the opening scatter.
Layout (ownership boundaries are directories, again)
maze-muncher/ ├── CLAUDE.md ├── .claude/ settings.json + agents/ (copied from template) ├── docs/ ROADMAP.md · STATUS.md · specs/ ├── public/ index.html · style.css ├── src/ │ ├── engine/ loop.js · input.js (with turn buffering) · draw.js │ ├── logic/ grid.js · movement.js · targeting.js · modes.js ← pure, no DOM │ ├── entities/ muncher.js · wisp.js · fruit.js ← plain objects │ ├── levels/ mazes.js (text grids) · parse.js ← pure │ ├── ui/ hud.js · screens.js │ └── main.js ├── tests/ unit/ (mirrors src) · integration/ ├── server.js Express static, port 3000 └── package.json npm start · npm test (node --test)
The load-bearing decision: all four targeting rules live in one pure file, src/logic/targeting.js, as small functions taking plain state and returning a target tile. That makes each personality independently unit-testable ("given muncher at (10,10) facing left and Rush at (3,3), Pincer targets (13,17)"), which is exactly what your QA teammate will do in Stage 4.
Roadmap
| Milestone | Scope | Built by |
|---|---|---|
| M1 Maze | Parse and render a maze; muncher moves on the grid with cornering; eats pellets; level clears when none remain. | Solo session (Stage 1) |
| M2 One wisp | Rush chases; collision costs a life (3 lives); power pellets flip frightened mode; eaten wisps return as eyes; score. | Solo + subagents (Stage 2) |
| M3 Tuning | Speeds, timers, frightened duration, cornering window — reviewed and tuned. | Review team (Stage 3) |
| M4 The pack | All four personalities, scatter/chase cycling, semi-random opening, the den and door logic, fruit that wanders the maze. | Build team (Stage 4) |
| M5 Maze pack | Three more mazes with per-maze colors and a maze-select screen, built as its own project in a worktree. | Two PMs + leads (Stage 5) |
1Scaffold: maze, muncher, pellets
Goal: a playable M1 — you steer the muncher around an original maze eating pellets, with cornering that feels right.
What you'll learn: speccing "feel" (cornering, turn buffering) precisely enough that an agent can build it, and designing an original maze in text.
Start claude --name solo in the repo, switch to plan mode (Shift+Tab), and paste:
Review the plan for two things: the pure boundary held (no Math.random in logic), and the maze is original. Approve, let it build, then npm install && npm start and play at localhost:3000. Cornering is the thing to feel-test: turns should catch when pressed slightly early. Paste the roadmap when asked, then commit, merge, push — the same rhythm as Brick Break.
Check yourself: maze renders and is clearly not the original layout; pellets disappear and score climbs; a buffered early turn executes at the corner; npm test passes; repo pushed.
2Subagents: one wisp, lives, and power pellets
Goal: M2 — Rush hunts you, contact costs a life, power pellets turn the tables — built through your explorer / implementer / reviewer roles.
What you'll learn: delegating a feature that spans pure logic and wiring, and writing the mode state machine that Stage 4's team will extend.
Restart the session if you've edited any role files. Then:
Play it: get chased, grab a power pellet, eat Rush, watch the eyes travel home. Fix anything that feels wrong by describing it in plain words, then commit, merge, push.
Why the mode machine had to be pure and rng-injected: Stage 4's QA teammate will unit-test "frightened wisps turn randomly" with a seeded rng, and "scatter flips to chase at exactly 7s" with a fake clock. Neither test is writable if the machine reads Date.now() or Math.random() directly. You just built the contract the team depends on.
Check yourself: Rush corners you eventually (it should — direct pursuit always does); frightened mode is survivable; the eyes reach the den and Rush re-emerges; tests pass; merged and pushed.
3Review team: tune the chase
Goal: a tuned M3 and a written spec for M4, produced by a three-teammate review team.
Approve the tuning numbers you like ("apply the tuning changelist, run npm test, show me the diff"), then have it write the spec: "Write the final docs/specs/pack.md, commit both as 'feat: tuning + docs: pack spec', push, and shut the team down." Read pack.md yourself before Stage 4 — every vagueness in it becomes a teammate's guess.
Check yourself: the game feels meaningfully better than your pre-review build; docs/specs/pack.md exists and gives each personality an exact target rule, the timer table, den release order, and fruit behavior.
4The agent team builds the four personalities
Goal: M4 shipped by a four-teammate team — the full pack with distinct personalities, scatter/chase cycling, the den, and fruit — with plan approval on the targeting contract and hooks gating every completion.
What you'll learn: ownership when the "feature" is behavior rather than screens, and letting QA test personalities as math instead of by playing.
Confirm the hooks block is in .claude/settings.json (test-gate on TaskCompleted, commit-and-report on TeammateIdle), start a fresh session, and paste:
Watch the same beats as Brick Break's build: Ctrl+T for the task list, the plan-approval exchange with brains, and any hook rejections. When all branches are ready: "Merge in the order brains, pack, face, qa, running npm test after each; stop on failure; then delete the branches, push, and report the final test count."
Then play it properly. The test that matters isn't in the suite: with all four active, does circling stop working? Cutoff should punish your straight runs, Pincer should surprise you from angles, and Wander's retreats should be the escape lane you learn to use. If a personality isn't legible in play, that's a spec conversation, not a bug — sharpen its rule in pack.md and hand it back to a single implementer.
Check yourself: four visually and behaviorally distinct wisps; scatter moments where the pack visibly disperses; the semi-random opening differs run to run; qa's tests pin each personality's math; merged, green, pushed.
5Second maze pack in a worktree
Goal: the two-project stack from the Brick Break guide's Stage 5, with real work on both sides: one PM polishes the game, the other builds a maze pack — three more original mazes, per-maze palettes, and a maze-select screen — in its own worktree. Ms. Pac-Man's signature improvement over the original was multiple mazes; it makes the perfect second project.
The shared contract between the projects is the maze format, which is why parse.js validation matters: the maze-pack PM's team designs against the format, and the game PM's team consumes whatever validates. Set it up exactly as the Brick Break guide describes — claude --worktree maze-pack --name pm-mazes, claude --worktree polish --name pm-game, two leads, standing briefs, idle notices — and give pm-mazes this charter:
If your son is running his own stack, this is the natural split: one of you takes pm-game, the other pm-mazes, and the leads negotiate the format between you. The maze-connectivity check qa runs (flood fill from the muncher spawn) is a nice piece of pure logic to compare implementations of.
Check yourself: four sessions in /list-agents; STATUS.md maintained by a lead; a format question that actually traveled PM → lead → PM; four selectable mazes, each clearing correctly.
Troubleshooting specific to this project
| Symptom | Likely cause | Fix |
|---|---|---|
| Wisps vibrate or spin at intersections | Direction chosen every frame instead of once per tile center | Decide direction only when centered on a new tile; cache it until the next center |
| A wisp reverses mid-corridor | Reverse not excluded from the legal-direction set | Exclude the opposite of current direction except on a mode change; qa has a test for this — check it's not skipped |
| Muncher sticks on corners | Turn buffering window too small, or buffered turn cleared on wall contact | Keep the buffered turn until executed or replaced; widen the window to 4px and re-feel |
| Wisps never leave the den | Door tile treated as a wall for wisps, or release rules never fire | The door is passable to wisps in eyes/exiting states only; check den release counters in modes.js |
| Pincer behaves exactly like Rush | Vector doubled from the wrong origin (muncher instead of Rush) | The vector runs FROM Rush's tile TO two-ahead-of-muncher, then doubles; qa's fixed-board test pins the exact tile |
| Frightened mode identical every time | rng not actually injected — a literal Math.random inside logic | Grep src/logic for Math.random; the seeded-rng tests should have caught it, so also check they assert on sequences, not just types |
| Game unwinnable / pellet count never reaches zero | A pellet tile unreachable, or tunnel tiles counted as pellets | Run the flood-fill connectivity check from Stage 5 against maze 1 too |
| Everything from the Brick Break guide | — | That guide's Troubleshooting and Field Notes sections all still apply, including role-loading at session start and the stale-tab problem |