Your next weekend project just got a serious upgrade. Devs staring at sluggish canvas experiments? This Conway’s Game of Life implementation — lean, mean, Uint8Array-powered — hands you silky performance without a single dependency. Real people win: hobbyists spawn Gosper Glider Guns in seconds, educators demo emergence without lag, and frontend folks rediscover why canvas still rules for compute-heavy toys.
And here’s the kicker — it bundles eight presets. Click. Boom. Gliders gunning across a toroidal grid. No more tedious cell-poking.
Why Rewrite Game of Life When You’ve Done It Before?
Everyone’s coded Life once. Twice, even. But this one’s different — it’s the version you wish you’d built first. Author Sen Ltd didn’t slap together a toy; they chased architectural elegance. Typed arrays for the grid? Toroidal wrapping? RAF-driven loops that laugh at tab-throttling?
It’s educational gold. Beginners grasp neighbor counts in a double loop. Pros nod at the GC-friendly snapshotting: next.set(current) clones a grid instantly. Cache misses? Non-issue, rows hug contiguous memory.
But dig deeper. This isn’t just code — it’s a quiet rebellion against bloat. In an age of megabyte frameworks, here’s vanilla JS shipping a full UI: controls, counters, play/pause/step/reset. Zero build step. Feels like 2010 web dev, but smarter.
Conway’s rules fit in three lines. Writing them and watching a glider chase itself across the grid — still a what have I just witnessed moment every single time.
That quote? Pure joy. Every run.
Small grids claustrophobic? Nah. Toroidal wrap turns ‘em infinite — gliders exit right, pop left. The index math’s a JS gotcha minefield, though.
How Does Toroidal Wrapping Actually Work?
Picture this: glider at edge. x=79 on 80-wide grid. Naive? Poof, gone. Smart? ((x % w) + w) % w.
Why the double modulo? JS’s % clings to signs — -1 % 80 = -1. Add w, modulo again: wraps negative coords clean. Boom, math modulo, not remainder.
In code:
export function index(grid, x, y) {
const w = grid.width
const h = grid.height
// Toroidal wrap. Double-modulo to handle negatives correctly.
const xx = ((x % w) + w) % w
const yy = ((y % h) + h) % h
return yy * w + xx
}
Elegant. Grids feel boundless. 80x50 playground? Plenty for chaos.
Now, the heart: stepping generations. Double loop, count neighbors, apply rules. But — critical — new Uint8Array each tick. No in-place mutations.
Why? Simultaneity. Update early cells, they poison later neighbor tallies. Disaster. Fresh array? Pure prior-state gospel. Bonus: stack ‘em for undo. Trivial history.
Rules? Three lines:
const alive = cells[i] === 1
if (alive && (n === 2 || n === 3)) next[i] = 1
else if (!alive && n === 3) next[i] = 1
Birth, survival. Dead simple. Dead precise.
Uint8Array vs. The Alternatives: A Speed Showdown
Three grid flavors: array-of-arrays (meh, GC nightmare), flat Uint8 (winner), bit-packed Uint32 (fastest, ugliest math).
Uint8 shines: one allocation. cells[y * w + x]. Readable. 80x50? 4KB. Bit-packing shrinks to 500B, but cells[i >> 5] |= 1 << (i & 31)? Readability killer. Educational? Pass.
Typed arrays give you: GC-friendly memory: one allocation instead of h+1 arrays of references - Fast snapshotting: next.set(current) copies an entire grid in one call - Cache locality: rows sit contiguous in memory
Nailed it. For web toys, perfection.
Presets seal the deal. Glider, Blinker, Toad — up to Gosper Glider Gun. Bill Gosper’s 1970 brainchild solved a $50 bounty: finite pattern, infinite growth? Yes — spits gliders every 30 ticks. Forever.
Stored sparse: [[0,4],[0,5],...] — 36 coords. stamp(grid, pattern, ox, oy) plops ‘em. Sparse or dense? Same path. Genius.
Why Does RAF Beat setInterval for Game Loops?
setInterval(step, 1000/N)? Fine… until tab backgrounds. Throttles. Janky.
RAF to rescue. Timestamp diffs drive steps:
if (now - lastStepAt >= stepMs) {
grid = step(grid)
render()
lastStepAt = now
}
60 steps/sec? Frame-perfect. 1/sec? Polls smooth, no drift. Pros.
UI? Canvas center, panel below. 1-60 speed slider, gen/live counts, edit clicks. Polish.
My hot take? This echoes early hacker ethos — Gosper at MIT ’70s, punching cards for automata. Today? Web reclaims that spark. Amid AI hype, Life reminds: simple rules birth complexity. No neurons needed. Prediction: WebGPU ports this to shaders soon, gliders at 1000fps. Frontend compute’s dawn.
But hype check — not reinventing wheels. It’s a masterclass in doing basics right. Skeptical? Fork it. Tweak. See.
🧬 Related Insights
- Read more: AI’s Forgotten Logic: Will Step-by-Step Reasoning Finally Dethrone Pattern-Matching Hype?
- Read more: From One LLM Call to Chaos: When You Truly Need an AI Gateway
Frequently Asked Questions
How do I implement Conway’s Game of Life with Uint8Array?
Grab the GitHub repo. Core: createGrid, index (toroidal), step (new array), RAF loop. Stamp presets for fun.
What’s the best grid for Canvas Game of Life?
Uint8Array flat — balances speed, size, readability. Skip bit-packing unless grids hit millions cells.
Why use toroidal wrapping in Game of Life?
Keeps patterns alive forever. Gliders loop edges. Fixes finite-grid death.