The demo below runs Rule 110 on a row of 80 cells with a single black cell as the starting seed. Each row displayed is one generation. Press Step to advance one generation at a time, or Run to animate continuously.
<div class="controls">
<button id="btn-step" type="button">Step</button>
<button id="btn-run" type="button">Run</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
<span class="gen-label">Generation: <span id="gen-count">0</span></span>
</div>
<canvas id="ca-canvas" width="560" height="360"></canvas>
<p class="hint">Each row is one generation of Rule 110. Click cells in the <b>top row</b> to set your own initial pattern, then press <b>Run</b>.</p>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; background: #f5f7fa; color: #222; }
.controls { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; margin-bottom: .6rem; }
button { font: 600 13px system-ui, sans-serif; padding: .4rem .85rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .45; cursor: default; }
.gen-label { font-size: .85rem; color: #555; margin-left: .4rem; }
#ca-canvas { display: block; border: 1px solid #cdd9e3; border-radius: 8px;
cursor: crosshair; background: #fff; max-width: 100%; }
.hint { font-size: .82rem; color: #555; margin: .5rem 0 0; line-height: 1.5; }
// Rule 110 cellular automaton
// Rule number 110 = 0b01101110
// Lookup: pattern (left,center,right) as 3-bit index -> output bit
// 111->0, 110->1, 101->1, 100->0, 011->1, 010->1, 001->1, 000->0
const RULE = 110;
const ruleTable = new Uint8Array(8);
for (let i = 0; i < 8; i++) ruleTable[i] = (RULE >> i) & 1;
const COLS = 80;
const VISIBLE_ROWS = 45;
const CELL_W = 7;
const CELL_H = 8;
const canvas = document.getElementById('ca-canvas');
canvas.width = COLS * CELL_W;
canvas.height = VISIBLE_ROWS * CELL_H;
const ctx = canvas.getContext('2d');
// history[0] = oldest shown, history[VISIBLE_ROWS-1] = newest
let history = [];
let generation = 0;
let running = false;
let rafId = null;
function makeInitialRow() {
const row = new Uint8Array(COLS);
row[Math.floor(COLS / 2)] = 1; // single black cell in the middle
return row;
}
function stepRow(row) {
const next = new Uint8Array(COLS);
for (let c = 0; c < COLS; c++) {
const left = row[(c - 1 + COLS) % COLS];
const center = row[c];
const right = row[(c + 1) % COLS];
const pattern = (left << 2) | (center << 1) | right;
next[c] = ruleTable[pattern];
}
return next;
}
function reset() {
stop();
generation = 0;
history = [];
history.push(makeInitialRow());
// precompute enough rows to fill screen
while (history.length < VISIBLE_ROWS) {
history.push(stepRow(history[history.length - 1]));
generation++;
}
document.getElementById('gen-count').textContent = generation;
draw();
}
function advance() {
history.push(stepRow(history[history.length - 1]));
if (history.length > VISIBLE_ROWS) history.shift();
generation++;
document.getElementById('gen-count').textContent = generation;
draw();
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let r = 0; r < history.length; r++) {
const row = history[r];
for (let c = 0; c < COLS; c++) {
if (row[c]) {
ctx.fillStyle = '#1d3557';
} else {
ctx.fillStyle = '#f0f4f8';
}
ctx.fillRect(c * CELL_W, r * CELL_H, CELL_W - 1, CELL_H - 1);
}
}
}
function stop() {
running = false;
if (rafId) { cancelAnimationFrame(rafId); rafId = null; }
document.getElementById('btn-run').textContent = 'Run';
document.getElementById('btn-run').disabled = false;
}
function run() {
if (running) { stop(); return; }
running = true;
document.getElementById('btn-run').textContent = 'Pause';
let last = 0;
function loop(ts) {
if (!running) return;
if (ts - last > 60) { advance(); last = ts; }
rafId = requestAnimationFrame(loop);
}
rafId = requestAnimationFrame(loop);
}
// Click top row to edit initial seed
canvas.addEventListener('click', function(e) {
if (running) return;
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
const x = (e.clientX - rect.left) * scaleX;
const y = (e.clientY - rect.top) * scaleY;
const clickedRow = Math.floor(y / CELL_H);
const clickedCol = Math.floor(x / CELL_W);
if (clickedRow === 0 && clickedCol >= 0 && clickedCol < COLS) {
// Reset history keeping only the modified seed row
const seed = history[0].slice();
seed[clickedCol] ^= 1;
generation = 0;
history = [seed];
while (history.length < VISIBLE_ROWS) {
history.push(stepRow(history[history.length - 1]));
generation++;
}
document.getElementById('gen-count').textContent = generation;
draw();
}
});
document.getElementById('btn-step').addEventListener('click', function() {
if (running) stop();
advance();
});
document.getElementById('btn-run').addEventListener('click', run);
document.getElementById('btn-reset').addEventListener('click', reset);
reset();
<div class="controls">
<button id="btn-step" type="button">Step</button>
<button id="btn-run" type="button">Run</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
<span class="gen-label">Generation: <span id="gen-count">0</span></span>
</div>
<canvas id="ca-canvas" width="560" height="360"></canvas>
<p class="hint">Each row is one generation of Rule 110. Click cells in the <b>top row</b> to set your own initial pattern, then press <b>Run</b>.</p>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; background: #f5f7fa; color: #222; }
.controls { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; margin-bottom: .6rem; }
button { font: 600 13px system-ui, sans-serif; padding: .4rem .85rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .45; cursor: default; }
.gen-label { font-size: .85rem; color: #555; margin-left: .4rem; }
#ca-canvas { display: block; border: 1px solid #cdd9e3; border-radius: 8px;
cursor: crosshair; background: #fff; max-width: 100%; }
.hint { font-size: .82rem; color: #555; margin: .5rem 0 0; line-height: 1.5; }
// Rule 110 cellular automaton
// Rule number 110 = 0b01101110
// Lookup: pattern (left,center,right) as 3-bit index -> output bit
// 111->0, 110->1, 101->1, 100->0, 011->1, 010->1, 001->1, 000->0
const RULE = 110;
const ruleTable = new Uint8Array(8);
for (let i = 0; i < 8; i++) ruleTable[i] = (RULE >> i) & 1;
const COLS = 80;
const VISIBLE_ROWS = 45;
const CELL_W = 7;
const CELL_H = 8;
const canvas = document.getElementById('ca-canvas');
canvas.width = COLS * CELL_W;
canvas.height = VISIBLE_ROWS * CELL_H;
const ctx = canvas.getContext('2d');
// history[0] = oldest shown, history[VISIBLE_ROWS-1] = newest
let history = [];
let generation = 0;
let running = false;
let rafId = null;
function makeInitialRow() {
const row = new Uint8Array(COLS);
row[Math.floor(COLS / 2)] = 1; // single black cell in the middle
return row;
}
function stepRow(row) {
const next = new Uint8Array(COLS);
for (let c = 0; c < COLS; c++) {
const left = row[(c - 1 + COLS) % COLS];
const center = row[c];
const right = row[(c + 1) % COLS];
const pattern = (left << 2) | (center << 1) | right;
next[c] = ruleTable[pattern];
}
return next;
}
function reset() {
stop();
generation = 0;
history = [];
history.push(makeInitialRow());
// precompute enough rows to fill screen
while (history.length < VISIBLE_ROWS) {
history.push(stepRow(history[history.length - 1]));
generation++;
}
document.getElementById('gen-count').textContent = generation;
draw();
}
function advance() {
history.push(stepRow(history[history.length - 1]));
if (history.length > VISIBLE_ROWS) history.shift();
generation++;
document.getElementById('gen-count').textContent = generation;
draw();
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let r = 0; r < history.length; r++) {
const row = history[r];
for (let c = 0; c < COLS; c++) {
if (row[c]) {
ctx.fillStyle = '#1d3557';
} else {
ctx.fillStyle = '#f0f4f8';
}
ctx.fillRect(c * CELL_W, r * CELL_H, CELL_W - 1, CELL_H - 1);
}
}
}
function stop() {
running = false;
if (rafId) { cancelAnimationFrame(rafId); rafId = null; }
document.getElementById('btn-run').textContent = 'Run';
document.getElementById('btn-run').disabled = false;
}
function run() {
if (running) { stop(); return; }
running = true;
document.getElementById('btn-run').textContent = 'Pause';
let last = 0;
function loop(ts) {
if (!running) return;
if (ts - last > 60) { advance(); last = ts; }
rafId = requestAnimationFrame(loop);
}
rafId = requestAnimationFrame(loop);
}
// Click top row to edit initial seed
canvas.addEventListener('click', function(e) {
if (running) return;
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
const x = (e.clientX - rect.left) * scaleX;
const y = (e.clientY - rect.top) * scaleY;
const clickedRow = Math.floor(y / CELL_H);
const clickedCol = Math.floor(x / CELL_W);
if (clickedRow === 0 && clickedCol >= 0 && clickedCol < COLS) {
// Reset history keeping only the modified seed row
const seed = history[0].slice();
seed[clickedCol] ^= 1;
generation = 0;
history = [seed];
while (history.length < VISIBLE_ROWS) {
history.push(stepRow(history[history.length - 1]));
generation++;
}
document.getElementById('gen-count').textContent = generation;
draw();
}
});
document.getElementById('btn-step').addEventListener('click', function() {
if (running) stop();
advance();
});
document.getElementById('btn-run').addEventListener('click', run);
document.getElementById('btn-reset').addEventListener('click', reset);
reset();
Watch for the persistent triangular structures (called gliders in the literature) that slide leftward across the field. They are the key objects Cook used to build logic gates in his universality proof. Notice that the pattern never settles into simple repetition — this perpetual complexity is the hallmark of a Turing-complete system. Try Reset to start over, or edit the initial row by clicking cells.
Comments
Loading comments...