Frontend & Web

Game of Life: Uint8Array & Canvas Tutorial

Imagine firing up a timeless cellular automaton that runs buttery smooth in your browser, no frameworks needed. This fresh take on Conway's Game of Life uses Uint8Arrays to make grids fly, letting hobbyists and pros alike tinker with gliders that loop forever.

Animated Gosper Glider Gun firing gliders in Conway's Game of Life on HTML canvas with Uint8Array grid

Key Takeaways

  • Uint8Arrays deliver GC-friendly, cache-hot grids for smooth Canvas rendering.
  • Toroidal wrapping via double-modulo handles JS % quirks perfectly.
  • RAF loops + presets like Gosper Gun make pro-level Life demos effortless.

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.

🔗 Live demo
📦 GitHub

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

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.

Aisha Patel
Written by

Former ML engineer turned writer. Covers computer vision and robotics with a practitioner perspective.

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.

Worth sharing?

Get the best Developer Tools stories of the week in your inbox — no noise, no spam.

Originally reported by dev.to

Stay in the loop

The week's most important stories from DevTools Feed, delivered once a week.