We built MacMan as a TypeScript monorepo whose core is a deterministic rules engine with no DOM or Phaser dependency. The browser renders it; the tests drive it directly.
Key design decisions
- Ghost personalities as target tiles. Every roaming ghost decides only at tile centres: it never reverses voluntarily and takes the exit whose next tile is closest to its target, breaking ties Up, Left, Down, Right. The four personalities differ only in
chaseTarget(): the player's tile, four tiles ahead, a vector doubled from the red ghost, or the player's tile unless within eight tiles. Coordinated-looking ambushes come out of these simple rules.
- A real state machine. Scatter and Chase alternate on a per-level schedule that pauses while ghosts are frightened, and every switch reverses the ghosts. Frightened ghosts turn using the seeded PRNG, and eaten ghosts follow a precomputed BFS distance field to the door, then run a scripted path into the house, revive and leave. House release uses personal dot counters, a shared counter after a lost life, and a no-eating timer.
- Forgiving controls in the engine, not the UI. A pressed direction is buffered for 0.6 seconds, turns are accepted up to 0.42 tiles before or after a centre, and the off-axis error is absorbed while moving. The input layer also latches taps, so a key pressed and released between two simulation ticks still counts.
- Fixed timestep, interpolated rendering. The simulation always advances in 1/60-second ticks. The renderer draws actors between the last two ticks, so motion stays smooth on high-refresh screens and the game never speeds up or slows down with the frame rate.
- Data-driven difficulty. Speeds, fright times, flash counts, the red ghost's speed-up thresholds, mode schedules and fruit values are tables in one config file, following the arcade originals.
- No asset pipeline. The neon maze is traced from the tile grid with rounded convex and concave corners and a glow pass; sprites are painted with Canvas 2D at the screen's own tile size and packed into one atlas; sounds are synthesised. Everything stays sharp from phones to 4K.
Architecture
packages/shared holds the maze loader and validator, movement, ghost targeting and state machine, house rules, scoring, fruit, the level tables and a BFS autopilot used for the menu demo and soak tests. apps/client is Vite and Phaser 3 (WebGL with Canvas fallback) behind a responsive HTML/CSS shell for menus, HUD, pause and game over, with a LeaderboardProvider hook for local and online scores. apps/server is a small Express API that validates, rate-limits and stores high scores, and serves the built game in production.