Code

Canvas

Conway's Game of Life

Advanced Classic cellular automaton — toroidal grid with typed arrays for efficient neighbour counting. Wikipedia

Generation: 0
// Flat typed array — fast memory layout
const grid = new Uint8Array(cols * rows);
const next = new Uint8Array(cols * rows);

// Toroidal index (wraps around edges)
const idx = (x: number, y: number) =>
  ((y + rows) % rows) * cols +
  ((x + cols) % cols);

// Conway's rules:
// Alive cell: survives with 2 or 3 neighbours
// Dead cell:  born with exactly 3 neighbours
const step = () => {
  for (let y = 0; y < rows; y++) {
    for (let x = 0; x < cols; x++) {
      let n = 0;
      for (let dy = -1; dy <= 1; dy++)
        for (let dx = -1; dx <= 1; dx++)
          if (dx || dy) n += grid[idx(x+dx, y+dy)];

      const alive = grid[idx(x, y)];
      next[idx(x, y)] = alive
        ? (n === 2 || n === 3 ? 1 : 0)
        : (n === 3 ? 1 : 0);
    }
  }
  grid.set(next); // swap buffers
};

// Draw
const draw = () => {
  ctx.fillStyle = '#0f172a';
  ctx.fillRect(0, 0, W, H);
  for (let y = 0; y < rows; y++) {
    for (let x = 0; x < cols; x++) {
      if (grid[idx(x, y)]) {
        ctx.fillStyle = '#38bdf8';
        ctx.fillRect(x*cs+1, y*cs+1, cs-1, cs-1);
      }
    }
  }
};
Key Concepts
  • Uint8Array — 1 byte per cell, cache-friendly flat layout vs. nested arrays
  • Double buffer: compute into next, then grid.set(next) — never read and write the same array
  • Toroidal wrap: (x + cols) % cols makes the grid wrap at edges
  • Count all 8 neighbours with a nested dx/dy loop, skip dx===0 && dy===0
  • Use setInterval (not rAF) for a fixed generation rate independent of frame rate
  • Interesting patterns: Glider, R-pentomino, Gosper Glider Gun