Run the simulation below. The ant starts at the centre of the grid facing up. Use the buttons to step slowly through the chaos phase, then jump ahead and watch the highway snap into place.
<div class="controls">
<button id="btn-step" type="button">Step</button>
<button id="btn-run" type="button">Run</button>
<button id="btn-pause" type="button" disabled>Pause</button>
<button id="btn-jump" type="button">Jump to ~10k</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
<div class="info-bar">
<span id="step-count">Step: 0</span>
<span id="phase-label" class="phase chaos">Phase: chaos</span>
</div>
<canvas id="canvas" width="380" height="300"></canvas>
<p class="hint">Watch the ant wander chaotically, then snap into a repeating diagonal highway around step 10,000.</p>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; background: #fff; }
.controls { display: flex; gap: .4rem; flex-wrap: wrap; margin-bottom: .5rem; }
button { font: 600 13px system-ui, sans-serif; padding: .38rem .75rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 7px; cursor: pointer; transition: opacity .15s; }
button:disabled { opacity: .4; cursor: default; }
button.ghost { background: #fff; color: #1d3557; }
.info-bar { display: flex; gap: 1rem; align-items: center; margin-bottom: .4rem;
font-size: .88rem; font-weight: 600; }
#step-count { color: #444; }
.phase { padding: .15rem .55rem; border-radius: 99px; font-size: .8rem; }
.phase.chaos { background: #fde8e8; color: #b91c1c; }
.phase.highway { background: #d1fae5; color: #065f46; }
canvas { border: 1px solid #cdd9e3; border-radius: 8px; display: block;
image-rendering: pixelated; width: 380px; height: 300px; }
.hint { font-size: .82rem; color: #555; margin: .5rem 0 0; line-height: 1.4; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Langton's Ant simulation
// Rules: on WHITE turn right, flip to BLACK, step forward.
// on BLACK turn left, flip to WHITE, step forward.
// After ~10,000 steps the ant enters a 104-step repeating "highway" cycle.
const CANVAS_W = 380, CANVAS_H = 300;
const CELL = 4; // pixels per cell
const COLS = Math.floor(CANVAS_W / CELL); // 95
const ROWS = Math.floor(CANVAS_H / CELL); // 75
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// Grid stored as a flat Uint8Array (0=white, 1=black)
let grid, antX, antY, antDir, stepCount, running, timer;
// Directions: 0=up, 1=right, 2=down, 3=left
const DX = [0, 1, 0, -1];
const DY = [-1, 0, 1, 0];
function init() {
grid = new Uint8Array(COLS * ROWS); // all white
antX = Math.floor(COLS / 2);
antY = Math.floor(ROWS / 2);
antDir = 0; // facing up
stepCount = 0;
running = false;
clearInterval(timer);
document.getElementById('btn-pause').disabled = true;
document.getElementById('btn-run').disabled = false;
updateInfoBar();
draw();
}
function step() {
const idx = antY * COLS + antX;
const color = grid[idx];
// Turn: white -> right (+1), black -> left (-1)
antDir = (antDir + (color === 0 ? 1 : 3)) % 4;
// Flip cell
grid[idx] = color === 0 ? 1 : 0;
// Move forward
antX += DX[antDir];
antY += DY[antDir];
// Wrap (toroidal grid so ant doesn't vanish off edge)
antX = ((antX % COLS) + COLS) % COLS;
antY = ((antY % ROWS) + ROWS) % ROWS;
stepCount++;
}
function draw() {
// Draw the grid
const imgData = ctx.createImageData(CANVAS_W, CANVAS_H);
const data = imgData.data;
for (let row = 0; row < ROWS; row++) {
for (let col = 0; col < COLS; col++) {
const cell = grid[row * COLS + col];
const r = cell === 1 ? 30 : 240;
const g = cell === 1 ? 40 : 245;
const b = cell === 1 ? 58 : 250;
for (let py = 0; py < CELL; py++) {
for (let px = 0; px < CELL; px++) {
const pi = ((row * CELL + py) * CANVAS_W + col * CELL + px) * 4;
data[pi] = r; data[pi+1] = g; data[pi+2] = b; data[pi+3] = 255;
}
}
}
}
ctx.putImageData(imgData, 0, 0);
// Draw ant as a red dot
ctx.fillStyle = '#e63946';
ctx.beginPath();
ctx.arc(antX * CELL + CELL/2, antY * CELL + CELL/2, CELL * 0.7, 0, Math.PI * 2);
ctx.fill();
}
function isHighway() {
// After 10,450+ steps the ant has reliably started the highway
return stepCount >= 10450;
}
function updateInfoBar() {
document.getElementById('step-count').textContent = fmt('Step: %stepCount%', { stepCount: stepCount.toLocaleString() });
const lbl = document.getElementById('phase-label');
if (isHighway()) {
lbl.textContent = 'Phase: highway!';
lbl.className = 'phase highway';
} else {
lbl.textContent = 'Phase: chaos';
lbl.className = 'phase chaos';
}
}
document.getElementById('btn-step').addEventListener('click', () => {
if (running) return;
step();
draw();
updateInfoBar();
});
document.getElementById('btn-run').addEventListener('click', () => {
if (running) return;
running = true;
document.getElementById('btn-run').disabled = true;
document.getElementById('btn-pause').disabled = false;
// Run ~30 steps per frame for smooth animation
timer = setInterval(() => {
for (let i = 0; i < 30; i++) step();
draw();
updateInfoBar();
}, 33);
});
document.getElementById('btn-pause').addEventListener('click', () => {
running = false;
clearInterval(timer);
document.getElementById('btn-run').disabled = false;
document.getElementById('btn-pause').disabled = true;
});
document.getElementById('btn-jump').addEventListener('click', () => {
// Jump to step 10,500 quickly without drawing intermediate frames
if (running) {
clearInterval(timer);
running = false;
document.getElementById('btn-run').disabled = false;
document.getElementById('btn-pause').disabled = true;
}
const target = 10500;
while (stepCount < target) step();
draw();
updateInfoBar();
});
document.getElementById('btn-reset').addEventListener('click', init);
init();
<div class="controls">
<button id="btn-step" type="button">Step</button>
<button id="btn-run" type="button">Run</button>
<button id="btn-pause" type="button" disabled>Pause</button>
<button id="btn-jump" type="button">Jump to ~10k</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
<div class="info-bar">
<span id="step-count">Step: 0</span>
<span id="phase-label" class="phase chaos">Phase: chaos</span>
</div>
<canvas id="canvas" width="380" height="300"></canvas>
<p class="hint">Watch the ant wander chaotically, then snap into a repeating diagonal highway around step 10,000.</p>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; background: #fff; }
.controls { display: flex; gap: .4rem; flex-wrap: wrap; margin-bottom: .5rem; }
button { font: 600 13px system-ui, sans-serif; padding: .38rem .75rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 7px; cursor: pointer; transition: opacity .15s; }
button:disabled { opacity: .4; cursor: default; }
button.ghost { background: #fff; color: #1d3557; }
.info-bar { display: flex; gap: 1rem; align-items: center; margin-bottom: .4rem;
font-size: .88rem; font-weight: 600; }
#step-count { color: #444; }
.phase { padding: .15rem .55rem; border-radius: 99px; font-size: .8rem; }
.phase.chaos { background: #fde8e8; color: #b91c1c; }
.phase.highway { background: #d1fae5; color: #065f46; }
canvas { border: 1px solid #cdd9e3; border-radius: 8px; display: block;
image-rendering: pixelated; width: 380px; height: 300px; }
.hint { font-size: .82rem; color: #555; margin: .5rem 0 0; line-height: 1.4; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Langton's Ant simulation
// Rules: on WHITE turn right, flip to BLACK, step forward.
// on BLACK turn left, flip to WHITE, step forward.
// After ~10,000 steps the ant enters a 104-step repeating "highway" cycle.
const CANVAS_W = 380, CANVAS_H = 300;
const CELL = 4; // pixels per cell
const COLS = Math.floor(CANVAS_W / CELL); // 95
const ROWS = Math.floor(CANVAS_H / CELL); // 75
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// Grid stored as a flat Uint8Array (0=white, 1=black)
let grid, antX, antY, antDir, stepCount, running, timer;
// Directions: 0=up, 1=right, 2=down, 3=left
const DX = [0, 1, 0, -1];
const DY = [-1, 0, 1, 0];
function init() {
grid = new Uint8Array(COLS * ROWS); // all white
antX = Math.floor(COLS / 2);
antY = Math.floor(ROWS / 2);
antDir = 0; // facing up
stepCount = 0;
running = false;
clearInterval(timer);
document.getElementById('btn-pause').disabled = true;
document.getElementById('btn-run').disabled = false;
updateInfoBar();
draw();
}
function step() {
const idx = antY * COLS + antX;
const color = grid[idx];
// Turn: white -> right (+1), black -> left (-1)
antDir = (antDir + (color === 0 ? 1 : 3)) % 4;
// Flip cell
grid[idx] = color === 0 ? 1 : 0;
// Move forward
antX += DX[antDir];
antY += DY[antDir];
// Wrap (toroidal grid so ant doesn't vanish off edge)
antX = ((antX % COLS) + COLS) % COLS;
antY = ((antY % ROWS) + ROWS) % ROWS;
stepCount++;
}
function draw() {
// Draw the grid
const imgData = ctx.createImageData(CANVAS_W, CANVAS_H);
const data = imgData.data;
for (let row = 0; row < ROWS; row++) {
for (let col = 0; col < COLS; col++) {
const cell = grid[row * COLS + col];
const r = cell === 1 ? 30 : 240;
const g = cell === 1 ? 40 : 245;
const b = cell === 1 ? 58 : 250;
for (let py = 0; py < CELL; py++) {
for (let px = 0; px < CELL; px++) {
const pi = ((row * CELL + py) * CANVAS_W + col * CELL + px) * 4;
data[pi] = r; data[pi+1] = g; data[pi+2] = b; data[pi+3] = 255;
}
}
}
}
ctx.putImageData(imgData, 0, 0);
// Draw ant as a red dot
ctx.fillStyle = '#e63946';
ctx.beginPath();
ctx.arc(antX * CELL + CELL/2, antY * CELL + CELL/2, CELL * 0.7, 0, Math.PI * 2);
ctx.fill();
}
function isHighway() {
// After 10,450+ steps the ant has reliably started the highway
return stepCount >= 10450;
}
function updateInfoBar() {
document.getElementById('step-count').textContent = fmt('Step: %stepCount%', { stepCount: stepCount.toLocaleString() });
const lbl = document.getElementById('phase-label');
if (isHighway()) {
lbl.textContent = 'Phase: highway!';
lbl.className = 'phase highway';
} else {
lbl.textContent = 'Phase: chaos';
lbl.className = 'phase chaos';
}
}
document.getElementById('btn-step').addEventListener('click', () => {
if (running) return;
step();
draw();
updateInfoBar();
});
document.getElementById('btn-run').addEventListener('click', () => {
if (running) return;
running = true;
document.getElementById('btn-run').disabled = true;
document.getElementById('btn-pause').disabled = false;
// Run ~30 steps per frame for smooth animation
timer = setInterval(() => {
for (let i = 0; i < 30; i++) step();
draw();
updateInfoBar();
}, 33);
});
document.getElementById('btn-pause').addEventListener('click', () => {
running = false;
clearInterval(timer);
document.getElementById('btn-run').disabled = false;
document.getElementById('btn-pause').disabled = true;
});
document.getElementById('btn-jump').addEventListener('click', () => {
// Jump to step 10,500 quickly without drawing intermediate frames
if (running) {
clearInterval(timer);
running = false;
document.getElementById('btn-run').disabled = false;
document.getElementById('btn-pause').disabled = true;
}
const target = 10500;
while (stepCount < target) step();
draw();
updateInfoBar();
});
document.getElementById('btn-reset').addEventListener('click', init);
init();
Notice the moment the highway begins: the grid goes from looking random to moving in a perfectly repeating diagonal stripe. The Step button lets you advance one move at a time. Run plays it at speed. Jump to ~10k skips straight to where the highway usually appears. Reset starts fresh.
The two rules are entirely deterministic — if you re-run with the same starting position you get exactly the same path. Yet predicting the highway's start time without running the simulation is, as far as we know, impossible.
Comments
Loading comments...