Developer notes · web game engineering

Rebuilding Classic 2D Grid Mechanics in Vanilla JavaScript without Framework Overhead

Grid games (2048, Sudoku, match-3) are discrete-state systems. This case study shows why vanilla DOM state machines beat frameworks and canvas for them, with the architecture patterns from Mind Bender Puzzles and our own catalog as evidence.

The state model decides the architecture

Grid games are finite-state systems: a small state object, a transition function per input, and a render that projects state to DOM. When state is discrete and updates are local (one cell, one row), a framework's virtual DOM adds reconciliation cost without benefit — the developer already knows exactly which cells changed. Vanilla JS with targeted updates touches only changed nodes, keeps memory flat, and eliminates framework load time entirely.

The three patterns that matter

Pattern one: the single store. One state object per game, mutated only through named transition functions — this makes undo/redo trivial (snapshot the store) and mid-puzzle persistence free (serialize the store). Pattern two: targeted rendering. A render function per state region, called only when that region's slice changes. Pattern three: CSS for animation. Grid movement animates via transform transitions; the state machine never waits for animation — it moves state immediately and lets CSS catch up visually.

Where canvas would be wrong

Canvas earns its complexity when per-frame rendering is the game (physics, particles, continuous motion). Grid games have no per-frame rendering: state changes are events, not frames. Implementing a 2048 in canvas means building a scene graph to do what DOM does natively — hit testing, accessibility, text layout. The catalog's Mind Bender Puzzles and our own 2048 implementation demonstrate the DOM pattern at production quality.

When to graduate to canvas

The crossover signal is continuous animation as a core mechanic: physics arcs, particle systems, scrolling worlds. At that point DOM per-frame updates thrash layout, and canvas (or WebGL) becomes correct. The decision rule: discrete state = DOM, continuous state = canvas. Games that mix both (hub UI + canvas events) should split the runtime — the hybrid architecture in Ultimate Sports Arena is the reference implementation.

Measuring the claim

The practical metrics: time-to-interactive (vanilla wins — no framework bootstrap), memory over long sessions (vanilla wins — no reconciliation artifacts), and development speed for solo authors (vanilla wins for grid games — less abstraction to fight). The counter-metric is team scale: beyond a few contributors, framework conventions pay for coordination. Solo-authored browser games sit firmly on the vanilla side of that line.

Persistence and the state snapshot

The same single-store pattern makes persistence free: serializing the state object to localStorage on every transition (throttled) means mid-puzzle recovery, undo stacks, and cross-session continuity at near-zero engineering cost. Mind Bender Puzzles restores exact mid-puzzle state including undo history — possible because the store is one serializable object, not a framework component tree. When persistence is designed as state serialization rather than event logging, save bugs disappear: there is nothing to replay and nothing to reconcile.

Testing the state machine

A vanilla state machine is directly unit-testable: call the transition function with input, assert the output state. No DOM, no framework test harness, no snapshot serializers. The catalog’s puzzle games ship their transition logic as pure functions for exactly this reason — every puzzle type’s rule set is an importable function, tested against a table of input/state pairs. The testing pattern is itself a design constraint that keeps game logic out of the render layer, which is the discipline that makes targeted rendering reliable in the first place.

All developer notes · Review portal · About the engineering desk