Conway's Game of Life

Project Overview

Invented by British mathematician John Horton Conway in 1970, the Game of Life is a zero-player game, meaning its evolution is determined entirely by its initial state, requiring no further input.

The Rules of Life

The universe of the Game of Life is an infinite, two-dimensional grid of cells, which are either alive or dead. Every cell interacts with its eight neighbors based on four simple rules:

  • Underpopulation: A live cell with fewer than 2 live neighbors dies.
  • Survival: A live cell with 2 or 3 live neighbors lives on to the next generation.
  • Overpopulation: A live cell with more than 3 live neighbors dies.
  • Reproduction: A dead cell with exactly 3 live neighbors becomes a live cell.

This modern, zero-dependency implementation utilizes a double-buffered spatial grid, high-performance edge-wrapping toroidal calculations, and native HTML5 Canvas rendering to achieve blazing-fast simulation speeds.

simulation.js

// --- Core Game of Life Logic ---
// Double-buffered, toroidal (edge-wrapping) algorithm

function countNeighbors(grid, x, y) {
  let sum = 0;
  // Read directly from the DOM to prevent state desync bugs
  const wrapEdges = document.getElementById('toroidal-checkbox').checked;

  for (let i = -1; i < 2; i++) {
    for (let j = -1; j < 2; j++) {
      if (i === 0 && j === 0) continue; // Skip the center cell

      if (wrapEdges) {
        // Toroidal wrapping using the modulo operator
        let col = (x + i + cols) % cols; 
        let row = (y + j + rows) % rows;
        sum += grid[col][row];
      } else {
        // Hard boundaries (cells off-screen are dead)
        let col = x + i;
        let row = y + j;
        if (col >= 0 && col < cols && row >= 0 && row < rows) {
          sum += grid[col][row];
        }
      }
    }
  }
  return sum;
}

function computeNextGeneration() {
  // Create a blank grid for the next frame (Double Buffering)
  let next = new Array(cols).fill(null).map(() => new Array(rows).fill(0));

  for (let i = 0; i < cols; i++) {
    for (let j = 0; j < rows; j++) {
      let state = grid[i][j];
      let neighbors = countNeighbors(grid, i, j);

      // Conway's Rules of Life
      if (state === 0 && neighbors === 3) {
        next[i][j] = 1; // Birth
      } else if (state === 1 && (neighbors < 2 || neighbors > 3)) {
        next[i][j] = 0; // Death by under/overpopulation
      } else {
        next[i][j] = state; // Survival
      }
    }
  }
  
  // Swap the buffers
  grid = next; 
}