<p class="hint">
A Büchi automaton accepts an ω-word iff an <b>accepting state</b> (double ring) is visited <b>infinitely often</b>. Step through the word and watch the visit counts.
</p>
<div id="automaton-vis"></div>
<div class="word-row">
<span class="label">ω-word:</span>
<span id="word-display"></span>
</div>
<div class="controls">
<button id="btn-step">Step →</button>
<button id="btn-run">Auto-run</button>
<button id="btn-reset" class="ghost">Reset</button>
</div>
<div id="verdict" class="verdict"></div>
<div class="legend">
<span class="legend-item"><span class="state-dot start"></span> Start</span>
<span class="legend-item"><span class="state-dot accept"></span> Accepting</span>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
/* Automaton SVG canvas */
#automaton-vis { width: 100%; max-width: 480px; margin: 0 auto; }
#automaton-vis svg { width: 100%; height: auto; overflow: visible; }
/* State circles */
.state-circle { fill: #e8eef3; stroke: #1d3557; stroke-width: 1.8; transition: fill .2s; }
.state-circle.active { fill: #1d3557; }
.state-circle.accept-ring { fill: none; stroke: #1d3557; stroke-width: 1.8; }
text.state-label { font: 600 13px system-ui, sans-serif; fill: #1d3557; dominant-baseline: central; text-anchor: middle; }
text.state-label.active { fill: #fff; }
text.visit-count { font: 600 10px system-ui, sans-serif; fill: #888; text-anchor: middle; }
text.visit-count.active { fill: #e8eef3; }
/* Arrows */
.arrow { fill: none; stroke: #6b8bad; stroke-width: 1.5; marker-end: url(#arrowhead); }
.arrow.active-edge { stroke: #1d3557; stroke-width: 2.2; }
.edge-label { font: 12px system-ui, sans-serif; fill: #1d3557; text-anchor: middle; }
/* Word display */
.word-row { display: flex; align-items: center; gap: .4rem; margin: .7rem 0 .5rem; flex-wrap: wrap; }
.label { font-size: .85rem; color: #666; }
.sym { display: inline-block; width: 22px; height: 22px; line-height: 22px; text-align: center;
font: 700 12px ui-monospace, monospace; border-radius: 4px; background: #e8eef3;
margin: 0 1px; border: 1px solid #cdd9e3; }
.sym.current { background: #1d3557; color: #fff; border-color: #1d3557; }
.sym.past { color: #aaa; background: #f4f6f8; border-color: #ddd; }
.sym.dots { background: none; border: none; color: #888; font-style: italic; }
/* Controls */
.controls { display: flex; gap: .5rem; flex-wrap: wrap; margin: .4rem 0; }
button { font: 600 14px 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: .5; cursor: default; }
/* Verdict */
.verdict { font-size: .95rem; font-weight: 600; min-height: 1.4em; margin: .35rem 0; }
.verdict.accept { color: #0a7d33; }
.verdict.reject { color: #c92f3c; }
.verdict.running { color: #555; }
/* Legend */
.legend { display: flex; gap: 1rem; font-size: .82rem; color: #555; margin-top: .3rem; }
.legend-item { display: flex; align-items: center; gap: .3rem; }
.state-dot { display: inline-block; width: 14px; height: 14px; border-radius: 50%; border: 1.5px solid #1d3557; }
.state-dot.start { background: #e8eef3; }
.state-dot.accept { background: #e8eef3; box-shadow: 0 0 0 2.5px #e8eef3, 0 0 0 4px #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Büchi automaton demo
// States: q0 (start), q1 (accepting), q2
// Alphabet: {a, b}
// Transitions:
// q0 --a--> q1
// q0 --b--> q2
// q1 --a--> q1
// q1 --b--> q0
// q2 --a--> q0
// q2 --b--> q2
// ω-word: "aab" + ("ab")* = a a b a b a b ...
// Trace: q0->q1->q1->q0->q1->q0->q1->...
// q1 (accepting) is visited at steps 1,2,4,6,... => infinitely often => ACCEPT
const STATES = ['q0','q1','q2'];
const START = 'q0';
const ACCEPT = new Set(['q1']);
const DELTA = {
q0: { a:'q1', b:'q2' },
q1: { a:'q1', b:'q0' },
q2: { a:'q0', b:'q2' }
};
// Infinite word: aab then (ab) repeating
const PREFIX = ['a','a','b'];
const CYCLE = ['a','b'];
function symbolAt(i){ return i < PREFIX.length ? PREFIX[i] : CYCLE[(i - PREFIX.length) % CYCLE.length]; }
const DISPLAY_LEN = 11; // symbols shown
let step = 0;
let current = START;
let visits = { q0: 0, q1: 0, q2: 0 };
let lastEdge = null; // {from, sym}
let timer = null;
// ── SVG layout ────────────────────────────────────────────────────────────
// q0: center-left, q1: center-right, q2: bottom-center
const POS = { q0:[80,80], q1:[280,80], q2:[180,210] };
const R = 28;
function buildSVG(){
const svg = document.createElementNS('http://www.w3.org/2000/svg','svg');
svg.setAttribute('viewBox','0 0 360 280');
// Arrowhead marker
const defs = document.createElementNS('http://www.w3.org/2000/svg','defs');
defs.innerHTML = `<marker id="arrowhead" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
<polygon points="0 0, 8 3, 0 6" fill="#6b8bad"/>
</marker>
<marker id="arrowhead-active" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
<polygon points="0 0, 8 3, 0 6" fill="#1d3557"/>
</marker>`;
svg.appendChild(defs);
// Draw edges first (so circles appear on top)
drawEdge(svg,'q0','q1','a', 0,-18); // q0->q1 (straight, offset up)
drawEdge(svg,'q1','q0','b', 0, 18); // q1->q0 (straight, offset down)
drawEdge(svg,'q0','q2','b',-18, 0); // q0->q2 (to lower-left)
drawEdge(svg,'q2','q0','a', 18, 0); // q2->q0
drawSelfLoop(svg,'q1','a'); // q1->q1 self-loop
drawSelfLoop(svg,'q2','b'); // q2->q2 self-loop
// Draw states
STATES.forEach(s => {
const [cx,cy] = POS[s];
// Outer circle
const c = document.createElementNS('http://www.w3.org/2000/svg','circle');
c.setAttribute('cx',cx); c.setAttribute('cy',cy); c.setAttribute('r',R);
c.setAttribute('class','state-circle'); c.id='circle-'+s;
svg.appendChild(c);
// Double ring for accepting
if(ACCEPT.has(s)){
const r2 = document.createElementNS('http://www.w3.org/2000/svg','circle');
r2.setAttribute('cx',cx); r2.setAttribute('cy',cy); r2.setAttribute('r',R-5);
r2.setAttribute('class','accept-ring'); svg.appendChild(r2);
}
// Start arrow for q0
if(s===START){
const arr = document.createElementNS('http://www.w3.org/2000/svg','line');
arr.setAttribute('x1',cx-R-22); arr.setAttribute('y1',cy);
arr.setAttribute('x2',cx-R-4); arr.setAttribute('y2',cy);
arr.setAttribute('class','arrow'); arr.setAttribute('marker-end','url(#arrowhead)');
svg.appendChild(arr);
}
// Label
const lbl = document.createElementNS('http://www.w3.org/2000/svg','text');
lbl.setAttribute('x',cx); lbl.setAttribute('y',cy-6);
lbl.setAttribute('class','state-label'); lbl.id='label-'+s;
lbl.textContent = s; svg.appendChild(lbl);
// Visit counter
const vc = document.createElementNS('http://www.w3.org/2000/svg','text');
vc.setAttribute('x',cx); vc.setAttribute('y',cy+9);
vc.setAttribute('class','visit-count'); vc.id='vc-'+s;
vc.textContent = '0 visits'; svg.appendChild(vc);
});
document.getElementById('automaton-vis').appendChild(svg);
}
function arrowPt(from, to, fromEdge){
// Returns start/end points on circle boundaries, with small perpendicular offset
const [fx,fy]=POS[from],[tx,ty]=POS[to];
const dx=tx-fx, dy=ty-fy, d=Math.sqrt(dx*dx+dy*dy);
const nx=-dy/d, ny=dx/d; // perpendicular unit
const ox=nx*fromEdge, oy=ny*fromEdge;
const sx=fx+ox, sy=fy+oy, ex=tx+ox, ey=ty+oy;
const angle=Math.atan2(ey-sy,ex-sx);
return {
x1: sx+Math.cos(angle)*R,
y1: sy+Math.sin(angle)*R,
x2: ex-Math.cos(angle)*(R+6),
y2: ey-Math.sin(angle)*(R+6),
mx: (sx+ex)/2, my: (sy+ey)/2
};
}
function drawEdge(svg, from, to, sym, ox, oy){
const {x1,y1,x2,y2,mx,my} = arrowPt(from,to,ox);
const path = document.createElementNS('http://www.w3.org/2000/svg','line');
path.setAttribute('x1',x1); path.setAttribute('y1',y1);
path.setAttribute('x2',x2); path.setAttribute('y2',y2);
path.setAttribute('class','arrow'); path.id='edge-'+from+'-'+to;
svg.appendChild(path);
const lbl = document.createElementNS('http://www.w3.org/2000/svg','text');
lbl.setAttribute('x',mx+oy*0.7); lbl.setAttribute('y',my-ox*0.7+5);
lbl.setAttribute('class','edge-label'); lbl.textContent=sym; svg.appendChild(lbl);
}
function drawSelfLoop(svg, state, sym){
const [cx,cy] = POS[state];
// small arc above the state
const lx = cx+R+4, ly = cy-R-4;
const path = document.createElementNS('http://www.w3.org/2000/svg','path');
const d = `M ${cx+R*0.5} ${cy-R*0.87} A 18 18 0 1 1 ${cx+R*0.87} ${cy-R*0.5}`;
path.setAttribute('d',d);
path.setAttribute('class','arrow'); path.setAttribute('marker-end','url(#arrowhead)');
path.id='edge-'+state+'-'+state;
svg.appendChild(path);
const lbl = document.createElementNS('http://www.w3.org/2000/svg','text');
lbl.setAttribute('x',cx+R+16); lbl.setAttribute('y',cy-R-10);
lbl.setAttribute('class','edge-label'); lbl.textContent=sym; svg.appendChild(lbl);
}
function updateVis(){
STATES.forEach(s=>{
const c = document.getElementById('circle-'+s);
const lbl = document.getElementById('label-'+s);
const vc = document.getElementById('vc-'+s);
const active = s===current;
c.classList.toggle('active', active);
lbl.classList.toggle('active', active);
vc.classList.toggle('active', active);
vc.textContent = fmt('%v% visit%v2%', { v: visits[s], v2: visits[s]!==1?'s':'' });
});
}
function updateWord(){
const wd = document.getElementById('word-display');
wd.innerHTML='';
for(let i=0;i<DISPLAY_LEN;i++){
const sp=document.createElement('span');
sp.className='sym'+(i===step-1?' past':i===step?' current':'');
sp.textContent=symbolAt(i);
wd.appendChild(sp);
}
const dots=document.createElement('span');
dots.className='sym dots'; dots.textContent='…'; wd.appendChild(dots);
}
function updateVerdict(){
const vd = document.getElementById('verdict');
const acceptCount = visits['q1'];
if(step===0){ vd.textContent=''; vd.className='verdict'; return; }
if(step < 6){ vd.className='verdict running';
vd.textContent=fmt('q1 visited %acceptCount% time%v%', { acceptCount: acceptCount, v: acceptCount!==1?'s so far. Keep stepping…':' so far. Keep stepping…' }); return; }
vd.className='verdict accept';
vd.textContent=fmt('q1 visited %acceptCount% times in %step% steps — it keeps recurring → ACCEPTED ✓', { acceptCount: acceptCount, step: step });
}
function doStep(){
const sym = symbolAt(step);
const next = DELTA[current][sym];
lastEdge = { from: current, to: next, sym };
current = next;
step++;
visits[current]++;
updateVis();
updateWord();
updateVerdict();
}
function resetAll(){
step=0; current=START; visits={q0:0,q1:0,q2:0}; lastEdge=null;
stopAuto();
updateVis(); updateWord();
const vd=document.getElementById('verdict'); vd.textContent=''; vd.className='verdict';
}
function stopAuto(){
if(timer){ clearInterval(timer); timer=null; }
document.getElementById('btn-run').textContent='Auto-run';
}
document.getElementById('btn-step').addEventListener('click',()=>{ stopAuto(); doStep(); });
document.getElementById('btn-reset').addEventListener('click',resetAll);
document.getElementById('btn-run').addEventListener('click',()=>{
if(timer){ stopAuto(); }
else {
document.getElementById('btn-run').textContent='Pause';
timer=setInterval(()=>{ doStep(); if(step>=20){ stopAuto(); } },600);
}
});
buildSVG();
updateVis();
updateWord();
<p class="hint">
A Büchi automaton accepts an ω-word iff an <b>accepting state</b> (double ring) is visited <b>infinitely often</b>. Step through the word and watch the visit counts.
</p>
<div id="automaton-vis"></div>
<div class="word-row">
<span class="label">ω-word:</span>
<span id="word-display"></span>
</div>
<div class="controls">
<button id="btn-step">Step →</button>
<button id="btn-run">Auto-run</button>
<button id="btn-reset" class="ghost">Reset</button>
</div>
<div id="verdict" class="verdict"></div>
<div class="legend">
<span class="legend-item"><span class="state-dot start"></span> Start</span>
<span class="legend-item"><span class="state-dot accept"></span> Accepting</span>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
/* Automaton SVG canvas */
#automaton-vis { width: 100%; max-width: 480px; margin: 0 auto; }
#automaton-vis svg { width: 100%; height: auto; overflow: visible; }
/* State circles */
.state-circle { fill: #e8eef3; stroke: #1d3557; stroke-width: 1.8; transition: fill .2s; }
.state-circle.active { fill: #1d3557; }
.state-circle.accept-ring { fill: none; stroke: #1d3557; stroke-width: 1.8; }
text.state-label { font: 600 13px system-ui, sans-serif; fill: #1d3557; dominant-baseline: central; text-anchor: middle; }
text.state-label.active { fill: #fff; }
text.visit-count { font: 600 10px system-ui, sans-serif; fill: #888; text-anchor: middle; }
text.visit-count.active { fill: #e8eef3; }
/* Arrows */
.arrow { fill: none; stroke: #6b8bad; stroke-width: 1.5; marker-end: url(#arrowhead); }
.arrow.active-edge { stroke: #1d3557; stroke-width: 2.2; }
.edge-label { font: 12px system-ui, sans-serif; fill: #1d3557; text-anchor: middle; }
/* Word display */
.word-row { display: flex; align-items: center; gap: .4rem; margin: .7rem 0 .5rem; flex-wrap: wrap; }
.label { font-size: .85rem; color: #666; }
.sym { display: inline-block; width: 22px; height: 22px; line-height: 22px; text-align: center;
font: 700 12px ui-monospace, monospace; border-radius: 4px; background: #e8eef3;
margin: 0 1px; border: 1px solid #cdd9e3; }
.sym.current { background: #1d3557; color: #fff; border-color: #1d3557; }
.sym.past { color: #aaa; background: #f4f6f8; border-color: #ddd; }
.sym.dots { background: none; border: none; color: #888; font-style: italic; }
/* Controls */
.controls { display: flex; gap: .5rem; flex-wrap: wrap; margin: .4rem 0; }
button { font: 600 14px 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: .5; cursor: default; }
/* Verdict */
.verdict { font-size: .95rem; font-weight: 600; min-height: 1.4em; margin: .35rem 0; }
.verdict.accept { color: #0a7d33; }
.verdict.reject { color: #c92f3c; }
.verdict.running { color: #555; }
/* Legend */
.legend { display: flex; gap: 1rem; font-size: .82rem; color: #555; margin-top: .3rem; }
.legend-item { display: flex; align-items: center; gap: .3rem; }
.state-dot { display: inline-block; width: 14px; height: 14px; border-radius: 50%; border: 1.5px solid #1d3557; }
.state-dot.start { background: #e8eef3; }
.state-dot.accept { background: #e8eef3; box-shadow: 0 0 0 2.5px #e8eef3, 0 0 0 4px #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Büchi automaton demo
// States: q0 (start), q1 (accepting), q2
// Alphabet: {a, b}
// Transitions:
// q0 --a--> q1
// q0 --b--> q2
// q1 --a--> q1
// q1 --b--> q0
// q2 --a--> q0
// q2 --b--> q2
// ω-word: "aab" + ("ab")* = a a b a b a b ...
// Trace: q0->q1->q1->q0->q1->q0->q1->...
// q1 (accepting) is visited at steps 1,2,4,6,... => infinitely often => ACCEPT
const STATES = ['q0','q1','q2'];
const START = 'q0';
const ACCEPT = new Set(['q1']);
const DELTA = {
q0: { a:'q1', b:'q2' },
q1: { a:'q1', b:'q0' },
q2: { a:'q0', b:'q2' }
};
// Infinite word: aab then (ab) repeating
const PREFIX = ['a','a','b'];
const CYCLE = ['a','b'];
function symbolAt(i){ return i < PREFIX.length ? PREFIX[i] : CYCLE[(i - PREFIX.length) % CYCLE.length]; }
const DISPLAY_LEN = 11; // symbols shown
let step = 0;
let current = START;
let visits = { q0: 0, q1: 0, q2: 0 };
let lastEdge = null; // {from, sym}
let timer = null;
// ── SVG layout ────────────────────────────────────────────────────────────
// q0: center-left, q1: center-right, q2: bottom-center
const POS = { q0:[80,80], q1:[280,80], q2:[180,210] };
const R = 28;
function buildSVG(){
const svg = document.createElementNS('http://www.w3.org/2000/svg','svg');
svg.setAttribute('viewBox','0 0 360 280');
// Arrowhead marker
const defs = document.createElementNS('http://www.w3.org/2000/svg','defs');
defs.innerHTML = `<marker id="arrowhead" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
<polygon points="0 0, 8 3, 0 6" fill="#6b8bad"/>
</marker>
<marker id="arrowhead-active" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
<polygon points="0 0, 8 3, 0 6" fill="#1d3557"/>
</marker>`;
svg.appendChild(defs);
// Draw edges first (so circles appear on top)
drawEdge(svg,'q0','q1','a', 0,-18); // q0->q1 (straight, offset up)
drawEdge(svg,'q1','q0','b', 0, 18); // q1->q0 (straight, offset down)
drawEdge(svg,'q0','q2','b',-18, 0); // q0->q2 (to lower-left)
drawEdge(svg,'q2','q0','a', 18, 0); // q2->q0
drawSelfLoop(svg,'q1','a'); // q1->q1 self-loop
drawSelfLoop(svg,'q2','b'); // q2->q2 self-loop
// Draw states
STATES.forEach(s => {
const [cx,cy] = POS[s];
// Outer circle
const c = document.createElementNS('http://www.w3.org/2000/svg','circle');
c.setAttribute('cx',cx); c.setAttribute('cy',cy); c.setAttribute('r',R);
c.setAttribute('class','state-circle'); c.id='circle-'+s;
svg.appendChild(c);
// Double ring for accepting
if(ACCEPT.has(s)){
const r2 = document.createElementNS('http://www.w3.org/2000/svg','circle');
r2.setAttribute('cx',cx); r2.setAttribute('cy',cy); r2.setAttribute('r',R-5);
r2.setAttribute('class','accept-ring'); svg.appendChild(r2);
}
// Start arrow for q0
if(s===START){
const arr = document.createElementNS('http://www.w3.org/2000/svg','line');
arr.setAttribute('x1',cx-R-22); arr.setAttribute('y1',cy);
arr.setAttribute('x2',cx-R-4); arr.setAttribute('y2',cy);
arr.setAttribute('class','arrow'); arr.setAttribute('marker-end','url(#arrowhead)');
svg.appendChild(arr);
}
// Label
const lbl = document.createElementNS('http://www.w3.org/2000/svg','text');
lbl.setAttribute('x',cx); lbl.setAttribute('y',cy-6);
lbl.setAttribute('class','state-label'); lbl.id='label-'+s;
lbl.textContent = s; svg.appendChild(lbl);
// Visit counter
const vc = document.createElementNS('http://www.w3.org/2000/svg','text');
vc.setAttribute('x',cx); vc.setAttribute('y',cy+9);
vc.setAttribute('class','visit-count'); vc.id='vc-'+s;
vc.textContent = '0 visits'; svg.appendChild(vc);
});
document.getElementById('automaton-vis').appendChild(svg);
}
function arrowPt(from, to, fromEdge){
// Returns start/end points on circle boundaries, with small perpendicular offset
const [fx,fy]=POS[from],[tx,ty]=POS[to];
const dx=tx-fx, dy=ty-fy, d=Math.sqrt(dx*dx+dy*dy);
const nx=-dy/d, ny=dx/d; // perpendicular unit
const ox=nx*fromEdge, oy=ny*fromEdge;
const sx=fx+ox, sy=fy+oy, ex=tx+ox, ey=ty+oy;
const angle=Math.atan2(ey-sy,ex-sx);
return {
x1: sx+Math.cos(angle)*R,
y1: sy+Math.sin(angle)*R,
x2: ex-Math.cos(angle)*(R+6),
y2: ey-Math.sin(angle)*(R+6),
mx: (sx+ex)/2, my: (sy+ey)/2
};
}
function drawEdge(svg, from, to, sym, ox, oy){
const {x1,y1,x2,y2,mx,my} = arrowPt(from,to,ox);
const path = document.createElementNS('http://www.w3.org/2000/svg','line');
path.setAttribute('x1',x1); path.setAttribute('y1',y1);
path.setAttribute('x2',x2); path.setAttribute('y2',y2);
path.setAttribute('class','arrow'); path.id='edge-'+from+'-'+to;
svg.appendChild(path);
const lbl = document.createElementNS('http://www.w3.org/2000/svg','text');
lbl.setAttribute('x',mx+oy*0.7); lbl.setAttribute('y',my-ox*0.7+5);
lbl.setAttribute('class','edge-label'); lbl.textContent=sym; svg.appendChild(lbl);
}
function drawSelfLoop(svg, state, sym){
const [cx,cy] = POS[state];
// small arc above the state
const lx = cx+R+4, ly = cy-R-4;
const path = document.createElementNS('http://www.w3.org/2000/svg','path');
const d = `M ${cx+R*0.5} ${cy-R*0.87} A 18 18 0 1 1 ${cx+R*0.87} ${cy-R*0.5}`;
path.setAttribute('d',d);
path.setAttribute('class','arrow'); path.setAttribute('marker-end','url(#arrowhead)');
path.id='edge-'+state+'-'+state;
svg.appendChild(path);
const lbl = document.createElementNS('http://www.w3.org/2000/svg','text');
lbl.setAttribute('x',cx+R+16); lbl.setAttribute('y',cy-R-10);
lbl.setAttribute('class','edge-label'); lbl.textContent=sym; svg.appendChild(lbl);
}
function updateVis(){
STATES.forEach(s=>{
const c = document.getElementById('circle-'+s);
const lbl = document.getElementById('label-'+s);
const vc = document.getElementById('vc-'+s);
const active = s===current;
c.classList.toggle('active', active);
lbl.classList.toggle('active', active);
vc.classList.toggle('active', active);
vc.textContent = fmt('%v% visit%v2%', { v: visits[s], v2: visits[s]!==1?'s':'' });
});
}
function updateWord(){
const wd = document.getElementById('word-display');
wd.innerHTML='';
for(let i=0;i<DISPLAY_LEN;i++){
const sp=document.createElement('span');
sp.className='sym'+(i===step-1?' past':i===step?' current':'');
sp.textContent=symbolAt(i);
wd.appendChild(sp);
}
const dots=document.createElement('span');
dots.className='sym dots'; dots.textContent='…'; wd.appendChild(dots);
}
function updateVerdict(){
const vd = document.getElementById('verdict');
const acceptCount = visits['q1'];
if(step===0){ vd.textContent=''; vd.className='verdict'; return; }
if(step < 6){ vd.className='verdict running';
vd.textContent=fmt('q1 visited %acceptCount% time%v%', { acceptCount: acceptCount, v: acceptCount!==1?'s so far. Keep stepping…':' so far. Keep stepping…' }); return; }
vd.className='verdict accept';
vd.textContent=fmt('q1 visited %acceptCount% times in %step% steps — it keeps recurring → ACCEPTED ✓', { acceptCount: acceptCount, step: step });
}
function doStep(){
const sym = symbolAt(step);
const next = DELTA[current][sym];
lastEdge = { from: current, to: next, sym };
current = next;
step++;
visits[current]++;
updateVis();
updateWord();
updateVerdict();
}
function resetAll(){
step=0; current=START; visits={q0:0,q1:0,q2:0}; lastEdge=null;
stopAuto();
updateVis(); updateWord();
const vd=document.getElementById('verdict'); vd.textContent=''; vd.className='verdict';
}
function stopAuto(){
if(timer){ clearInterval(timer); timer=null; }
document.getElementById('btn-run').textContent='Auto-run';
}
document.getElementById('btn-step').addEventListener('click',()=>{ stopAuto(); doStep(); });
document.getElementById('btn-reset').addEventListener('click',resetAll);
document.getElementById('btn-run').addEventListener('click',()=>{
if(timer){ stopAuto(); }
else {
document.getElementById('btn-run').textContent='Pause';
timer=setInterval(()=>{ doStep(); if(step>=20){ stopAuto(); } },600);
}
});
buildSVG();
updateVis();
updateWord();
Notice the asymmetry with finite automata. For a finite word you simply check where the machine ends up. Here, no single moment decides acceptance — only the long-run behavior matters. Change the word to one that never loops through q1 and the automaton rejects, even though it keeps running.
Comments
Loading comments...