<p class="hint">A Turing machine reads a tape of tokens, one cell at a time. Blue and orange tokens are the symbols 0 and 1; an empty slot is a blank. The marker shows the head and its state. Pick a machine and run it.</p>
<div class="presets" id="presets" role="group" aria-label="Machine presets">
<button type="button" data-p="inc">Binary +1</button>
<button type="button" data-p="add">Unary 3+2</button>
<button type="button" data-p="bb3">Busy beaver 3</button>
<button type="button" data-p="bb4">Busy beaver 4</button>
<button type="button" data-p="loop">Never halts</button>
</div>
<p class="desc" id="desc"></p>
<div class="headrow" id="headrow"></div>
<div class="tape" id="tape" aria-label="Tape cells around the head"></div>
<div class="stats">
<span>Steps <b id="s_steps">0</b></span>
<span>State <b id="s_state">-</b></span>
<span>Ones <b id="s_ones">0</b></span>
</div>
<div class="btns">
<button type="button" id="step">Step</button>
<button type="button" id="run">Run</button>
<button type="button" id="skip" class="ghost">Run to budget</button>
<button type="button" id="reset" class="ghost">Reset</button>
<label class="bud">Step budget <select id="budget">
<option value="25">25</option>
<option value="100">100</option>
<option value="1000" selected>1000</option>
<option value="100000">100000</option>
</select></label>
</div>
<div class="verdict" id="verdict" role="status" aria-live="polite"></div>
<table class="rules" id="rules" aria-label="Transition table"></table>
:root { --bg: #ffffff; --fg: #1f2933; --muted: #52606d; --line: #cbd2d9; --panel: #f0f4f8; --accent: #1d3557; --accent-fg: #ffffff; --t0: #3a7bd5; --t1: #e8912d; --tfg: #ffffff; --ok: #0a7d33; --warn: #b4540a; }
@media (prefers-color-scheme: dark) {
:root { --bg: #14181d; --fg: #e4e9ee; --muted: #9aa8b6; --line: #394552; --panel: #1e252d; --accent: #8fb4e8; --accent-fg: #10151a; --t0: #4f8fe0; --t1: #f0a24a; --tfg: #10151a; --ok: #4cc27a; --warn: #f0a24a; }
}
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: var(--fg); background: var(--bg); margin: 0; padding: 2px; }
.hint { font-size: .85rem; color: var(--muted); margin: 0 0 .6rem; line-height: 1.4; }
.presets { display: flex; flex-wrap: wrap; gap: .35rem; margin-bottom: .4rem; }
.presets button { font: 600 12px system-ui, sans-serif; padding: .35rem .6rem; border: 1px solid var(--line); background: var(--panel); color: var(--fg); border-radius: 999px; cursor: pointer; }
.presets button.active { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }
.desc { font-size: .8rem; color: var(--muted); margin: 0 0 .5rem; line-height: 1.35; min-height: 2.2em; }
.headrow, .tape { display: grid; grid-template-columns: repeat(11, minmax(0, 1fr)); gap: 3px; }
.marker { text-align: center; font: 700 13px ui-monospace, monospace; color: var(--accent); }
.marker::after { content: "\25BC"; display: block; line-height: 1; font-size: 11px; }
.marker.stop { color: var(--warn); }
.cell { aspect-ratio: 3 / 4; max-height: 64px; border: 1px dashed var(--line); border-radius: 6px; display: flex; align-items: center; justify-content: center; background: var(--panel); }
.cell.here { border: 2px solid var(--accent); }
.tok { display: inline-flex; align-items: center; justify-content: center; font: 700 15px ui-monospace, monospace; color: var(--tfg); border-radius: 5px; }
.cell .tok { width: 84%; height: 88%; }
.tok.t0 { background: var(--t0); }
.tok.t1 { background: var(--t1); }
.tok.t_ { background: transparent; }
.stats { display: flex; gap: .9rem; flex-wrap: wrap; font-size: .85rem; margin: .5rem 0; color: var(--muted); }
.stats b { color: var(--fg); font-family: ui-monospace, monospace; }
.btns { display: flex; gap: .4rem; flex-wrap: wrap; align-items: center; }
.btns button { font: 600 13px system-ui, sans-serif; padding: .4rem .8rem; border: 1px solid var(--accent); background: var(--accent); color: var(--accent-fg); border-radius: 8px; cursor: pointer; }
.btns button.ghost { background: transparent; color: var(--accent); }
.bud { font-size: .8rem; color: var(--muted); }
.bud select { font: inherit; background: var(--panel); color: var(--fg); border: 1px solid var(--line); border-radius: 6px; padding: .2rem; }
.verdict { margin: .6rem 0; padding: .45rem .6rem; border-radius: 8px; background: var(--panel); font-size: .85rem; font-weight: 600; line-height: 1.35; min-height: 2.4em; }
.verdict.ok { color: var(--ok); }
.verdict.warn { color: var(--warn); }
.rules { border-collapse: collapse; font: 12px ui-monospace, monospace; width: 100%; max-width: 360px; }
.rules th, .rules td { border: 1px solid var(--line); padding: 3px 6px; text-align: center; }
.rules td.on { background: var(--accent); color: var(--accent-fg); }
.rules .tok { width: 18px; height: 18px; font-size: 11px; }
.rules .tok.t_ { border: 1px dashed var(--line); }
const PRESETS = {
inc: { states: ['C'], start: 'C', symbols: ['_', '0', '1'], tape: '1011', head: 3,
rules: { C: { '1': ['0', 'L', 'C'], '0': ['1', 'L', 'H'], '_': ['1', 'L', 'H'] } } },
bb3: { states: ['A', 'B', 'C'], start: 'A', symbols: ['_', '1'], tape: '', head: 0,
rules: { A: { '_': ['1', 'L', 'C'], '1': ['1', 'L', 'H'] }, B: { '_': ['1', 'R', 'B'], '1': ['1', 'R', 'A'] }, C: { '_': ['1', 'R', 'C'], '1': ['_', 'L', 'B'] } } },
bb4: { states: ['A', 'B', 'C', 'D'], start: 'A', symbols: ['_', '1'], tape: '', head: 0,
rules: { A: { '_': ['1', 'R', 'B'], '1': ['1', 'L', 'B'] }, B: { '_': ['1', 'L', 'A'], '1': ['_', 'L', 'C'] }, C: { '_': ['1', 'R', 'H'], '1': ['1', 'L', 'D'] }, D: { '_': ['1', 'R', 'D'], '1': ['_', 'R', 'A'] } } },
add: { states: ['A', 'B', 'C'], start: 'A', symbols: ['_', '1'], tape: '111_11', head: 0,
rules: { A: { '1': ['1', 'R', 'A'], '_': ['1', 'R', 'B'] }, B: { '1': ['1', 'R', 'B'], '_': ['_', 'L', 'C'] }, C: { '1': ['_', 'L', 'H'], '_': ['_', 'L', 'H'] } } },
loop: { states: ['A', 'B'], start: 'A', symbols: ['_', '0', '1'], tape: '', head: 0,
rules: { A: { '1': ['0', 'L', 'A'], '0': ['1', 'R', 'B'], '_': ['1', 'R', 'B'] }, B: { '0': ['0', 'R', 'B'], '1': ['1', 'R', 'B'], '_': ['_', 'L', 'A'] } } }
};
function makeMachine(key) {
const p = PRESETS[key];
const tape = new Map();
for (let i = 0; i < p.tape.length; i++) if (p.tape[i] !== '_') tape.set(i, p.tape[i]);
return { p, tape, pos: p.head, state: p.start, steps: 0, halted: false, last: null };
}
function stepMachine(m) {
if (m.halted) return false;
const sym = m.tape.get(m.pos) || '_';
const r = m.p.rules[m.state][sym];
m.last = [m.state, sym];
if (sym === '_') m.tape.delete(m.pos);
if (r[0] === '_') m.tape.delete(m.pos); else m.tape.set(m.pos, r[0]);
m.pos += r[1] === 'R' ? 1 : -1;
m.steps++;
if (r[2] === 'H') m.halted = true; else m.state = r[2];
return true;
}
function countOnes(m) { let n = 0; m.tape.forEach(v => { if (v === '1') n++; }); return n; }
function tapeString(m) {
if (!m.tape.size) return '';
const ks = [...m.tape.keys()]; const lo = Math.min(...ks), hi = Math.max(...ks); let s = '';
for (let i = lo; i <= hi; i++) s += m.tape.get(i) || '_';
return s;
}
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
const W = 11;
const MID = 5;
const NAMES = { inc: "Adds 1 to the binary number 1011. The head starts on the last bit; each 1 flips to 0 and carries left until a 0 or a blank becomes 1.", bb3: "A 3-state busy beaver on a blank tape. It halts after exactly 21 steps with five 1s, the most steps any halting 3-state, 2-symbol machine takes.", bb4: "A 4-state busy beaver: 107 steps and thirteen 1s, the record for 4 states. With a budget of 100 it looks like it may never stop, 7 steps short of halting.", add: "Unary addition 3 + 2: the blank between 111 and 11 is filled with a 1, then the final 1 is erased, leaving five 1s.", loop: "A binary counter that counts 1, 2, 3, and so on without end. It never halts, yet waiting can never prove that." };
let key = 'inc';
let m = makeMachine(key);
let timer = null;
const $ = (id) => document.getElementById(id);
function budget() { return parseInt($('budget').value, 10); }
function renderRules() {
const p = m.p;
const syms = p.symbols;
let h = '<tr><th></th>' + syms.map((s) => '<th><span class="tok t' + s + '">' + (s === '_' ? '' : s) + '</span></th>').join('') + '</tr>';
for (const st of p.states) {
h += '<tr><th>' + st + '</th>';
for (const s of syms) {
const r = p.rules[st][s];
h += '<td data-s="' + st + s + '">' + (r ? (r[0] === '_' ? '·' : r[0]) + ' ' + r[1] + ' ' + r[2] : '—') + '</td>';
}
h += '</tr>';
}
$('rules').innerHTML = h;
}
function markRule() {
const cells = $('rules').querySelectorAll('td');
const sym = m.tape.get(m.pos) || '_';
cells.forEach((c) => c.classList.toggle('on', !m.halted && c.dataset.s === m.state + sym));
}
function renderTape() {
let h = '';
for (let i = 0; i < W; i++) {
const idx = m.pos - MID + i;
const s = m.tape.get(idx) || '_';
h += '<div class="cell' + (i === MID ? ' here' : '') + '"><span class="tok t' + s + '">' + (s === '_' ? '' : s) + '</span></div>';
}
$('tape').innerHTML = h;
const hr = $('headrow');
hr.innerHTML = '';
const mk = document.createElement('div');
mk.className = 'marker' + (m.halted ? ' stop' : '');
mk.textContent = m.halted ? 'H' : m.state;
mk.style.gridColumn = String(MID + 1);
hr.appendChild(mk);
}
function renderStats() {
$('s_steps').textContent = m.steps;
$('s_state').textContent = m.halted ? 'H' : m.state;
$('s_ones').textContent = countOnes(m);
}
function verdict(kind) {
const v = $('verdict');
if (kind === 'halt') {
v.className = 'verdict ok';
v.textContent = i18nFmt("Halted after %n% steps with %k% ones on the tape.", { n: m.steps, k: countOnes(m) });
} else if (kind === 'budget') {
v.className = 'verdict warn';
v.textContent = i18nFmt("Still running after %n% steps (budget reached). It may halt later or never; no algorithm decides this for every machine.", { n: m.steps });
} else {
v.className = 'verdict';
v.textContent = "Press Step or Run. The machine will either halt or keep going.";
}
}
function paint() {
renderTape();
renderStats();
markRule();
if (m.halted) verdict('halt');
}
function stop() {
if (timer) { clearInterval(timer); timer = null; }
$('run').textContent = "Run";
}
function load(k) {
stop();
key = k;
m = makeMachine(k);
document.querySelectorAll('#presets button').forEach((b) => b.classList.toggle('active', b.dataset.p === k));
$('desc').textContent = NAMES[k];
renderRules();
verdict('ready');
paint();
}
function doStep() {
if (m.halted) return;
if (m.steps >= budget()) { verdict('budget'); return; }
stepMachine(m);
paint();
if (!m.halted && m.steps >= budget()) { stop(); verdict('budget'); }
}
function runTo(limit) {
while (!m.halted && m.steps < limit) stepMachine(m);
}
function toggleRun() {
if (timer) { stop(); return; }
if (m.halted) return;
$('run').textContent = "Pause";
timer = setInterval(() => {
const chunk = 1 + Math.floor(m.steps / 30);
runTo(Math.min(budget(), m.steps + chunk));
paint();
if (m.halted) stop();
else if (m.steps >= budget()) { stop(); verdict('budget'); }
}, 70);
}
function skip() {
stop();
runTo(budget());
paint();
if (!m.halted) verdict('budget');
}
document.querySelectorAll('#presets button').forEach((b) => b.addEventListener('click', () => load(b.dataset.p)));
$('step').addEventListener('click', () => { stop(); doStep(); });
$('run').addEventListener('click', toggleRun);
$('skip').addEventListener('click', skip);
$('reset').addEventListener('click', () => load(key));
$('budget').addEventListener('change', () => { if (!m.halted) verdict('ready'); });
load('inc');
<p class="hint">A Turing machine reads a tape of tokens, one cell at a time. Blue and orange tokens are the symbols 0 and 1; an empty slot is a blank. The marker shows the head and its state. Pick a machine and run it.</p>
<div class="presets" id="presets" role="group" aria-label="Machine presets">
<button type="button" data-p="inc">Binary +1</button>
<button type="button" data-p="add">Unary 3+2</button>
<button type="button" data-p="bb3">Busy beaver 3</button>
<button type="button" data-p="bb4">Busy beaver 4</button>
<button type="button" data-p="loop">Never halts</button>
</div>
<p class="desc" id="desc"></p>
<div class="headrow" id="headrow"></div>
<div class="tape" id="tape" aria-label="Tape cells around the head"></div>
<div class="stats">
<span>Steps <b id="s_steps">0</b></span>
<span>State <b id="s_state">-</b></span>
<span>Ones <b id="s_ones">0</b></span>
</div>
<div class="btns">
<button type="button" id="step">Step</button>
<button type="button" id="run">Run</button>
<button type="button" id="skip" class="ghost">Run to budget</button>
<button type="button" id="reset" class="ghost">Reset</button>
<label class="bud">Step budget <select id="budget">
<option value="25">25</option>
<option value="100">100</option>
<option value="1000" selected>1000</option>
<option value="100000">100000</option>
</select></label>
</div>
<div class="verdict" id="verdict" role="status" aria-live="polite"></div>
<table class="rules" id="rules" aria-label="Transition table"></table>
:root { --bg: #ffffff; --fg: #1f2933; --muted: #52606d; --line: #cbd2d9; --panel: #f0f4f8; --accent: #1d3557; --accent-fg: #ffffff; --t0: #3a7bd5; --t1: #e8912d; --tfg: #ffffff; --ok: #0a7d33; --warn: #b4540a; }
@media (prefers-color-scheme: dark) {
:root { --bg: #14181d; --fg: #e4e9ee; --muted: #9aa8b6; --line: #394552; --panel: #1e252d; --accent: #8fb4e8; --accent-fg: #10151a; --t0: #4f8fe0; --t1: #f0a24a; --tfg: #10151a; --ok: #4cc27a; --warn: #f0a24a; }
}
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: var(--fg); background: var(--bg); margin: 0; padding: 2px; }
.hint { font-size: .85rem; color: var(--muted); margin: 0 0 .6rem; line-height: 1.4; }
.presets { display: flex; flex-wrap: wrap; gap: .35rem; margin-bottom: .4rem; }
.presets button { font: 600 12px system-ui, sans-serif; padding: .35rem .6rem; border: 1px solid var(--line); background: var(--panel); color: var(--fg); border-radius: 999px; cursor: pointer; }
.presets button.active { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }
.desc { font-size: .8rem; color: var(--muted); margin: 0 0 .5rem; line-height: 1.35; min-height: 2.2em; }
.headrow, .tape { display: grid; grid-template-columns: repeat(11, minmax(0, 1fr)); gap: 3px; }
.marker { text-align: center; font: 700 13px ui-monospace, monospace; color: var(--accent); }
.marker::after { content: "\25BC"; display: block; line-height: 1; font-size: 11px; }
.marker.stop { color: var(--warn); }
.cell { aspect-ratio: 3 / 4; max-height: 64px; border: 1px dashed var(--line); border-radius: 6px; display: flex; align-items: center; justify-content: center; background: var(--panel); }
.cell.here { border: 2px solid var(--accent); }
.tok { display: inline-flex; align-items: center; justify-content: center; font: 700 15px ui-monospace, monospace; color: var(--tfg); border-radius: 5px; }
.cell .tok { width: 84%; height: 88%; }
.tok.t0 { background: var(--t0); }
.tok.t1 { background: var(--t1); }
.tok.t_ { background: transparent; }
.stats { display: flex; gap: .9rem; flex-wrap: wrap; font-size: .85rem; margin: .5rem 0; color: var(--muted); }
.stats b { color: var(--fg); font-family: ui-monospace, monospace; }
.btns { display: flex; gap: .4rem; flex-wrap: wrap; align-items: center; }
.btns button { font: 600 13px system-ui, sans-serif; padding: .4rem .8rem; border: 1px solid var(--accent); background: var(--accent); color: var(--accent-fg); border-radius: 8px; cursor: pointer; }
.btns button.ghost { background: transparent; color: var(--accent); }
.bud { font-size: .8rem; color: var(--muted); }
.bud select { font: inherit; background: var(--panel); color: var(--fg); border: 1px solid var(--line); border-radius: 6px; padding: .2rem; }
.verdict { margin: .6rem 0; padding: .45rem .6rem; border-radius: 8px; background: var(--panel); font-size: .85rem; font-weight: 600; line-height: 1.35; min-height: 2.4em; }
.verdict.ok { color: var(--ok); }
.verdict.warn { color: var(--warn); }
.rules { border-collapse: collapse; font: 12px ui-monospace, monospace; width: 100%; max-width: 360px; }
.rules th, .rules td { border: 1px solid var(--line); padding: 3px 6px; text-align: center; }
.rules td.on { background: var(--accent); color: var(--accent-fg); }
.rules .tok { width: 18px; height: 18px; font-size: 11px; }
.rules .tok.t_ { border: 1px dashed var(--line); }
const PRESETS = {
inc: { states: ['C'], start: 'C', symbols: ['_', '0', '1'], tape: '1011', head: 3,
rules: { C: { '1': ['0', 'L', 'C'], '0': ['1', 'L', 'H'], '_': ['1', 'L', 'H'] } } },
bb3: { states: ['A', 'B', 'C'], start: 'A', symbols: ['_', '1'], tape: '', head: 0,
rules: { A: { '_': ['1', 'L', 'C'], '1': ['1', 'L', 'H'] }, B: { '_': ['1', 'R', 'B'], '1': ['1', 'R', 'A'] }, C: { '_': ['1', 'R', 'C'], '1': ['_', 'L', 'B'] } } },
bb4: { states: ['A', 'B', 'C', 'D'], start: 'A', symbols: ['_', '1'], tape: '', head: 0,
rules: { A: { '_': ['1', 'R', 'B'], '1': ['1', 'L', 'B'] }, B: { '_': ['1', 'L', 'A'], '1': ['_', 'L', 'C'] }, C: { '_': ['1', 'R', 'H'], '1': ['1', 'L', 'D'] }, D: { '_': ['1', 'R', 'D'], '1': ['_', 'R', 'A'] } } },
add: { states: ['A', 'B', 'C'], start: 'A', symbols: ['_', '1'], tape: '111_11', head: 0,
rules: { A: { '1': ['1', 'R', 'A'], '_': ['1', 'R', 'B'] }, B: { '1': ['1', 'R', 'B'], '_': ['_', 'L', 'C'] }, C: { '1': ['_', 'L', 'H'], '_': ['_', 'L', 'H'] } } },
loop: { states: ['A', 'B'], start: 'A', symbols: ['_', '0', '1'], tape: '', head: 0,
rules: { A: { '1': ['0', 'L', 'A'], '0': ['1', 'R', 'B'], '_': ['1', 'R', 'B'] }, B: { '0': ['0', 'R', 'B'], '1': ['1', 'R', 'B'], '_': ['_', 'L', 'A'] } } }
};
function makeMachine(key) {
const p = PRESETS[key];
const tape = new Map();
for (let i = 0; i < p.tape.length; i++) if (p.tape[i] !== '_') tape.set(i, p.tape[i]);
return { p, tape, pos: p.head, state: p.start, steps: 0, halted: false, last: null };
}
function stepMachine(m) {
if (m.halted) return false;
const sym = m.tape.get(m.pos) || '_';
const r = m.p.rules[m.state][sym];
m.last = [m.state, sym];
if (sym === '_') m.tape.delete(m.pos);
if (r[0] === '_') m.tape.delete(m.pos); else m.tape.set(m.pos, r[0]);
m.pos += r[1] === 'R' ? 1 : -1;
m.steps++;
if (r[2] === 'H') m.halted = true; else m.state = r[2];
return true;
}
function countOnes(m) { let n = 0; m.tape.forEach(v => { if (v === '1') n++; }); return n; }
function tapeString(m) {
if (!m.tape.size) return '';
const ks = [...m.tape.keys()]; const lo = Math.min(...ks), hi = Math.max(...ks); let s = '';
for (let i = lo; i <= hi; i++) s += m.tape.get(i) || '_';
return s;
}
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
const W = 11;
const MID = 5;
const NAMES = { inc: "Adds 1 to the binary number 1011. The head starts on the last bit; each 1 flips to 0 and carries left until a 0 or a blank becomes 1.", bb3: "A 3-state busy beaver on a blank tape. It halts after exactly 21 steps with five 1s, the most steps any halting 3-state, 2-symbol machine takes.", bb4: "A 4-state busy beaver: 107 steps and thirteen 1s, the record for 4 states. With a budget of 100 it looks like it may never stop, 7 steps short of halting.", add: "Unary addition 3 + 2: the blank between 111 and 11 is filled with a 1, then the final 1 is erased, leaving five 1s.", loop: "A binary counter that counts 1, 2, 3, and so on without end. It never halts, yet waiting can never prove that." };
let key = 'inc';
let m = makeMachine(key);
let timer = null;
const $ = (id) => document.getElementById(id);
function budget() { return parseInt($('budget').value, 10); }
function renderRules() {
const p = m.p;
const syms = p.symbols;
let h = '<tr><th></th>' + syms.map((s) => '<th><span class="tok t' + s + '">' + (s === '_' ? '' : s) + '</span></th>').join('') + '</tr>';
for (const st of p.states) {
h += '<tr><th>' + st + '</th>';
for (const s of syms) {
const r = p.rules[st][s];
h += '<td data-s="' + st + s + '">' + (r ? (r[0] === '_' ? '·' : r[0]) + ' ' + r[1] + ' ' + r[2] : '—') + '</td>';
}
h += '</tr>';
}
$('rules').innerHTML = h;
}
function markRule() {
const cells = $('rules').querySelectorAll('td');
const sym = m.tape.get(m.pos) || '_';
cells.forEach((c) => c.classList.toggle('on', !m.halted && c.dataset.s === m.state + sym));
}
function renderTape() {
let h = '';
for (let i = 0; i < W; i++) {
const idx = m.pos - MID + i;
const s = m.tape.get(idx) || '_';
h += '<div class="cell' + (i === MID ? ' here' : '') + '"><span class="tok t' + s + '">' + (s === '_' ? '' : s) + '</span></div>';
}
$('tape').innerHTML = h;
const hr = $('headrow');
hr.innerHTML = '';
const mk = document.createElement('div');
mk.className = 'marker' + (m.halted ? ' stop' : '');
mk.textContent = m.halted ? 'H' : m.state;
mk.style.gridColumn = String(MID + 1);
hr.appendChild(mk);
}
function renderStats() {
$('s_steps').textContent = m.steps;
$('s_state').textContent = m.halted ? 'H' : m.state;
$('s_ones').textContent = countOnes(m);
}
function verdict(kind) {
const v = $('verdict');
if (kind === 'halt') {
v.className = 'verdict ok';
v.textContent = i18nFmt("Halted after %n% steps with %k% ones on the tape.", { n: m.steps, k: countOnes(m) });
} else if (kind === 'budget') {
v.className = 'verdict warn';
v.textContent = i18nFmt("Still running after %n% steps (budget reached). It may halt later or never; no algorithm decides this for every machine.", { n: m.steps });
} else {
v.className = 'verdict';
v.textContent = "Press Step or Run. The machine will either halt or keep going.";
}
}
function paint() {
renderTape();
renderStats();
markRule();
if (m.halted) verdict('halt');
}
function stop() {
if (timer) { clearInterval(timer); timer = null; }
$('run').textContent = "Run";
}
function load(k) {
stop();
key = k;
m = makeMachine(k);
document.querySelectorAll('#presets button').forEach((b) => b.classList.toggle('active', b.dataset.p === k));
$('desc').textContent = NAMES[k];
renderRules();
verdict('ready');
paint();
}
function doStep() {
if (m.halted) return;
if (m.steps >= budget()) { verdict('budget'); return; }
stepMachine(m);
paint();
if (!m.halted && m.steps >= budget()) { stop(); verdict('budget'); }
}
function runTo(limit) {
while (!m.halted && m.steps < limit) stepMachine(m);
}
function toggleRun() {
if (timer) { stop(); return; }
if (m.halted) return;
$('run').textContent = "Pause";
timer = setInterval(() => {
const chunk = 1 + Math.floor(m.steps / 30);
runTo(Math.min(budget(), m.steps + chunk));
paint();
if (m.halted) stop();
else if (m.steps >= budget()) { stop(); verdict('budget'); }
}, 70);
}
function skip() {
stop();
runTo(budget());
paint();
if (!m.halted) verdict('budget');
}
document.querySelectorAll('#presets button').forEach((b) => b.addEventListener('click', () => load(b.dataset.p)));
$('step').addEventListener('click', () => { stop(); doStep(); });
$('run').addEventListener('click', toggleRun);
$('skip').addEventListener('click', skip);
$('reset').addEventListener('click', () => load(key));
$('budget').addEventListener('change', () => { if (!m.halted) verdict('ready'); });
load('inc');
Comments
Loading comments...