Dot Muncher Maze Game — Free HTML CSS JS Snippet
Dot Muncher Maze Game · Games · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Dot Muncher — Grid-Locked Movement, Ghost Chase AI and a Self-Healing Maze

The maze-chase arcade formula — eat every dot while being hunted through a grid, with power pellets that briefly reverse the roles — packs an unusual amount of game programming into a small board: tile-locked movement that turns cleanly at intersections, several ghosts each running their own targeting behaviour, a global mode timer, and a win condition that depends on the maze actually being solvable. This snippet builds the whole thing on one HTML5 <canvas> with a four-way D-pad, and adds a robustness trick that makes the maze self-healing.
Tile-locked movement with progress between tiles
Every mover — the player and each ghost — is stored as a current tile (c, r), a direction, and a prog value from 0 to 1 representing how far it has travelled toward the next tile. Its pixel position is just the tile centre plus dir * prog * TS. Each frame prog advances by speed * dt, and when it crosses 1 the mover snaps onto the next tile and makes a decision. This tile-hop model means an entity can only ever turn at a tile centre and can never clip through a wall, no matter the frame rate — the essential property a maze game needs, and something a free-floating velocity model gets wrong.
Direction buffering and instant reversal
The player's D-pad and keyboard don't turn immediately — they set a *queued* want direction, and at each tile the game turns into it only if that way is open. That buffering is why the game feels forgiving: you can press up just before the corridor opens and the turn still lands. Reversing direction is special-cased to happen instantly mid-corridor, because turning back the way you came is always legal and should feel immediate.
Three ghosts, three targeting rules
Each ghost picks its direction at every tile by choosing, among the open non-reverse exits, the one that gets it closest to a *target tile*. What differs is the target. A global timer cycles between scatter (each ghost heads for its own corner) and chase. In chase, the first ghost targets the player directly, the second aims a few tiles *ahead* of the player's heading to cut you off, and the third switches between chasing and retreating depending on how close it is. Eating a power pellet flips every ghost to frightened: they slow down, move randomly, turn blue, and can be eaten for bonus points before the timer runs out and they flash back.
A self-healing, always-winnable maze
Because the win condition is "no dots left", an unreachable dot in a hand-drawn maze would make the game impossible. At load, buildMaze() flood-fills (BFS) from the player's start over every non-wall tile, clears any dot that wasn't reached, and counts only the reachable ones — so the board is guaranteed solvable however the ASCII maze was drawn. The same reachability pass picks a valid, reachable centre tile to spawn the ghosts, so they can always roam. Movement is delta-time scaled, lives and levels are tracked, and the best score persists to localStorage.
Build with AI
Build, Understand, Optimize, and Extend It With AI
Paste this snippet's HTML, CSS and JS into an AI assistant like Claude and ask it to explain the tile-locked mover (current tile plus 0..1 progress), the buffered-turn-and-instant-reversal input, the target-tile ghost AI with scatter/chase/frightened modes, and the BFS that makes the maze self-healing. It is a strong base to extend: ask for a fourth ghost with its own targeting, a proper ghost house with staggered release and a return-to-house state for eaten ghosts, bonus fruit that appears mid-level, a side tunnel with screen wrap, or multiple hand-drawn boards selected by level. You could also ask it to add swipe controls as an alternative to the D-pad, or to port the loop into a React component with useRef and useEffect. Treat it as a working prototype to question and rebuild.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build a mobile-friendly Pac-Man-style maze-chase game on an HTML5 canvas in plain HTML, CSS and JavaScript — no frameworks. Use a generic name and art (do not copy trademarked characters).
Requirements:
- Parse an ASCII maze (walls, dots, power pellets, a player start) into wall and dot grids. At load, run a BFS flood fill from the player start, remove any unreachable dots, count only reachable ones for the win condition, and pick a reachable centre tile to spawn the ghosts — so the board is always winnable.
- Use tile-locked movement for the player and ghosts: store a current tile, a direction, and a progress value from 0 to 1 toward the next tile, deriving pixel position from those. Only turn and test walls when progress crosses a tile boundary, so entities turn cleanly at intersections and never clip walls at any frame rate.
- Controls: a four-way on-screen D-pad plus keyboard arrows/WASD. Buffer the input as a queued direction applied at the next open tile, and make reversing direction instant. Provide these on touch and desktop from the same state.
- Add three ghosts that each pick, at every tile, the open non-reverse exit minimising distance to a target tile. Cycle a global timer between scatter (target own corner) and chase (one targets the player, one aims ahead to ambush, one alternates chase/retreat). Power pellets set all ghosts frightened for a few seconds: slower, random movement, edible for bonus points, with an end-of-timer blink.
- Track score (dots, pellets, eaten ghosts), three lives with position resets on death, and levels that refill dots and speed ghosts up. Use requestAnimationFrame with delta-time scaling, persist the high score in localStorage with defensive try/catch, and show start / game-over overlays with an animated muncher mouth.Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.
Step by step
How to Use
- 1Start the mazeTap "Start" to call startGame(), which parses the maze, flood-fills to keep only reachable dots, spawns the muncher and three ghosts, and begins the delta-time loop.
- 2Steer with the D-padTap ▲ ◀ ▶ ▼ (or arrow keys / WASD) to set your direction. The input is buffered — the muncher turns into it at the next tile where that way is open — and reversing is instant, so pressing back the way you came flips you immediately.
- 3Eat every dotTravel the corridors to eat all the small dots (10 points each). Clearing every dot advances you to the next level, which refills the board and speeds the ghosts up.
- 4Use the power pelletsThe four large flashing pellets (50 points) turn all ghosts blue and frightened for a few seconds. While blue they slow down and flee; touch one to eat it for 200 points, sending it back to the centre.
- 5Avoid the ghostsWhen not frightened, a ghost touching you costs one of your three lives and resets everyone to their start tiles. Each ghost hunts differently — one chases directly, one tries to cut you off, one wavers — so read their colours.
- 6Beat your high scoreSurvive, clear levels, and rack up points; the best persists in localStorage under dot-muncher-best. Redraw the MAZE array to design your own board — unreachable dots are cleaned automatically, so you cannot make it unwinnable.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Every entity uses tile-locked movement: it stores the tile it is on, a direction, and a progress value from 0 to 1 toward the next tile, with its pixel position derived from those. Turns and wall checks only happen when progress crosses 1 and the entity snaps onto the next tile. Because it can never be partway through a wall, it turns cleanly at intersections and never clips, regardless of frame rate — unlike a free-floating velocity model.
Input is buffered rather than applied instantly. The D-pad and keyboard set a queued want direction, and at each tile the muncher turns into it only if that path is open. So if you press up a fraction before reaching the opening, the turn is remembered and executed the moment the corridor allows it. Reversing direction is special-cased to apply immediately, since turning back is always legal.
At each tile a ghost looks at its open exits (excluding an immediate U-turn) and picks the one that minimises the distance to a target tile. A global timer alternates scatter mode (each ghost targets its own corner) with chase mode. In chase, one ghost targets your tile directly, one targets a few tiles ahead of your heading to ambush you, and one alternates between chasing and retreating based on distance — so they cover the board instead of following identical paths.
Eating one of the large flashing pellets sets every ghost to frightened for a few seconds: they slow down, choose directions randomly, and turn blue. During that window touching a ghost eats it for 200 points and sends it back to the centre spawn instead of costing you a life. As the timer runs out the ghosts blink to warn you before they become dangerous again.
The game cleans the board at load. It runs a breadth-first flood fill from the player start over all corridor tiles, removes any dot that was not reached, and counts only reachable dots toward the win condition — so the level is always winnable no matter how the ASCII maze is drawn. The same pass also chooses a reachable centre tile to spawn the ghosts, so they can always move.