👻 Planning Workbook: Pac-Man
How to use this workbook: work top to bottom. It walks you through planning Pac-Man using the method from 01-How-to-Plan-a-Game, then gives you a build order. It tells you what each piece is and which technique to reach for — but you open the Technique Playground and write the code. Wherever you see ✍️ YOUR TURN or a checkbox, that's work for you.
The game in a nutshell
You steer Pac-Man through a maze full of dots. Eat every dot to win. Four ghosts chase you — touch one and you lose a life. Sometimes there are big "power" dots that let you turn the tables and eat the ghosts for a few seconds.
Step 1 — Say it in one sentence
Here's a starting version. ✍️ YOUR TURN: tweak it into your own words.
"Move Pac-Man around a maze eating all the dots without getting caught by the ghosts."
Step 2 — What's on the screen? (objects)
Here are the objects Pac-Man needs. ✍️ YOUR TURN: for each, jot the shape/color you'll use (remember: shapes are fine — Pac-Man is literally a yellow circle).
- Pac-Man — the player. (yellow circle?)
- Walls — the maze. (blue squares?)
- Dots / pellets — the little things you eat. (tiny pale circles?)
- Power pellets — bigger dots with a special effect. (bigger circles?)
- Ghosts — the enemies. (colored squares or circles?)
- Score + Lives text — the HUD.
Step 3 — What does each object DO? (behaviors)
✍️ YOUR TURN: fill in the blanks (a word or two each).
- Pac-Man: moves in the direction you press, and __________ at walls.
- Walls: just sit there and __________ Pac-Man and ghosts.
- Dots: sit still until Pac-Man __________ them.
- Ghosts: move around the maze and try to __________ Pac-Man.
Step 4 — The rules ("when ___, then ___")
Some are written for you. ✍️ YOUR TURN: finish the last two.
- When Pac-Man touches a dot → remove the dot, +10 score.
- When Pac-Man touches a power pellet → remove it, ghosts become "scared" for a few seconds.
- When Pac-Man touches a ghost (normal) → lose a life; if lives = 0, game over.
- When Pac-Man touches a ghost (scared) → __________________________.
- When all dots are gone → __________________________.
Step 5 — States (scenes)
A simple, solid set:
Title ("Press SPACE") → Playing → Win (all dots eaten) or Game Over (lives = 0) → back to Title.
(Technique 11 covers scenes.)
🧩 Your Architecture Map
Two rows are filled in as examples. ✍️ YOUR TURN: complete the rest.
| Object | Shape & color | What it does | Collides with | Components it needs | Technique(s) |
|---|---|---|---|---|---|
| Pac-Man | yellow circle | moves with keys, stops at walls | walls, dots, ghosts | pos, circle, color, area, body |
4, 5, 7, 8 |
| Wall | blue square | blocks movement | (everything solid) | pos, rect, color, area, body({isStatic:true}) |
7, 10 |
| Dot | ? | ? | Pac-Man | ? | 8 |
| Power pellet | ? | ? | Pac-Man | ? | 8 |
| Ghost | ? | ? | Pac-Man | ? | 8, 14 |
| Score/Lives | text | shows numbers | — | pos, text |
12 |
🏗️ Build order (your milestones)
Build these one at a time, in order. After each one, run your game and check the "✅ works when" line before moving on. Never build the next thing until the current thing works.
Milestone 1 — Draw the maze
Build the walls from a text map. A maze is just a grid, and Technique 10 (tile maps) turns a picture-made-of-letters into real wall objects. Here's a starter map you can paste and then redesign — # is a wall, . is a dot, o is a power pellet, space is empty:
#####################
#.........#.........#
#.###.###.#.###.###.#
#o###.###.#.###.###o#
#.........#.........#
#.###.#.#####.#.###.#
#.....#...#...#.....#
#####.### # ###.#####
#........ P ........#
#####.### # ###.#####
#.....#...#...#.....#
#.###.#.#####.#.###.#
#o.................o#
#####################
Use Technique 10 to turn each # into a solid wall (area + body({isStatic:true})), each . into a dot, each o into a power pellet, and each P into Pac-Man.
Use 32×32 tiles (tileWidth: 32, tileHeight: 32). The map is 21 wide and 14 tall, so the level is 672 × 448 pixels — start with kaplay({ width: 672, height: 448, letterbox: true }).
🔑 Make Pac-Man smaller than his corridor. The corridors here are one tile — 32 pixels — wide. If Pac-Man is nearly 32 wide too, he'll catch on every corner and the game will feel broken. A good rule for any grid game: the player should be about 70% of a tile. For 32-pixel tiles that's
circle(11)(22 pixels across), leaving 5 pixels of slack on each side — once he's centred in his tile, which is what thepos(16, 16)below does. If cornering still feels sticky, go smaller before you go looking for a bug.
⚠️ Exactly one
P. Each letter in the map becomes an object, so twoPs means two Pac-Men — both taking your key presses at once. If your player seems to "split in two", count yourPs.
- 🔧 Techniques: 1, 2, 10
- ✅ Works when: you open the game and see your maze drawn on screen.
💡 Your dots will straddle the tile corners. A tile is placed by its top-left corner, and a
circle()is drawn from its centre — so each dot ends up centred on the corner, half in one tile and half in the next. Nudge it by half a tile withpos()inside the recipe:".": () => [ circle(4), color(255, 245, 200), area(), pos(16, 16), "dot" ],Do the same for
"o"and"P"(16 is half of your 32-pixel tile). Without it Pac-Man starts 11 pixels inside the wall above him and gets shoved out on the first frame — you'll see him hop.⚠️ Reaching for
anchor("center")here is the natural guess and it does nothing — KAPLAY circles are already anchored at their centre. It's the tile that sits at the corner, sopos()is the thing that moves it.
Milestone 2 — Pac-Man moves and stops at walls
Add Pac-Man as a body with an area. Read the arrow keys (Technique 4) and move him (Technique 5). Because the walls are solid bodies, they'll block him automatically — that's Technique 7. Make sure you do not turn on gravity for this game (Pac-Man shouldn't fall).
⚠️ Read this first — it's the one that stops everybody. Pac-Man was made by
addLevel(), so he lives inside the level, not loose in the scene. Plainget()won't find him:const player = get("player")[0] // undefined ❌ const player = get("player", { recursive: true })[0] // works ✅Nothing goes red when the page loads. It looks fine until you press an arrow key, and then you get
TypeError: Cannot read properties of undefined (reading 'move'). If that's your error, this is why. The same applies to"dot","power"and"wall".
- 🤔 DECISION: In the real Pac-Man, he keeps moving in the last direction you pressed until he hits a wall — you don't hold the key down. Do you want that, or the simpler "only moves while the key is held"? Pick one and note why. (Simpler is a fine first choice — you can upgrade later.)
- 🔧 Techniques: 4, 5, 7
- ✅ Works when: you can drive Pac-Man around the corridors and he can't pass through walls.
Milestone 3 — Eat the dots and score
Give Pac-Man an onCollide with the "dot" tag (Technique 8). When they touch: destroy the dot and add to the score. Show the score with a text label (Technique 12).
- 🔧 Techniques: 8, 12
- ✅ Works when: driving over a dot makes it disappear and the score goes up.
Milestone 4 — Win when all dots are gone
After eating a dot, check how many dots are left. If there are zero, go to the Win scene. (Hint: there's a way to grab every object with a given tag and count them — look for it in Technique 3.)
⚠️ Count them the recursive way, for the same reason as Milestone 2:
if (get("dot", { recursive: true }).length === 0) go("win")With plain
get("dot")the count is zero from the very first frame, so you'd win before eating a single dot — and nothing would look broken, you'd just go straight to the Win screen.
- 🔧 Techniques: 3, 11
- ✅ Works when: eating the last dot sends you to a "You Win!" screen — and not before.
Milestone 5 — Add the ghosts
Add one ghost first (get one working before you add four). A ghost wanders the maze on its own: it walks in a straight line, and every time it reaches the middle of a tile it picks a new random direction.
🔑 The big idea: ask the map, not the walls. Your maze came from
MAP, an array of strings — so you can just look up whether a tile is a wall before stepping onto it, instead of walking into it and waiting to be stopped.// is the tile at column c, row r a wall? const solid = (c, r) => !MAP[r] || MAP[r][c] === undefined || MAP[r][c] === "#"When the ghost reaches a tile centre, keep only the directions whose next tile isn't solid, and choose one of those at random. Because it only ever steps onto a tile it has already checked, it can never touch a wall — so the ghost needs no
body()at all.
- ⚠️ Don't build it the other way round. "Walk until a wall stops you, then turn" sounds simpler and it jams. A ghost with a
body()gets wedged into a corner, and every new direction it rolls is blocked too, so it sits there twitching — measured at 91% of frames stalled and 8 tiles visited in 25 seconds. The map-checking version visits the same maze freely and stalls 0% of the time. Any time you find yourself asking the physics "did something stop me?", check the map instead. - ⚠️ Don't reach for Technique 14 either. Its chaser is built for open field, so it only has
area()and drives straight through maze walls; addingbody()fails the same way as above. Same lesson, same fix. - 🤔 DECISION: what stops your ghost from immediately turning round and retracing its steps? (Hint: drop the direction it just came from — unless that's the only legal one, or it'll get stuck in dead ends.)
- When a ghost touches Pac-Man → lose a life (Technique 12). If lives hit 0 → Game Over scene (Technique 11).
- 🔧 Techniques: 8, 12, plus 5 for moving and 10 for reading the map
- ✅ Works when: the ghost roams the whole maze on its own, never overlaps a wall, and touching it costs you a life.
Milestone 6 — More ghosts + the title and game-over screens
Add the other ghosts (give them different colors and maybe different speeds). Wrap everything in scenes: a Title screen that starts the game, and a Game Over screen that shows your score and lets you restart.
- 🔧 Techniques: 11, 12
- ✅ Works when: the game has a start screen, plays, and ends properly — a full loop.
Milestone 7 (stretch) — Power pellets
Make the o pellets special: when eaten, set a "ghosts are scared" mode for ~6 seconds (change their color to blue). While scared, touching a ghost eats it (send it back home / +points) instead of costing a life. This is a great exercise in tracking a temporary state with a timer.
- 🤔 DECISION: how will you remember "we are in scared mode"? A variable? A timer that flips it back? Plan it before you code it.
- 🔧 Techniques: 8, 9 (timers), 12
🌟 Stretch goals (only after it works!)
- A ghost that really hunts you. You already have almost all of this from Milestone 5. Your wandering ghost works out the legal directions at each tile centre and then picks one at random — change that one line to pick the legal direction that gets it closest to Pac-Man instead, and you have a hunter. Everything else stays the same, including the
solid()lookup and having nobody(). That really is the algorithm the arcade ghosts use. Give each ghost a different rule and they start to feel like characters: one aims at Pac-Man, one aims a few tiles ahead of where he's heading, one stays random. - Two dots side by side look better with a real sprite — swap Pac-Man's circle for a sprite image (Technique 2 shows how).
- Make Pac-Man's mouth "chomp" by flipping between two shapes/sprites.
- Add a siren beep or a "wakka wakka" when eating dots (Technique 15).
- Add levels: when you win, load a harder maze and speed the ghosts up.
- Smarter ghosts: each one chases differently (one targets Pac-Man, one tries to get ahead of him).
✅ Pac-Man planning checklist
- I filled in the Architecture Map (all rows, including Components and Technique columns).
- I decided how Pac-Man moves (keep-going vs hold-to-move).
- I decided how ghosts move (random vs chase) for my first version.
- I know my win condition (all dots gone) and lose condition (0 lives).
- I have my milestone list and I'm building them one at a time, testing each.
Now go build Milestone 1. When you get stuck on a piece, the answer is almost always "open the Playground and find that technique." 🎮