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;
}