Puzzle
A playbook for grid/board puzzle games — the board model, move input, rule resolution
(matching, pushing, logic), scoring, undo, and level progression. This is a compositional
skill: it models board state and rules and presents them through a tilemap/UI. It does not
re-teach tilemaps; it defines the resolution loop and the correctness rules (clean state,
deterministic resolution, undo) that keep a puzzle fair and bug-free.
When to use
- Use when the game is a discrete board the player changes with moves, and the board
resolves by rules: match-3/tile-matching, sokoban/block-pusher, sliding puzzle, logic grid.
- Use when designing match/cascade resolution, undo, level progression, or solvability.
When not to use: real-time grid action with permadeath → roguelike. Card zones/turns →
card-game. Physics-based "puzzle platformer" → platformer + physics-tuning. For the tile
rendering, use godot-tilemap / unity-tilemap-2d.
Core loop
Read the board → plan a move → make the move → the board resolves by its rules (match, push,
fall, fill, cascade) → see progress toward the objective → repeat until solved/failed. The
fun is the planning; the engine's job is to resolve each move deterministically and
present it clearly.
Must-have systems
- Board model — a grid of cells holding pieces; the single source of truth (logic, not visuals).
- Move input — swap, push, drag, rotate, or place; validate legality before applying.
- Rule resolution — detect and apply the genre's rule (matches, pushes, logic) until stable.
- Cascades/chains — when resolution changes the board, re-resolve until no more changes.
- Objectives + scoring — win/lose conditions (score, clear all, reach goal); move/time limits.
- Undo — revert the last move (and its resolution) exactly; essential for thinky puzzles.
- Level progression + (often) generation — hand-authored or generated solvable boards.
- Feedback ("juice") — clear, satisfying animation/sound for matches, falls, and chains.
Design knobs
| Knob |
Effect |
Notes |
| Grid size / shape |
complexity |
Square is standard; hex/irregular change feel. |
| Match/push rule |
genre identity |
3-in-a-row, shapes, push-into-goal, etc. |
| Cascade scoring |
reward depth |
Bigger chains = exponential payoff. |
| Move / time limit |
pressure |
Move-limited = puzzly; time = arcade. |
| Difficulty curve |
learning |
Introduce one mechanic at a time. |
| Undo depth |
forgiveness |
Single-step vs. full history. |
| Solvability guarantee |
fairness |
Generated boards must be solvable. |
| Deadlock handling |
no dead ends |
Detect no-moves; shuffle or end (refs). |
Patterns
1. Board model + match detection (logic separate from visuals)
# Pseudocode. The board is the truth; rendering reads from it. (0,0) top-left, y grows down.
board = [[piece_or_empty for _ in range(W)] for _ in range(H)]
def find_matches(board):
matched = set()
for y in range(H): # horizontal runs of >= 3 equal pieces
run = 1
for x in range(1, W):
if board[y][x] and board[y][x] == board[y][x-1]: run += 1
else:
if run >= 3: matched |= {(y, k) for k in range(x-run, x)}
run = 1
if run >= 3: matched |= {(y, k) for k in range(W-run, W)}
# ... repeat the same scan vertically (columns) ...
return matched
2. Resolve → collapse → refill → cascade (repeat to stability)
# Pseudocode. One player move can trigger a chain; loop until the board stops changing.
def resolve(board):
chain = 0
while True:
matches = find_matches(board)
if not matches: break # stable: resolution complete
chain += 1
score += score_for(matches, chain) # later chain steps score more (see refs)
clear(board, matches) # remove matched pieces
apply_gravity(board) # pieces fall into the gaps
refill(board, rng) # spawn new pieces at the top (seeded RNG)
return chain
3. Undo via state snapshot or command
# Pseudocode. Snapshot before each move; undo restores it exactly (board + score + counters).
def make_move(move):
history.append(snapshot(board, score, moves_left)) # push BEFORE applying
apply(move); resolve(board); moves_left -= 1
def undo():
if history:
board, score, moves_left = history.pop() # exact revert, including resolution
For large boards prefer the command pattern (store the move + enough to invert it) over full
snapshots to save memory; snapshots are simplest and fine for small boards.
Pitfalls / failure modes
- Mixing logic and visuals → animations desync from state and cause bugs. The board model is
the single source of truth; the view only renders it.
- Resolving only once → cascades/chains are missed. Loop resolution until the board is stable
(Pattern 2).
- Undo that doesn't restore everything → score/move-count/random-state drift. Snapshot all
state, or make the move fully invertible.
- Unseeded refill RNG → can't reproduce a level / no deterministic undo or daily puzzle. Seed it.
- Generated boards that aren't solvable → unfair dead ends. Generate-and-verify, or generate
from a known solution backward (refs).
- No deadlock detection (match-3) → board with no valid moves softlocks. Detect "no moves"
and shuffle or end the level (refs).
- Difficulty spikes → too many mechanics at once. Teach one mechanic per level before combining.
- Resolution mid-animation accepts input → double-moves/corruption. Lock input until the
board is stable.
Composition (build it from these skills)
- Board rendering:
godot-tilemap / unity-tilemap-2d for the grid; godot-ui-control for HUD, score, and menus.
- Levels:
level-design for hand-authored puzzles and difficulty pacing; procedural-gen for solvable generated boards.
- Persistence:
save-systems for level progress, high scores, and seeded daily puzzles.
- Juice:
game-feel for match/cascade pop, screen shake, and chain feedback; the engine animation/Tween skill for swaps/falls/clears; audio-design for match and chain cues.
- Scripting:
godot-gdscript / unity-csharp-scripting for the resolution loop and rules.
References
- For match-3 detection/gravity/refill/cascade detail, deadlock detection and reshuffles,
sokoban/rule-based puzzles, undo strategies, solvable generation, and scoring, read
references/board-and-resolution.md.
1---
2name: puzzle
3description: >
4 Build a puzzle game: grid/board state, move input, rule-based resolution (match-3 cascades, sokoban
5 pushes, tile logic), scoring, and undo. Use for a match-3, sokoban, or grid-logic puzzle.
6---
7
8# Puzzle
9
10A playbook for grid/board puzzle games — the board model, move input, rule resolution
11(matching, pushing, logic), scoring, undo, and level progression. This is a **compositional**
12skill: it models board state and rules and presents them through a tilemap/UI. It does not
13re-teach tilemaps; it defines the resolution loop and the correctness rules (clean state,
14deterministic resolution, undo) that keep a puzzle fair and bug-free.
15
16## When to use
17
18- Use when the game is a **discrete board** the player changes with moves, and the board
19 **resolves by rules**: match-3/tile-matching, sokoban/block-pusher, sliding puzzle, logic grid.
20- Use when designing match/cascade resolution, undo, level progression, or solvability.
21
22**When *not* to use:** real-time grid action with permadeath → `roguelike`. Card zones/turns →
23`card-game`. Physics-based "puzzle platformer" → `platformer` + `physics-tuning`. For the tile
24rendering, use `godot-tilemap` / `unity-tilemap-2d`.
25
26## Core loop
27
28**Read the board → plan a move → make the move → the board resolves by its rules (match, push,
29fall, fill, cascade) → see progress toward the objective → repeat until solved/failed.** The
30fun is the *planning*; the engine's job is to resolve each move **deterministically** and
31present it clearly.
32
33## Must-have systems
34
351. **Board model** — a grid of cells holding pieces; the single source of truth (logic, not visuals).
362. **Move input** — swap, push, drag, rotate, or place; validate legality before applying.
373. **Rule resolution** — detect and apply the genre's rule (matches, pushes, logic) until stable.
384. **Cascades/chains** — when resolution changes the board, re-resolve until no more changes.
395. **Objectives + scoring** — win/lose conditions (score, clear all, reach goal); move/time limits.
406. **Undo** — revert the last move (and its resolution) exactly; essential for thinky puzzles.
417. **Level progression + (often) generation** — hand-authored or generated **solvable** boards.
428. **Feedback ("juice")** — clear, satisfying animation/sound for matches, falls, and chains.
43
44## Design knobs
45
46| Knob | Effect | Notes |
47|------|--------|-------|
48| Grid size / shape | complexity | Square is standard; hex/irregular change feel. |
49| Match/push rule | genre identity | 3-in-a-row, shapes, push-into-goal, etc. |
50| Cascade scoring | reward depth | Bigger chains = exponential payoff. |
51| Move / time limit | pressure | Move-limited = puzzly; time = arcade. |
52| Difficulty curve | learning | Introduce one mechanic at a time. |
53| Undo depth | forgiveness | Single-step vs. full history. |
54| Solvability guarantee | fairness | Generated boards must be solvable. |
55| Deadlock handling | no dead ends | Detect no-moves; shuffle or end (refs). |
56
57## Patterns
58
59### 1. Board model + match detection (logic separate from visuals)
60
61```python
62# Pseudocode. The board is the truth; rendering reads from it. (0,0) top-left, y grows down.
63board = [[piece_or_empty for _ in range(W)] for _ in range(H)]
64
65def find_matches(board):
66 matched = set()
67 for y in range(H): # horizontal runs of >= 3 equal pieces
68 run = 1
69 for x in range(1, W):
70 if board[y][x] and board[y][x] == board[y][x-1]: run += 1
71 else:
72 if run >= 3: matched |= {(y, k) for k in range(x-run, x)}
73 run = 1
74 if run >= 3: matched |= {(y, k) for k in range(W-run, W)}
75 # ... repeat the same scan vertically (columns) ...
76 return matched
77```
78
79### 2. Resolve → collapse → refill → cascade (repeat to stability)
80
81```python
82# Pseudocode. One player move can trigger a chain; loop until the board stops changing.
83def resolve(board):
84 chain = 0
85 while True:
86 matches = find_matches(board)
87 if not matches: break # stable: resolution complete
88 chain += 1
89 score += score_for(matches, chain) # later chain steps score more (see refs)
90 clear(board, matches) # remove matched pieces
91 apply_gravity(board) # pieces fall into the gaps
92 refill(board, rng) # spawn new pieces at the top (seeded RNG)
93 return chain
94```
95
96### 3. Undo via state snapshot or command
97
98```python
99# Pseudocode. Snapshot before each move; undo restores it exactly (board + score + counters).
100def make_move(move):
101 history.append(snapshot(board, score, moves_left)) # push BEFORE applying
102 apply(move); resolve(board); moves_left -= 1
103
104def undo():
105 if history:
106 board, score, moves_left = history.pop() # exact revert, including resolution
107```
108
109For large boards prefer the **command** pattern (store the move + enough to invert it) over full
110snapshots to save memory; snapshots are simplest and fine for small boards.
111
112## Pitfalls / failure modes
113
114- **Mixing logic and visuals** → animations desync from state and cause bugs. The board model is
115 the single source of truth; the view only renders it.
116- **Resolving only once** → cascades/chains are missed. Loop resolution until the board is stable
117 (Pattern 2).
118- **Undo that doesn't restore everything** → score/move-count/random-state drift. Snapshot *all*
119 state, or make the move fully invertible.
120- **Unseeded refill RNG** → can't reproduce a level / no deterministic undo or daily puzzle. Seed it.
121- **Generated boards that aren't solvable** → unfair dead ends. Generate-and-verify, or generate
122 from a known solution backward (refs).
123- **No deadlock detection** (match-3) → board with no valid moves softlocks. Detect "no moves"
124 and shuffle or end the level (refs).
125- **Difficulty spikes** → too many mechanics at once. Teach one mechanic per level before combining.
126- **Resolution mid-animation accepts input** → double-moves/corruption. Lock input until the
127 board is stable.
128
129## Composition (build it from these skills)
130
131- **Board rendering:** `godot-tilemap` / `unity-tilemap-2d` for the grid; `godot-ui-control` for HUD, score, and menus.
132- **Levels:** `level-design` for hand-authored puzzles and difficulty pacing; `procedural-gen` for solvable generated boards.
133- **Persistence:** `save-systems` for level progress, high scores, and seeded daily puzzles.
134- **Juice:** `game-feel` for match/cascade pop, screen shake, and chain feedback; the engine animation/`Tween` skill for swaps/falls/clears; `audio-design` for match and chain cues.
135- **Scripting:** `godot-gdscript` / `unity-csharp-scripting` for the resolution loop and rules.
136
137## References
138
139- For match-3 detection/gravity/refill/cascade detail, deadlock detection and reshuffles,
140 sokoban/rule-based puzzles, undo strategies, solvable generation, and scoring, read
141 `references/board-and-resolution.md`.