<p class="hint">The machine is in <b>one</b> of these states, but you are blindfolded: you don't know which. So every state is lit. Press <b>a</b> or <b>b</b> — each letter moves every possible state along its arrow — and watch the set of possibilities shrink. Can you squeeze it down to a single state?</p>
<div class="controls">
<label>States n
<select id="nsel">
<option value="4">4</option>
<option value="5" selected>5</option>
<option value="6">6</option>
<option value="7">7</option>
<option value="8">8</option>
</select>
</label>
<label>Automaton
<select id="modesel">
<option value="cerny" selected>Černý automaton C_n</option>
<option value="random">Random automaton</option>
</select>
</label>
<button id="newrand" type="button" class="ghost">New random</button>
</div>
<svg id="dia" viewBox="0 0 300 300" role="img" aria-label="Diagram of the automaton: states on a circle, red arrows for letter a, blue arrows for letter b"></svg>
<div class="legend"><span class="la">━ letter a</span> <span class="lb">━ letter b</span></div>
<div class="btns">
<button id="ka" type="button" class="letter la-btn">a</button>
<button id="kb" type="button" class="letter lb-btn">b</button>
<button id="undo" type="button" class="ghost">Undo</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
<div class="status" id="status"></div>
<div class="word"><span class="wl">Your word:</span> <span id="word"></span></div>
<div class="btns">
<button id="bfs" type="button">Find shortest reset word (BFS)</button>
<button id="play" type="button" class="ghost" disabled>Play it</button>
</div>
<div class="result" id="result"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .6rem; line-height: 1.45; }
.controls { display: flex; gap: .6rem; flex-wrap: wrap; align-items: center; font-size: .85rem; margin-bottom: .3rem; }
.controls select { font: inherit; margin-left: .3rem; padding: .2rem .3rem; border-radius: 6px; border: 1px solid #adb1b8; background: #fff; color: #222; }
#dia { width: 100%; max-width: 320px; display: block; margin: 0 auto; }
.node { stroke: #8a929c; stroke-width: 2; fill: #fff; transition: fill .25s, stroke .25s; }
.node.on { fill: #1d3557; stroke: #1d3557; }
.node.sync { fill: #0a7d33; stroke: #0a7d33; }
.nlabel { font: 700 13px ui-monospace, monospace; text-anchor: middle; dominant-baseline: central; fill: #1d3557; pointer-events: none; }
.nlabel.on { fill: #fff; }
.ea { stroke: #e63946; }
.eb { stroke: #1d6fb8; }
.edge { fill: none; stroke-width: 1.6; opacity: .75; }
.legend { text-align: center; font-size: .8rem; margin: .1rem 0 .4rem; }
.la { color: #e63946; font-weight: 600; }
.lb { color: #1d6fb8; font-weight: 600; margin-left: .8rem; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin: .35rem 0; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; 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; }
button.letter { font: 700 18px ui-monospace, monospace; min-width: 52px; }
button.la-btn { background: #e63946; border-color: #c92f3c; }
button.lb-btn { background: #1d6fb8; border-color: #175d9b; }
.status { font-size: .95rem; font-weight: 600; margin: .4rem 0 .2rem; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.word { font: 14px ui-monospace, monospace; word-break: break-all; min-height: 1.3em; color: #333; }
.word .wl { font-family: system-ui, sans-serif; color: #666; }
.result { font-size: .9rem; line-height: 1.45; margin-top: .3rem; min-height: 1.3em; }
.result code { font: 13px ui-monospace, monospace; background: #eef2f6; padding: 0 .25rem; border-radius: 4px; word-break: break-all; }
.result .tight { color: #c92f3c; font-weight: 600; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Černý automaton C_n: letter b rotates every state one step around the circle; letter a sends state 0 to state 1 and leaves all the others where they are.
function cernyDfa(n) {
const a = [], b = [];
for (let i = 0; i < n; i++) { a.push(i === 0 ? 1 : i); b.push((i + 1) % n); }
return { a, b };
}
// Random automaton: each letter sends each state to a uniformly random state (it may or may not be synchronizing).
function randomDfa(n) {
const r = () => Math.floor(Math.random() * n);
const a = [], b = [];
for (let i = 0; i < n; i++) { a.push(r()); b.push(r()); }
return { a, b };
}
// Image of a set of states (bitmask) under one letter.
function stepSet(d, n, mask, letter) {
let out = 0;
for (let i = 0; i < n; i++) if ((mask >> i) & 1) out |= 1 << d[letter][i];
return out;
}
// Breadth-first search over subsets, starting from the full set; the first singleton reached gives a shortest reset word.
function shortestReset(d, n) {
const full = (1 << n) - 1;
const parent = new Map([[full, null]]);
const queue = [full];
for (let h = 0; h < queue.length; h++) {
const S = queue[h];
if ((S & (S - 1)) === 0) {
let w = '', cur = S;
while (parent.get(cur)) { const [p, x] = parent.get(cur); w = x + w; cur = p; }
return { word: w, explored: parent.size };
}
for (const x of ['a', 'b']) {
const T = stepSet(d, n, S, x);
if (!parent.has(T)) { parent.set(T, [S, x]); queue.push(T); }
}
}
return { word: null, explored: parent.size };
}
// Interface state and drawing helpers.
let n = 5, mode = 'cerny', dfa = cernyDfa(5), S = 31, word = '', hist = [], timer = null, best = null;
const $ = (id) => document.getElementById(id);
const pop = (m) => { let c = 0; while (m) { c += m & 1; m >>= 1; } return c; };
const NS = 'http://www.w3.org/2000/svg';
const R = 110, C = 150, NR = 16;
const pos = (i) => { const t = -Math.PI / 2 + 2 * Math.PI * i / n; return [C + R * Math.cos(t), C + R * Math.sin(t)]; };
function el(tag, attrs) { const e = document.createElementNS(NS, tag); for (const k in attrs) e.setAttribute(k, attrs[k]); return e; }
// Draw the states on a circle with curved arrows: a bends one way, b the other.
function draw() {
const svg = $('dia'); svg.innerHTML = '';
const defs = el('defs', {});
for (const [id, col] of [['ma', '#e63946'], ['mb', '#1d6fb8']]) {
const m = el('marker', { id, viewBox: '0 0 10 10', refX: 9, refY: 5, markerWidth: 6, markerHeight: 6, orient: 'auto-start-reverse' });
m.appendChild(el('path', { d: 'M0,0 L10,5 L0,10 z', fill: col }));
defs.appendChild(m);
}
svg.appendChild(defs);
for (const x of ['a', 'b']) {
for (let i = 0; i < n; i++) {
const j = dfa[x][i];
const [x1, y1] = pos(i);
if (i === j) {
// Self-loop: a small circle just outside the node.
const t = -Math.PI / 2 + 2 * Math.PI * i / n + (x === 'a' ? -0.7 : 0.7);
const cx = x1 + (NR + 6) * Math.cos(t), cy = y1 + (NR + 6) * Math.sin(t);
svg.appendChild(el('circle', { cx, cy, r: 9, class: 'edge e' + x }));
continue;
}
const [x2, y2] = pos(j);
const mx = (x1 + x2) / 2, my = (y1 + y2) / 2;
const dx = x2 - x1, dy = y2 - y1, L = Math.hypot(dx, dy);
const off = (x === 'a' ? 1 : -1) * Math.min(28, L * 0.22);
const qx = mx - dy / L * off, qy = my + dx / L * off;
const sh = (px, py, tx, ty) => { const ux = tx - px, uy = ty - py, l = Math.hypot(ux, uy); return [px + ux / l * NR, py + uy / l * NR]; };
const [sx, sy] = sh(x1, y1, qx, qy), [ex, ey] = sh(x2, y2, qx, qy);
svg.appendChild(el('path', { d: `M${sx},${sy} Q${qx},${qy} ${ex},${ey}`, class: 'edge e' + x, 'marker-end': `url(#m${x})` }));
}
}
for (let i = 0; i < n; i++) {
const [px, py] = pos(i);
svg.appendChild(el('circle', { cx: px, cy: py, r: NR, class: 'node', id: 'n' + i }));
const t = el('text', { x: px, y: py, class: 'nlabel', id: 't' + i }); t.textContent = i;
svg.appendChild(t);
}
paint();
}
// Compact display: runs like bbbb become b with an exponent.
function compress(w) {
return w.replace(/(a+|b+)/g, (r) => r.length > 1 ? `${r[0]}<sup>${r.length}</sup>` : r);
}
function paint() {
const k = pop(S), single = k === 1;
for (let i = 0; i < n; i++) {
const on = (S >> i) & 1;
$('n' + i).setAttribute('class', 'node' + (on ? (single ? ' sync' : ' on') : ''));
$('t' + i).setAttribute('class', 'nlabel' + (on ? ' on' : ''));
}
const st = $('status');
if (single) {
st.textContent = i18nFmt("Synchronized! After %len% letters the machine is surely in state %q%, whatever state it started in.", { len: word.length, q: Math.log2(S) });
st.className = 'status ok';
} else {
st.textContent = i18nFmt("Possible states: %k% of %n% · letters typed: %len%", { k, n, len: word.length });
st.className = 'status';
}
$('word').innerHTML = word ? compress(word) : "(empty)";
}
function apply(x) {
hist.push(S); S = stepSet(dfa, n, S, x); word += x; paint();
}
function stopPlay() { if (timer) { clearInterval(timer); timer = null; } }
function restart() { stopPlay(); S = (1 << n) - 1; word = ''; hist = []; paint(); }
function rebuild() {
// New automaton: restart with every state possible and forget the previous search.
stopPlay();
dfa = mode === 'cerny' ? cernyDfa(n) : randomDfa(n);
best = null; $('play').disabled = true; $('result').innerHTML = '';
S = (1 << n) - 1; word = ''; hist = [];
draw();
}
function runBfs() {
stopPlay();
const res = shortestReset(dfa, n);
const bound = (n - 1) * (n - 1);
best = res.word;
if (best === null) {
$('result').textContent = i18nFmt("No reset word exists: this automaton is not synchronizing (%k% reachable subsets explored, none of them a single state). Try another random one.", { k: res.explored });
$('play').disabled = true;
return;
}
let html = i18nFmt("Shortest reset word: length %len% (Černý bound (n−1)² = %bound%; subsets explored: %k%).", { len: best.length, bound, k: res.explored });
html += ` <code>${best ? compress(best) : '—'}</code>`;
if (best.length === bound) html += ` <span class="tight">${"It hits the bound exactly — this is Černý's extreme family."}</span>`;
else html += ' ' + i18nFmt("That is %gap% letters below the bound.", { gap: bound - best.length });
$('result').innerHTML = html;
$('play').disabled = false;
}
function play() {
// Replay the shortest reset word letter by letter from the full set.
if (best === null) return;
restart();
let i = 0;
const delay = best.length > 30 ? 160 : 320;
timer = setInterval(() => {
if (i >= best.length) { stopPlay(); return; }
apply(best[i++]);
}, delay);
}
$('ka').addEventListener('click', () => { stopPlay(); apply('a'); });
$('kb').addEventListener('click', () => { stopPlay(); apply('b'); });
$('undo').addEventListener('click', () => { stopPlay(); if (hist.length) { S = hist.pop(); word = word.slice(0, -1); paint(); } });
$('reset').addEventListener('click', restart);
$('bfs').addEventListener('click', runBfs);
$('play').addEventListener('click', play);
$('nsel').addEventListener('change', (e) => { n = +e.target.value; rebuild(); });
$('modesel').addEventListener('change', (e) => { mode = e.target.value; rebuild(); });
$('newrand').addEventListener('click', () => { mode = 'random'; $('modesel').value = 'random'; rebuild(); });
// Keyboard shortcuts: the a and b keys apply letters.
document.addEventListener('keydown', (e) => {
if (e.target && e.target.tagName === 'SELECT') return;
if (e.key === 'a' || e.key === 'b') { stopPlay(); apply(e.key); }
});
rebuild();
<p class="hint">The machine is in <b>one</b> of these states, but you are blindfolded: you don't know which. So every state is lit. Press <b>a</b> or <b>b</b> — each letter moves every possible state along its arrow — and watch the set of possibilities shrink. Can you squeeze it down to a single state?</p>
<div class="controls">
<label>States n
<select id="nsel">
<option value="4">4</option>
<option value="5" selected>5</option>
<option value="6">6</option>
<option value="7">7</option>
<option value="8">8</option>
</select>
</label>
<label>Automaton
<select id="modesel">
<option value="cerny" selected>Černý automaton C_n</option>
<option value="random">Random automaton</option>
</select>
</label>
<button id="newrand" type="button" class="ghost">New random</button>
</div>
<svg id="dia" viewBox="0 0 300 300" role="img" aria-label="Diagram of the automaton: states on a circle, red arrows for letter a, blue arrows for letter b"></svg>
<div class="legend"><span class="la">━ letter a</span> <span class="lb">━ letter b</span></div>
<div class="btns">
<button id="ka" type="button" class="letter la-btn">a</button>
<button id="kb" type="button" class="letter lb-btn">b</button>
<button id="undo" type="button" class="ghost">Undo</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
<div class="status" id="status"></div>
<div class="word"><span class="wl">Your word:</span> <span id="word"></span></div>
<div class="btns">
<button id="bfs" type="button">Find shortest reset word (BFS)</button>
<button id="play" type="button" class="ghost" disabled>Play it</button>
</div>
<div class="result" id="result"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .6rem; line-height: 1.45; }
.controls { display: flex; gap: .6rem; flex-wrap: wrap; align-items: center; font-size: .85rem; margin-bottom: .3rem; }
.controls select { font: inherit; margin-left: .3rem; padding: .2rem .3rem; border-radius: 6px; border: 1px solid #adb1b8; background: #fff; color: #222; }
#dia { width: 100%; max-width: 320px; display: block; margin: 0 auto; }
.node { stroke: #8a929c; stroke-width: 2; fill: #fff; transition: fill .25s, stroke .25s; }
.node.on { fill: #1d3557; stroke: #1d3557; }
.node.sync { fill: #0a7d33; stroke: #0a7d33; }
.nlabel { font: 700 13px ui-monospace, monospace; text-anchor: middle; dominant-baseline: central; fill: #1d3557; pointer-events: none; }
.nlabel.on { fill: #fff; }
.ea { stroke: #e63946; }
.eb { stroke: #1d6fb8; }
.edge { fill: none; stroke-width: 1.6; opacity: .75; }
.legend { text-align: center; font-size: .8rem; margin: .1rem 0 .4rem; }
.la { color: #e63946; font-weight: 600; }
.lb { color: #1d6fb8; font-weight: 600; margin-left: .8rem; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin: .35rem 0; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; 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; }
button.letter { font: 700 18px ui-monospace, monospace; min-width: 52px; }
button.la-btn { background: #e63946; border-color: #c92f3c; }
button.lb-btn { background: #1d6fb8; border-color: #175d9b; }
.status { font-size: .95rem; font-weight: 600; margin: .4rem 0 .2rem; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.word { font: 14px ui-monospace, monospace; word-break: break-all; min-height: 1.3em; color: #333; }
.word .wl { font-family: system-ui, sans-serif; color: #666; }
.result { font-size: .9rem; line-height: 1.45; margin-top: .3rem; min-height: 1.3em; }
.result code { font: 13px ui-monospace, monospace; background: #eef2f6; padding: 0 .25rem; border-radius: 4px; word-break: break-all; }
.result .tight { color: #c92f3c; font-weight: 600; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Černý automaton C_n: letter b rotates every state one step around the circle; letter a sends state 0 to state 1 and leaves all the others where they are.
function cernyDfa(n) {
const a = [], b = [];
for (let i = 0; i < n; i++) { a.push(i === 0 ? 1 : i); b.push((i + 1) % n); }
return { a, b };
}
// Random automaton: each letter sends each state to a uniformly random state (it may or may not be synchronizing).
function randomDfa(n) {
const r = () => Math.floor(Math.random() * n);
const a = [], b = [];
for (let i = 0; i < n; i++) { a.push(r()); b.push(r()); }
return { a, b };
}
// Image of a set of states (bitmask) under one letter.
function stepSet(d, n, mask, letter) {
let out = 0;
for (let i = 0; i < n; i++) if ((mask >> i) & 1) out |= 1 << d[letter][i];
return out;
}
// Breadth-first search over subsets, starting from the full set; the first singleton reached gives a shortest reset word.
function shortestReset(d, n) {
const full = (1 << n) - 1;
const parent = new Map([[full, null]]);
const queue = [full];
for (let h = 0; h < queue.length; h++) {
const S = queue[h];
if ((S & (S - 1)) === 0) {
let w = '', cur = S;
while (parent.get(cur)) { const [p, x] = parent.get(cur); w = x + w; cur = p; }
return { word: w, explored: parent.size };
}
for (const x of ['a', 'b']) {
const T = stepSet(d, n, S, x);
if (!parent.has(T)) { parent.set(T, [S, x]); queue.push(T); }
}
}
return { word: null, explored: parent.size };
}
// Interface state and drawing helpers.
let n = 5, mode = 'cerny', dfa = cernyDfa(5), S = 31, word = '', hist = [], timer = null, best = null;
const $ = (id) => document.getElementById(id);
const pop = (m) => { let c = 0; while (m) { c += m & 1; m >>= 1; } return c; };
const NS = 'http://www.w3.org/2000/svg';
const R = 110, C = 150, NR = 16;
const pos = (i) => { const t = -Math.PI / 2 + 2 * Math.PI * i / n; return [C + R * Math.cos(t), C + R * Math.sin(t)]; };
function el(tag, attrs) { const e = document.createElementNS(NS, tag); for (const k in attrs) e.setAttribute(k, attrs[k]); return e; }
// Draw the states on a circle with curved arrows: a bends one way, b the other.
function draw() {
const svg = $('dia'); svg.innerHTML = '';
const defs = el('defs', {});
for (const [id, col] of [['ma', '#e63946'], ['mb', '#1d6fb8']]) {
const m = el('marker', { id, viewBox: '0 0 10 10', refX: 9, refY: 5, markerWidth: 6, markerHeight: 6, orient: 'auto-start-reverse' });
m.appendChild(el('path', { d: 'M0,0 L10,5 L0,10 z', fill: col }));
defs.appendChild(m);
}
svg.appendChild(defs);
for (const x of ['a', 'b']) {
for (let i = 0; i < n; i++) {
const j = dfa[x][i];
const [x1, y1] = pos(i);
if (i === j) {
// Self-loop: a small circle just outside the node.
const t = -Math.PI / 2 + 2 * Math.PI * i / n + (x === 'a' ? -0.7 : 0.7);
const cx = x1 + (NR + 6) * Math.cos(t), cy = y1 + (NR + 6) * Math.sin(t);
svg.appendChild(el('circle', { cx, cy, r: 9, class: 'edge e' + x }));
continue;
}
const [x2, y2] = pos(j);
const mx = (x1 + x2) / 2, my = (y1 + y2) / 2;
const dx = x2 - x1, dy = y2 - y1, L = Math.hypot(dx, dy);
const off = (x === 'a' ? 1 : -1) * Math.min(28, L * 0.22);
const qx = mx - dy / L * off, qy = my + dx / L * off;
const sh = (px, py, tx, ty) => { const ux = tx - px, uy = ty - py, l = Math.hypot(ux, uy); return [px + ux / l * NR, py + uy / l * NR]; };
const [sx, sy] = sh(x1, y1, qx, qy), [ex, ey] = sh(x2, y2, qx, qy);
svg.appendChild(el('path', { d: `M${sx},${sy} Q${qx},${qy} ${ex},${ey}`, class: 'edge e' + x, 'marker-end': `url(#m${x})` }));
}
}
for (let i = 0; i < n; i++) {
const [px, py] = pos(i);
svg.appendChild(el('circle', { cx: px, cy: py, r: NR, class: 'node', id: 'n' + i }));
const t = el('text', { x: px, y: py, class: 'nlabel', id: 't' + i }); t.textContent = i;
svg.appendChild(t);
}
paint();
}
// Compact display: runs like bbbb become b with an exponent.
function compress(w) {
return w.replace(/(a+|b+)/g, (r) => r.length > 1 ? `${r[0]}<sup>${r.length}</sup>` : r);
}
function paint() {
const k = pop(S), single = k === 1;
for (let i = 0; i < n; i++) {
const on = (S >> i) & 1;
$('n' + i).setAttribute('class', 'node' + (on ? (single ? ' sync' : ' on') : ''));
$('t' + i).setAttribute('class', 'nlabel' + (on ? ' on' : ''));
}
const st = $('status');
if (single) {
st.textContent = i18nFmt("Synchronized! After %len% letters the machine is surely in state %q%, whatever state it started in.", { len: word.length, q: Math.log2(S) });
st.className = 'status ok';
} else {
st.textContent = i18nFmt("Possible states: %k% of %n% · letters typed: %len%", { k, n, len: word.length });
st.className = 'status';
}
$('word').innerHTML = word ? compress(word) : "(empty)";
}
function apply(x) {
hist.push(S); S = stepSet(dfa, n, S, x); word += x; paint();
}
function stopPlay() { if (timer) { clearInterval(timer); timer = null; } }
function restart() { stopPlay(); S = (1 << n) - 1; word = ''; hist = []; paint(); }
function rebuild() {
// New automaton: restart with every state possible and forget the previous search.
stopPlay();
dfa = mode === 'cerny' ? cernyDfa(n) : randomDfa(n);
best = null; $('play').disabled = true; $('result').innerHTML = '';
S = (1 << n) - 1; word = ''; hist = [];
draw();
}
function runBfs() {
stopPlay();
const res = shortestReset(dfa, n);
const bound = (n - 1) * (n - 1);
best = res.word;
if (best === null) {
$('result').textContent = i18nFmt("No reset word exists: this automaton is not synchronizing (%k% reachable subsets explored, none of them a single state). Try another random one.", { k: res.explored });
$('play').disabled = true;
return;
}
let html = i18nFmt("Shortest reset word: length %len% (Černý bound (n−1)² = %bound%; subsets explored: %k%).", { len: best.length, bound, k: res.explored });
html += ` <code>${best ? compress(best) : '—'}</code>`;
if (best.length === bound) html += ` <span class="tight">${"It hits the bound exactly — this is Černý's extreme family."}</span>`;
else html += ' ' + i18nFmt("That is %gap% letters below the bound.", { gap: bound - best.length });
$('result').innerHTML = html;
$('play').disabled = false;
}
function play() {
// Replay the shortest reset word letter by letter from the full set.
if (best === null) return;
restart();
let i = 0;
const delay = best.length > 30 ? 160 : 320;
timer = setInterval(() => {
if (i >= best.length) { stopPlay(); return; }
apply(best[i++]);
}, delay);
}
$('ka').addEventListener('click', () => { stopPlay(); apply('a'); });
$('kb').addEventListener('click', () => { stopPlay(); apply('b'); });
$('undo').addEventListener('click', () => { stopPlay(); if (hist.length) { S = hist.pop(); word = word.slice(0, -1); paint(); } });
$('reset').addEventListener('click', restart);
$('bfs').addEventListener('click', runBfs);
$('play').addEventListener('click', play);
$('nsel').addEventListener('change', (e) => { n = +e.target.value; rebuild(); });
$('modesel').addEventListener('change', (e) => { mode = e.target.value; rebuild(); });
$('newrand').addEventListener('click', () => { mode = 'random'; $('modesel').value = 'random'; rebuild(); });
// Keyboard shortcuts: the a and b keys apply letters.
document.addEventListener('keydown', (e) => {
if (e.target && e.target.tagName === 'SELECT') return;
if (e.key === 'a' || e.key === 'b') { stopPlay(); apply(e.key); }
});
rebuild();
Two things to notice. First, the letter a is the only one that ever merges two states, and it only does so when both 0 and 1 are still lit — so you have to rotate the set with b until the right pair lines up, again and again. That is why Cn is so slow: the BFS button confirms its shortest reset word is (abn−1)n−2a, of length exactly (n−1)2. Second, switch to random automata: most of them synchronize, and they do so far below the bound. Nobody has ever found an automaton that beats (n−1)2 — but nobody can prove that none exists.
Comments
Loading comments...