Essay
Building Sand Wizard
A falling-sand game where you conjure terrain to keep a tiny wizard alive. Built from a grid of cells, a deterministic RNG, and a circular brush — no physics engine required.
12 min read
Sand Wizard is a side-scrolling survival game. A pixel-art wizard walks endlessly to the right across a procedurally generated desert. The terrain gets increasingly hostile — chasms, spike walls, boulders, falling rocks — and the player's only tool is sand. Click to conjure it, right-click to remove it. Build ramps, fill gaps, block hazards.
The entire thing runs on a 400×225 logical grid, scaled 3× to canvas pixels. There is no physics engine. Every behaviour — gravity, terrain, obstacles — is a direct manipulation of that grid. This article walks through the core systems and lets you play with each one.
Falling sand
The foundation is a cellular automaton. Each cell in the grid is one of three types: empty, sand, or rock. Every frame, the simulation scans bottom-to-top. For each sand cell, it checks: can I fall straight down? If not, can I slide diagonally? If neither, stay put.
The scan direction alternates each row (left-to-right on even rows, right-to-left on odd) to prevent sand from developing a directional bias. The diagonal check is randomised — left-first or right-first — so piles form natural slopes rather than leaning consistently.
Procedural terrain
The world generates one column at a time as the camera scrolls right. Each column's surface height is computed from the world X coordinate using layered sine waves at different frequencies. Low frequencies create gentle rolling dunes; high frequencies add jagged spikes.
Difficulty scales from 0 to 1 based on the player's score. At zero, only the gentle low-frequency terms are active. As difficulty rises, sawtooth waves and higher harmonics kick in, producing increasingly treacherous terrain. Chasms, spike walls, and ridges are layered on top using hash-seeded intervals.
Design choice
Terrain is computed from a pure function of world X and score — no stored map, no chunks. This means the world is infinite and deterministic: the same score always produces the same landscape.
Deterministic randomness
The game uses a linear congruential generator seeded at 42. Every call to rng() advances the same global state, so the sequence of "random" decisions — which way sand slides, where obstacles spawn — is identical every run. This makes high scores meaningful: everyone faces the same game.
The constants (multiplier 1664525, increment 1013904223) are the classic Numerical Recipes LCG. It's fast, tiny, and good enough for a game that needs unpredictable-looking behaviour without cryptographic strength.
The brush
Player interaction is a circular brush that writes cells into the grid. Left-click sets empty cells to sand; right-click clears sand cells back to empty. Rock is immutable — you can't paint over it or erase it.
Sand is a finite resource that regenerates slowly. The regen rate decreases as the tank empties, creating pressure: spend sand freely early and you'll run dry when you need it most. Power-ups restore sand in bursts, rewarding exploration.
Scrolling and the player
The camera scrolls at a constant rate (1.2 pixels per frame). The grid shifts left by integer columns, and new columns are generated on the right edge. The wizard walks on top of whatever surface exists beneath them — sand or rock — using a simple ground-detection scan.
Small bumps (≤5 cells) are auto-climbed. Taller walls push the wizard left. If the wizard is pushed off the left edge of the screen, they die. This creates the core tension: the world scrolls relentlessly, and you must keep the path clear.
Parallax depth
The background is built from five layers that scroll at different fractions of the camera speed. Distant rock spires move at just 4% of the camera rate. Three dune layers sit between them at 10%, 30%, and 60%. The foreground terrain scrolls at full speed.
Each layer is a sine-wave silhouette rendered directly into the ImageData buffer before the grid is painted on top. The sky is a vertical gradient that transitions from a night palette (deep indigo) to a day palette (burnt orange) as the score increases, giving the game a sense of time passing.
Decorative elements ride on specific layers: palm trees on the middle dunes (×0.3), camel silhouettes on the far dunes (×0.1), and circling bird flocks that orbit fixed points in the sky. A Majora's Mask-style moon/sun face watches from the upper right, its expression shifting from neutral to menacing as more obstacles appear on screen.
Why parallax matters
Without parallax, the desert looks flat — a texture sliding left. With it, the player perceives depth, distance, and atmosphere from nothing but offset multipliers on a 2D grid. It's the cheapest possible 3D illusion.
Enemies and contact death
Eight obstacle types populate the desert, each with distinct behaviour. Boulders roll left at constant velocity, ploughing through any sand in their path. Falling rocks drop from above with a ground-shadow telegraph, then embed themselves as permanent rock cells on impact. Dust devils wander horizontally, scattering any sand they touch. Scorpions and snakes sit on the ground, animated but stationary. Cacti scale in size with difficulty. Cave gates descend from above with a narrow gap the wizard must pass through. Rock arches are baked directly into the grid as impassable terrain.
Collision detection is axis-aligned bounding box (AABB). The wizard's hitbox is 4 pixels wide and 14 pixels tall. Each obstacle has its own bounding box defined by width and height. Every frame, the game checks whether these rectangles overlap:
player.x < obstacle.x + width && player.x + 4 > obstacle.x && player.top < obstacle.y + height && player.y > obstacle.y
If they overlap, the wizard dies instantly — unless a shield power-up is active, in which case the shield absorbs the hit and deactivates. There is no health bar, no knockback, no invincibility frames. One touch and you're dead. This keeps the stakes high and makes the sand-placement mechanic feel urgent.
Near-misses and spawn pacing
When the wizard passes within 12 pixels of a boulder or falling rock without actually colliding, the game awards 50 bonus points and plays a chime. A 30-frame cooldown prevents the same obstacle from triggering multiple near-miss rewards. This encourages risky play — building minimal sand bridges rather than burying every hazard.
Obstacle spawning is paced by a rolling interval that tightens with difficulty. At low difficulty, spawns are 150–400 pixels apart and limited to pits, falling rocks, cacti, scorpions, and snakes. As difficulty rises, boulders, rock arches, cave gates, and dust devils enter the pool, and multiple obstacles can spawn simultaneously. Power-ups appear every 500–700 pixels, offering sand refills, shields, sand bursts, slow-time, or full tank restores.
Putting it together
Audio is entirely synthesised from the Web Audio API — no sample files. A Hijaz-scale melody plays over a sub-bass drone, with sparse tabla-like percussion and occasional wind. Sound effects are single oscillator bursts.
The renderer writes directly to an ImageData buffer, painting each grid cell as a 3×3 block of canvas pixels. Sprites (wizard, vultures, scorpions) are drawn from character-map bitmaps with palette lookups — each sprite is a 2D array of single characters mapped to hex colours. The whole frame is a single putImageData call for the background and grid, then fillRect overlays for obstacles, power-ups, particles, and screen shake.