Cellular Automata: How to Create Emergent Art From Simple Rules With Code
A grid of cells. Each cell is either alive or dead. A handful of rules — usually three or four — determine what happens next. Press play, and complexity erupts from simplicity. Galaxies form. Gliders march across the screen. Entire ecosystems evolve. This is the magic of cellular automata: the most visually stunning things in generative art often emerge from the fewest lines of code.
Cellular automata sit at the intersection of mathematics, computer science, and art. They were formalized by John von Neumann in the 1940s, popularized by John Conway's Game of Life in 1970, and explored exhaustively by Stephen Wolfram in the 1980s. Today they're everywhere — in generative art, game design, texture synthesis, and even theoretical biology.
This guide covers eight working implementations you can run in your browser, from the iconic Game of Life to lesser-known systems like Langton's Ant and Brian's Brain. Every example uses vanilla JavaScript and HTML Canvas. No libraries, no frameworks — just the raw elegance of simple rules creating infinite complexity.
1. Conway's Game of Life — the original
Conway's Game of Life is the most famous cellular automaton. It's a 2D grid where each cell is alive or dead. Every tick, four rules decide the next state:
- Underpopulation: A live cell with fewer than 2 neighbors dies.
- Survival: A live cell with 2 or 3 neighbors lives on.
- Overpopulation: A live cell with more than 3 neighbors dies.
- Reproduction: A dead cell with exactly 3 neighbors becomes alive.
That's it. Four rules. From these emerge gliders, oscillators, spaceships, and even structures that can compute anything a Turing machine can.
const W = 200, H = 120, S = 4;
const canvas = document.createElement('canvas');
canvas.width = W * S; canvas.height = H * S;
canvas.style.cssText = 'background:#111;display:block;margin:20px auto';
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
let grid = Array.from({length: H}, () =>
Array.from({length: W}, () => Math.random() < 0.3 ? 1 : 0)
);
function countNeighbors(g, x, y) {
let count = 0;
for (let dy = -1; dy <= 1; dy++)
for (let dx = -1; dx <= 1; dx++) {
if (dx === 0 && dy === 0) continue;
const nx = (x + dx + W) % W, ny = (y + dy + H) % H;
count += g[ny][nx];
}
return count;
}
function step() {
const next = grid.map(row => [...row]);
for (let y = 0; y < H; y++)
for (let x = 0; x < W; x++) {
const n = countNeighbors(grid, x, y);
if (grid[y][x]) next[y][x] = (n === 2 || n === 3) ? 1 : 0;
else next[y][x] = (n === 3) ? 1 : 0;
}
grid = next;
}
function draw() {
ctx.fillStyle = '#111';
ctx.fillRect(0, 0, W * S, H * S);
ctx.fillStyle = '#4fc3f7';
for (let y = 0; y < H; y++)
for (let x = 0; x < W; x++)
if (grid[y][x]) ctx.fillRect(x * S, y * S, S - 1, S - 1);
step();
requestAnimationFrame(draw);
}
draw();
The toroidal wrapping ((x + dx + W) % W) means the grid has no edges — gliders that fly off the right side reappear on the left. This prevents edge effects and makes the simulation feel infinite.
2. Elementary cellular automata — Wolfram's 256 rules
Before Conway's 2D grid, there were 1D cellular automata. A row of cells, each either on or off. The next row is determined by each cell's current state and its two neighbors — three binary inputs, so 8 possible patterns, each mapping to 0 or 1. That's 2^8 = 256 possible rules, numbered 0-255.
Stephen Wolfram catalogued all of them. Rule 30 produces chaos from a single seed cell. Rule 110 is proven Turing-complete. Rule 90 generates Sierpinski triangles. Each rule number, when written in binary, directly encodes the output for each of the 8 input patterns.
const W = 800, H = 400, S = 2;
const canvas = document.createElement('canvas');
canvas.width = W; canvas.height = H;
canvas.style.cssText = 'background:#111;display:block;margin:20px auto';
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
const RULE = 30; // Try 90, 110, 150, 184
const ruleBits = Array.from({length: 8}, (_, i) => (RULE >> i) & 1);
let row = new Uint8Array(W / S);
row[Math.floor(row.length / 2)] = 1; // single seed
ctx.fillStyle = '#111';
ctx.fillRect(0, 0, W, H);
for (let y = 0; y < H / S; y++) {
// Draw current row
for (let x = 0; x < row.length; x++) {
if (row[x]) {
const hue = (y * 0.8) % 360;
ctx.fillStyle = `hsl(${hue}, 70%, 55%)`;
ctx.fillRect(x * S, y * S, S, S);
}
}
// Compute next row
const next = new Uint8Array(row.length);
for (let x = 0; x < row.length; x++) {
const left = row[(x - 1 + row.length) % row.length];
const center = row[x];
const right = row[(x + 1) % row.length];
const index = (left << 2) | (center << 1) | right;
next[x] = ruleBits[index];
}
row = next;
}
Rule 30 is particularly fascinating — it was used as the random number generator in Mathematica for years. From a single black cell, it produces apparently random, non-repeating patterns. The left side shows nested triangular structures; the right side is chaotic. This asymmetry from a symmetric rule is one of the great surprises of cellular automata theory.
3. Brian's Brain — the three-state automaton
Brian's Brain adds a third state: "dying." Live cells don't just die — they flash bright for one frame before going dark. This creates a sparkly, frenetic animation where patterns constantly race across the grid like neural fireworks.
- Dead cells with exactly 2 live neighbors become alive.
- Alive cells become dying.
- Dying cells become dead.
const W = 160, H = 100, S = 5;
const canvas = document.createElement('canvas');
canvas.width = W * S; canvas.height = H * S;
canvas.style.cssText = 'background:#0a0a1a;display:block;margin:20px auto';
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
// 0=dead, 1=alive, 2=dying
let grid = Array.from({length: H}, () =>
Array.from({length: W}, () => Math.random() < 0.15 ? 1 : 0)
);
const colors = ['#0a0a1a', '#ff6b6b', '#2a2a4a'];
function step() {
const next = grid.map(r => [...r]);
for (let y = 0; y < H; y++)
for (let x = 0; x < W; x++) {
if (grid[y][x] === 1) { next[y][x] = 2; continue; }
if (grid[y][x] === 2) { next[y][x] = 0; continue; }
// Dead cell: count live neighbors
let live = 0;
for (let dy = -1; dy <= 1; dy++)
for (let dx = -1; dx <= 1; dx++) {
if (!dx && !dy) continue;
const nx = (x+dx+W)%W, ny = (y+dy+H)%H;
if (grid[ny][nx] === 1) live++;
}
if (live === 2) next[y][x] = 1;
}
grid = next;
}
function draw() {
ctx.fillStyle = '#0a0a1a';
ctx.fillRect(0, 0, W*S, H*S);
for (let y = 0; y < H; y++)
for (let x = 0; x < W; x++) {
if (grid[y][x]) {
ctx.fillStyle = colors[grid[y][x]];
ctx.fillRect(x*S, y*S, S-1, S-1);
}
}
step();
requestAnimationFrame(draw);
}
draw();
Brian's Brain naturally produces "spaceships" — stable moving patterns that glide across the grid. The dying state creates ghostly trails behind every moving structure, making it one of the most visually appealing cellular automata for generative art.
4. Langton's Ant — order from chaos
Langton's Ant is a 2D Turing machine that operates on a grid of black and white cells. The ant follows two rules:
- On a white cell: turn 90° right, flip the cell to black, move forward.
- On a black cell: turn 90° left, flip the cell to white, move forward.
For the first ~10,000 steps, the ant creates chaotic-looking patterns. Then something remarkable happens: it begins building a diagonal "highway" — a repeating pattern extending infinitely. This emergent order from chaos is proven to always occur, regardless of initial conditions.
const W = 300, H = 300;
const canvas = document.createElement('canvas');
canvas.width = W; canvas.height = H;
canvas.style.cssText = 'background:#111;display:block;margin:20px auto;image-rendering:pixelated';
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
const img = ctx.createImageData(W, H);
const grid = new Uint8Array(W * H);
let ax = W >> 1, ay = H >> 1, dir = 0; // 0=up,1=right,2=down,3=left
const dx = [0,1,0,-1], dy = [-1,0,1,0];
let steps = 0;
function tick(count) {
for (let i = 0; i < count; i++) {
const idx = ay * W + ax;
if (grid[idx] === 0) {
dir = (dir + 1) & 3; // turn right
grid[idx] = 1;
} else {
dir = (dir + 3) & 3; // turn left
grid[idx] = 0;
}
ax = (ax + dx[dir] + W) % W;
ay = (ay + dy[dir] + H) % H;
steps++;
}
}
function draw() {
tick(50); // 50 steps per frame
for (let i = 0; i < W * H; i++) {
const p = i * 4;
if (i === ay * W + ax) {
img.data[p] = 255; img.data[p+1] = 100; img.data[p+2] = 100;
} else if (grid[i]) {
const hue = (steps * 0.01 + i * 0.001) % 1;
const r = Math.sin(hue * 6.28) * 80 + 120;
const g = Math.sin(hue * 6.28 + 2.09) * 80 + 120;
const b = Math.sin(hue * 6.28 + 4.19) * 80 + 120;
img.data[p] = r; img.data[p+1] = g; img.data[p+2] = b;
} else {
img.data[p] = 17; img.data[p+1] = 17; img.data[p+2] = 17;
}
img.data[p+3] = 255;
}
ctx.putImageData(img, 0, 0);
requestAnimationFrame(draw);
}
draw();
Watch it long enough and you'll see the transition happen: roughly 10,000 steps of apparent randomness, then suddenly a perfectly regular diagonal highway emerges. No one has been able to prove why this transition must happen, but it has never failed to occur in any starting configuration tested.
5. Wireworld — electricity in a grid
Wireworld is a cellular automaton designed to simulate electronic circuits. It has four states: empty, electron head, electron tail, and conductor. The rules are simple:
- Empty stays empty.
- Electron head becomes electron tail.
- Electron tail becomes conductor.
- Conductor becomes electron head if exactly 1 or 2 neighbors are electron heads.
const W = 120, H = 80, S = 6;
const canvas = document.createElement('canvas');
canvas.width = W * S; canvas.height = H * S;
canvas.style.cssText = 'background:#0a0a0a;display:block;margin:20px auto';
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
// 0=empty, 1=head, 2=tail, 3=conductor
let grid = Array.from({length: H}, () => new Uint8Array(W));
const colors = ['#0a0a0a', '#4fc3f7', '#ff5722', '#ffa726'];
// Create circuit loops
for (let i = 0; i < 5; i++) {
const cx = 20 + i * 20, cy = 40;
const r = 8 + i * 2;
for (let a = 0; a < 40; a++) {
const t = a / 40 * Math.PI * 2;
const x = Math.round(cx + Math.cos(t) * r);
const y = Math.round(cy + Math.sin(t) * r * 0.6);
if (x >= 0 && x < W && y >= 0 && y < H) grid[y][x] = 3;
}
// Inject electrons
grid[cy][cx + r] = 1;
grid[cy][(cx + r - 1 + W) % W] = 2;
}
// Connect loops with horizontal wires
for (let i = 0; i < 4; i++) {
const x1 = 20 + i * 20 + 10 + i * 2, x2 = 20 + (i+1) * 20 - 10 - (i+1) * 2;
for (let x = x1; x <= x2; x++) grid[40][x] = 3;
}
function step() {
const next = grid.map(r => new Uint8Array(r));
for (let y = 0; y < H; y++)
for (let x = 0; x < W; x++) {
const c = grid[y][x];
if (c === 0) continue;
if (c === 1) { next[y][x] = 2; continue; }
if (c === 2) { next[y][x] = 3; continue; }
// Conductor: count head neighbors
let heads = 0;
for (let dy = -1; dy <= 1; dy++)
for (let dx = -1; dx <= 1; dx++) {
if (!dx && !dy) continue;
const nx = (x+dx+W)%W, ny = (y+dy+H)%H;
if (grid[ny][nx] === 1) heads++;
}
next[y][x] = (heads === 1 || heads === 2) ? 1 : 3;
}
grid = next;
}
function draw() {
ctx.fillStyle = '#0a0a0a';
ctx.fillRect(0, 0, W*S, H*S);
for (let y = 0; y < H; y++)
for (let x = 0; x < W; x++) {
if (grid[y][x]) {
ctx.fillStyle = colors[grid[y][x]];
ctx.fillRect(x*S, y*S, S-1, S-1);
}
}
step();
requestAnimationFrame(draw);
}
draw();
Wireworld is Turing-complete — people have built working logic gates, clocks, and even entire processors within it. The visual appeal comes from watching electrons pulse through wire networks, creating rhythmic patterns that look like a neon circuit board.
6. Cyclic cellular automaton — rainbow spirals
In a cyclic automaton, each cell holds a state from 0 to N-1. A cell advances to the next state only if at least one neighbor is already in that next state. The result: spontaneous spiral formation. Rotating rainbow pinwheels emerge from random noise.
const W = 200, H = 150, S = 4, STATES = 16;
const canvas = document.createElement('canvas');
canvas.width = W * S; canvas.height = H * S;
canvas.style.cssText = 'background:#111;display:block;margin:20px auto';
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
let grid = Array.from({length: H}, () =>
Array.from({length: W}, () => Math.floor(Math.random() * STATES))
);
function step() {
const next = grid.map(r => [...r]);
for (let y = 0; y < H; y++)
for (let x = 0; x < W; x++) {
const target = (grid[y][x] + 1) % STATES;
let found = false;
for (let dy = -1; dy <= 1 && !found; dy++)
for (let dx = -1; dx <= 1 && !found; dx++) {
if (!dx && !dy) continue;
const nx = (x+dx+W)%W, ny = (y+dy+H)%H;
if (grid[ny][nx] === target) found = true;
}
if (found) next[y][x] = target;
}
grid = next;
}
function draw() {
ctx.fillStyle = '#111';
ctx.fillRect(0, 0, W*S, H*S);
for (let y = 0; y < H; y++)
for (let x = 0; x < W; x++) {
const hue = (grid[y][x] / STATES) * 360;
ctx.fillStyle = `hsl(${hue}, 75%, 50%)`;
ctx.fillRect(x*S, y*S, S, S);
}
step();
requestAnimationFrame(draw);
}
draw();
The spiral formation is completely spontaneous — no spiral shape is encoded anywhere in the rules. It emerges from the interaction of cycling states competing for territory. Increase STATES for tighter spirals, decrease it for chunkier patterns. At STATES=3, you get something resembling rock-paper-scissors dynamics.
7. Smooth Life — continuous Game of Life
Smooth Life generalizes Conway's Game of Life to continuous space and continuous states. Instead of counting integer neighbors on a grid, it uses circular neighborhoods and smooth transition functions. The result looks like living organisms — blobs that divide, merge, pulsate, and swim through a fluid medium.
const W = 200, H = 150, S = 4;
const canvas = document.createElement('canvas');
canvas.width = W * S; canvas.height = H * S;
canvas.style.cssText = 'background:#0a1628;display:block;margin:20px auto';
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
let grid = Array.from({length: H}, () =>
new Float32Array(W)
);
// Seed with random blobs
for (let i = 0; i < 30; i++) {
const cx = Math.random()*W|0, cy = Math.random()*H|0, r = 3+Math.random()*5;
for (let y = -r; y <= r; y++)
for (let x = -r; x <= r; x++)
if (x*x+y*y < r*r) {
const gx = (cx+x+W)%W|0, gy = (cy+y+H)%H|0;
grid[gy][gx] = Math.random() * 0.8 + 0.2;
}
}
const ri = 7, ra = 21; // inner/outer radius
function sigma(x, a, alpha) {
return 1 / (1 + Math.exp(-(x - a) * 4 / alpha));
}
function sigmaN(x, a, b) {
return sigma(x, a, 0.028) * (1 - sigma(x, b, 0.028));
}
function sigmaM(x, y, m) {
return x * (1 - sigma(m, 0.5, 0.028)) + y * sigma(m, 0.5, 0.028);
}
function step() {
const next = grid.map(r => new Float32Array(r.length));
for (let y = 0; y < H; y++)
for (let x = 0; x < W; x++) {
let sumI = 0, countI = 0, sumO = 0, countO = 0;
for (let dy = -ra; dy <= ra; dy++)
for (let dx = -ra; dx <= ra; dx++) {
const d = Math.sqrt(dx*dx + dy*dy);
if (d > ra) continue;
const val = grid[(y+dy+H)%H][(x+dx+W)%W];
if (d <= ri) { sumI += val; countI++; }
else { sumO += val; countO++; }
}
const m = countI > 0 ? sumI / countI : 0;
const n = countO > 0 ? sumO / countO : 0;
const s = sigmaM(sigmaN(n, 0.278, 0.365), sigmaN(n, 0.267, 0.445), m);
next[y][x] = Math.max(0, Math.min(1, grid[y][x] + 0.1 * (2 * s - 1)));
}
grid = next;
}
function draw() {
const imgData = ctx.createImageData(W*S, H*S);
for (let y = 0; y < H; y++)
for (let x = 0; x < W; x++) {
const v = grid[y][x];
const r = v * 60, g = v * 180 + 20, b = v * 120 + 40;
for (let sy = 0; sy < S; sy++)
for (let sx = 0; sx < S; sx++) {
const p = ((y*S+sy)*W*S + x*S+sx) * 4;
imgData.data[p] = r; imgData.data[p+1] = g;
imgData.data[p+2] = b; imgData.data[p+3] = 255;
}
}
ctx.putImageData(imgData, 0, 0);
step();
requestAnimationFrame(draw);
}
draw();
SmoothLife was invented by Stephan Rafler in 2011 and it's one of the most beautiful cellular automata ever discovered. The continuous states create organic, biomorphic forms that look genuinely alive. It's computationally expensive — the nested radius loops make it O(n² r²) — but the visual results justify it.
8. Multiple neighborhoods — generative texture synthesis
By using different neighborhood shapes and combining multiple rules, cellular automata can generate textures that look like animal skins, coral, fabric weaves, or alien landscapes. This final example uses a weighted neighborhood with asymmetric rules to create organic, ever-shifting patterns.
const W = 200, H = 150, S = 4;
const canvas = document.createElement('canvas');
canvas.width = W * S; canvas.height = H * S;
canvas.style.cssText = 'background:#111;display:block;margin:20px auto';
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
let grid = Array.from({length: H}, () =>
new Float32Array(W).map(() => Math.random())
);
// Kernel weights: inner ring vs outer ring
const kernel = [];
for (let dy = -3; dy <= 3; dy++)
for (let dx = -3; dx <= 3; dx++) {
const d = Math.sqrt(dx*dx + dy*dy);
if (d > 0 && d <= 3.5)
kernel.push({dx, dy, w: d <= 1.5 ? 1.0 : -0.5});
}
function step() {
const next = grid.map(r => new Float32Array(r.length));
for (let y = 0; y < H; y++)
for (let x = 0; x < W; x++) {
let sum = 0;
for (const k of kernel) {
const nx = (x+k.dx+W)%W, ny = (y+k.dy+H)%H;
sum += grid[ny][nx] * k.w;
}
const activation = 1 / (1 + Math.exp(-10 * (sum / kernel.length - 0.15)));
next[y][x] = grid[y][x] * 0.8 + activation * 0.2;
}
grid = next;
}
let frame = 0;
function draw() {
for (let y = 0; y < H; y++)
for (let x = 0; x < W; x++) {
const v = grid[y][x];
const hue = (v * 60 + frame * 0.2) % 360;
const light = 20 + v * 40;
ctx.fillStyle = `hsl(${hue}, 60%, ${light}%)`;
ctx.fillRect(x*S, y*S, S, S);
}
step();
frame++;
requestAnimationFrame(draw);
}
draw();
This technique — continuous-valued cellular automata with learnable kernels — is the foundation of Lenia, a modern generalization of cellular automata discovered by Bert Wang-Chak Chan. Lenia produces creatures so lifelike that they've been published in artificial life research journals. The example above is a simplified version, but it demonstrates the core idea: weighted neighborhoods plus activation functions create organic, self-organizing patterns.
Why cellular automata matter for generative art
Cellular automata are uniquely powerful for creative coding because they're the purest form of emergence. You don't design the output — you design the rules, and the output designs itself. This makes them:
- Infinitely variable — Change one parameter and the entire visual character transforms.
- Resolution-independent — Scale the grid up or down without changing behavior.
- Computationally simple — Most automata need only addition and comparison per cell.
- Endlessly watchable — Emergent systems never truly repeat, even when they're deterministic.
They also connect to almost every other area of generative art. Procedural generation uses cellular automata for cave systems and terrain. Fractal patterns emerge naturally from rules like Rule 90. Mathematical art finds expression in the symmetric structures of Wireworld circuits. And reaction-diffusion systems (covered in our generative design guide) are continuous cousins of discrete automata.
Extending the examples
Every example in this guide can be extended in powerful ways:
- Mouse interaction — Let users paint cells alive/dead by clicking and dragging. Add
canvas.addEventListener('mousemove', ...)to toggle cells under the cursor. - Multiple rulesets — Run different rules in different regions of the grid. The boundaries between rulesets create fascinating interference patterns.
- Color mapping by age — Instead of binary alive/dead coloring, track how many generations each cell has been alive. Map age to a gradient for heatmap-style visualization.
- 3D cellular automata — Extend any 2D automaton to 3D and render with raycasting or isometric projection. 3D Game of Life produces stunning crystalline structures.
- Audio reactivity — Use audio data to modulate parameters: birth/survival thresholds, playback speed, color mapping, or periodic cell injection.
- Export to image — Elementary cellular automata produce beautiful static images. Render thousands of rows at once for poster-quality prints.
Go further
Cellular automata are a gateway drug to emergence — once you've seen complexity arise from simplicity, you start seeing it everywhere. The eight systems in this guide barely scratch the surface. There are turmites (multi-state Langton's Ants), Larger than Life (extended-range Game of Life variants), Lenia (continuous-space artificial life), and lattice gas automata that simulate fluid dynamics.
On Lumitree, several micro-worlds use cellular automata to create living textures and evolving patterns — all within the 50KB constraint. Visitors plant a seed, and what grows is an emergent system unique to their input. Each branch is a tiny universe governed by simple rules, producing infinite complexity.
The code examples in this guide use vanilla JavaScript and HTML Canvas — no libraries, no build tools. Start with the Game of Life to understand the fundamentals. Then try Brian's Brain to see how a third state transforms the dynamics. Then experiment with SmoothLife to watch discrete cells become continuous life. That's the trajectory of cellular automata: from grids to galaxies, from rules to life.