Conway's Game of Life is the most famous program that isn't really a game. There's no player, no score, no way to win. You draw a few cells on a grid, press play, and watch. What comes back is uncanny: patterns that crawl across the screen, blink forever, collide, and occasionally build machines. All of it falls out of four tiny rules that a mathematician named John Conway scribbled down in 1970. Today (Day 21 of GameFromZero) we built a real, running Game of Life in plain vanilla JavaScript. Here's exactly how it works.
Play the finished version here: https://dev48v.infy.uk/game/day21-game-of-life.html
The grid is just an array of ones and zeros
Every cell is in one of two states — alive or dead — so the whole universe is a flat array of 0s and 1s. We use 40 columns by 30 rows, which is 1,200 cells.
const COLS = 40, ROWS = 30, CELLS = COLS * ROWS;
let grid = new Uint8Array(CELLS); // 0 = dead, 1 = alive
const rowOf = i => Math.floor(i / COLS);
const colOf = i => i % COLS;
const idx = (r, c) => r * COLS + c;
We keep it flat — one array, not a grid of arrays — because scanning and copying become simple loops. When we need a cell's position we convert its index; when we need the index we convert its row and column back. That's the entire data model.
Everything depends on counting eight neighbours
Each cell has eight neighbours: up, down, left, right, and the four diagonals. The single number that drives the whole simulation is how many of those eight are currently alive. We loop over the nine offsets around a cell and skip the centre.
function neighbours(g, r, c){
let n = 0;
for (let dr = -1; dr <= 1; dr++)
for (let dc = -1; dc <= 1; dc++){
if (dr === 0 && dc === 0) continue; // skip the cell itself
const rr = (r + dr + ROWS) % ROWS; // wrap top/bottom
const cc = (c + dc + COLS) % COLS; // wrap left/right
n += g[idx(rr, cc)];
}
return n; // always 0..8
}
Notice the wrap. Adding ROWS before the % keeps the number positive, so a cell on the top edge treats the bottom edge as its neighbour. That turns the flat grid into a torus — a doughnut. A glider that walks off the right side reappears on the left. (You could instead treat the edges as permanently dead; wrapping just makes the little world feel endless.)
The four rules collapse into one line
Here's where the magic hides. Conway's four rules are almost boring on their own:
- A live cell with fewer than two live neighbours dies (underpopulation).
- A live cell with two or three live neighbours survives.
- A live cell with more than three live neighbours dies (overpopulation).
- A dead cell with exactly three live neighbours becomes alive (reproduction).
Read them again and you'll see they fold into two conditions: a live cell survives on 2 or 3, and a dead cell is born on exactly 3. Everyone writes this as B3/S23 — Born on 3, Survives on 2 or 3.
function nextState(alive, n){
if (alive) return (n === 2 || n === 3) ? 1 : 0; // survive
return (n === 3) ? 1 : 0; // birth
}
That one function is the entire law of the universe. Tweak the numbers — say B36/S23 — and you get a completely different automaton called HighLife, with patterns that replicate themselves. Conway tried dozens of variants by hand before settling on B3/S23 because it sat right on the edge between fizzling out and blowing up.
The one bug everyone hits: mutating in place
Now the crux, and the mistake nearly every beginner makes. It's tempting to walk the grid and update each cell as you go. Don't. If you kill a cell and then evaluate its neighbour, that neighbour sees a grid that's already half-changed — but the rules demand every cell decide from the same snapshot of the current generation.
The fix is double buffering. Read from the current grid, write every result into a brand-new array, and only swap it in once every cell is done.
function step(){
const next = new Uint8Array(CELLS); // a fresh buffer
for (let i = 0; i < CELLS; i++){
const n = neighbours(grid, rowOf(i), colOf(i));
next[i] = nextState(grid[i] === 1, n);
}
grid = next; // swap in — the old grid is never touched mid-scan
generation++;
render();
}
I verified this with a quick test: stamp a Glider, run four generations, and check that its five cells kept their exact shape but shifted one row down and one column right. They did — the Glider walks. A Blinker flips between horizontal and vertical and returns to itself every two steps. If you'd mutated in place, none of that would come out right; you'd get garbage that looks vaguely alive but breaks the rules.
Drawing, playing, and famous patterns
Rendering is cheap: build one small <div> per cell once, then on each frame just toggle an alive class so CSS paints it green or white. Seeding is a click-and-drag paint tool using pointer events (so it works on phones too). Playing is setInterval(step, 1000 / speed), where a speed slider of 10 means ten generations a second.
The fun part is the presets — famous patterns are just lists of live coordinates:
const PATTERNS = {
glider: [[0,1],[1,2],[2,0],[2,1],[2,2]], // a spaceship that walks diagonally
blinker: [[1,0],[1,1],[1,2]], // period-2 oscillator
// pulsar, LWSS, and the Gosper Glider Gun are bigger lists
};
Drop in a Glider and it strolls across the screen forever. A Blinker ticks. A Pulsar pulses on a three-beat. A Lightweight Spaceship slides sideways. And the Gosper Glider Gun — the pattern that won Conway's own bet — sits still and fires a fresh glider every 30 generations, a factory that never runs out.
The punchline: it's a computer
Here's the fact that keeps people up at night. The Game of Life is Turing-complete. Using glider guns as signal sources, streams of gliders as wires carrying bits, and engineered collisions as logic gates, people have built AND, OR and NOT gates, memory cells, clocks — even a working calculator — entirely out of Life patterns. In principle, anything your laptop can compute, a big enough Life board can compute too.
That's the whole thing: a grid, an eight-neighbour count, four rules, and a fresh buffer each step. From that you get gliders, guns, oscillators, and universal computation. Simple local rules, endless emergence — that's why this "game" has fascinated programmers for over fifty years.
Try it, draw your own pattern, and press play: https://dev48v.infy.uk/game/day21-game-of-life.html
Top comments (0)