<div class="controls">
<label for="dfaSelect">DFA:</label>
<select id="dfaSelect">
<option value="even_a">Even number of a’s (over {a,b})</option>
<option value="ends_01">Ends with 01 (over {0,1})</option>
<option value="div3">Binary number divisible by 3</option>
</select>
</div>
<div class="controls">
<label for="inputStr">Input string:</label>
<input id="inputStr" type="text" value="aaba" maxlength="20" spellcheck="false" autocomplete="off"/>
</div>
<div id="diagram"></div>
<div class="tape-row" id="tapeRow"></div>
<div class="status" id="status">Press Step or Run.</div>
<div class="btns">
<button id="btnStep">Step →</button>
<button id="btnRun">Run</button>
<button id="btnReset" class="ghost">Reset</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.controls { display: flex; align-items: center; gap: .5rem; margin-bottom: .45rem; flex-wrap: wrap; }
label { font-size: .85rem; font-weight: 600; color: #444; white-space: nowrap; }
select, input { font: .9rem system-ui, sans-serif; padding: .3rem .5rem; border: 1px solid #cdd9e3;
border-radius: 6px; background: #f5f8fa; color: #1d3557; }
input { width: 140px; letter-spacing: .05em; }
#diagram { display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
background: #f0f4f8; border-radius: 10px; padding: 10px 12px; margin: .5rem 0; min-height: 68px; }
.state-box { display: flex; flex-direction: column; align-items: center; gap: 3px; }
.state-circle { width: 46px; height: 46px; border-radius: 50%; border: 2px solid #4a6fa5;
display: flex; align-items: center; justify-content: center;
font: 700 14px ui-monospace, monospace; color: #1d3557;
background: #fff; transition: all .2s; }
.state-circle.accept { border-width: 4px; border-color: #0a7d33; }
.state-circle.current { background: #ffd166; border-color: #d4a000; color: #7a5500; transform: scale(1.13); }
.state-circle.current.accept { border-color: #0a7d33; }
.state-circle.dead { background: #fce8e8; border-color: #e63946; }
.state-label { font-size: .7rem; color: #666; max-width: 60px; text-align: center; word-break: break-word; }
.arrow { font-size: 1.1rem; color: #4a6fa5; flex-shrink: 0; }
.arrow-label { font-size: .75rem; color: #555; text-align: center; margin-top: -4px; }
.tape-row { display: flex; gap: 4px; margin: .4rem 0; flex-wrap: wrap; }
.sym { width: 32px; height: 32px; display: flex; align-items: center; justify-content: center;
font: 700 14px ui-monospace, monospace; border-radius: 6px;
background: #dde4ec; border: 1px solid #b8c4d0; color: #1d3557; }
.sym.read { background: #ffd166; border-color: #c8970a; color: #7a5500; }
.sym.past { background: #c5e8cc; border-color: #5aa96a; color: #1a5c2b; }
.status { font-size: 1rem; font-weight: 600; margin: .4rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; } .status.bad { color: #c92f3c; } .status.info { color: #1d3557; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
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; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Finite Automata demo: step through a DFA symbol by symbol.
// Each DFA is defined as { states, alphabet, start, accept, delta }.
// delta[state][symbol] -> nextState (undefined => dead/reject trap)
const DFAS = {
even_a: {
label: 'Even number of a’s',
states: ['q0','q1'],
stateLabels: { q0: 'even a', q1: 'odd a' },
alphabet: ['a','b'],
start: 'q0',
accept: new Set(['q0']),
delta: { q0: { a:'q1', b:'q0' }, q1: { a:'q0', b:'q1' } }
},
ends_01: {
label: 'Ends with 01',
states: ['A','B','C'],
stateLabels: { A: 'start', B: 'saw 0', C: 'saw 01' },
alphabet: ['0','1'],
start: 'A',
accept: new Set(['C']),
delta: { A: { '0':'B', '1':'A' }, B: { '0':'B', '1':'C' }, C: { '0':'B', '1':'A' } }
},
div3: {
label: 'Divisible by 3',
states: ['r0','r1','r2'],
stateLabels: { r0: 'rem 0', r1: 'rem 1', r2: 'rem 2' },
alphabet: ['0','1'],
start: 'r0',
accept: new Set(['r0']),
delta: { r0: { '0':'r0', '1':'r1' }, r1: { '0':'r2', '1':'r0' }, r2: { '0':'r1', '1':'r2' } }
}
};
let dfa, input, pos, cur, done;
function load() {
dfa = DFAS[document.getElementById('dfaSelect').value];
// set a sensible default input for the chosen DFA
const defaults = { even_a:'aaba', ends_01:'1001', div3:'1001' };
const sel = document.getElementById('dfaSelect').value;
if (!userTyped) document.getElementById('inputStr').value = defaults[sel] || '';
reset();
}
let userTyped = false;
document.getElementById('inputStr').addEventListener('input', () => { userTyped = true; reset(); });
document.getElementById('dfaSelect').addEventListener('change', () => { userTyped = false; load(); });
function reset() {
dfa = DFAS[document.getElementById('dfaSelect').value];
input = document.getElementById('inputStr').value.replace(/[^a-zA-Z01]/g,'');
pos = 0; cur = dfa.start; done = false;
renderDiagram(); renderTape(); setStatus('Press Step or Run.', 'info');
}
function renderDiagram() {
const d = document.getElementById('diagram'); d.innerHTML = '';
dfa.states.forEach((s, i) => {
const box = document.createElement('div'); box.className = 'state-box';
const circle = document.createElement('div');
circle.className = 'state-circle' +
(dfa.accept.has(s) ? ' accept' : '') +
(s === cur && !done ? ' current' : '') +
(s === cur && done && !dfa.accept.has(s) ? ' dead' : '');
circle.textContent = s;
const lbl = document.createElement('div'); lbl.className = 'state-label';
lbl.textContent = dfa.stateLabels[s] || '';
box.appendChild(circle); box.appendChild(lbl); d.appendChild(box);
if (i < dfa.states.length - 1) {
const arr = document.createElement('div'); arr.className = 'state-box';
const a = document.createElement('div'); a.className = 'arrow'; a.textContent = '→';
arr.appendChild(a); d.appendChild(arr);
}
});
}
function renderTape() {
const row = document.getElementById('tapeRow'); row.innerHTML = '';
[...input].forEach((ch, i) => {
const sym = document.createElement('div');
sym.className = 'sym' + (i === pos && !done ? ' read' : i < pos ? ' past' : '');
sym.textContent = ch; row.appendChild(sym);
});
}
function setStatus(t, cls) {
const s = document.getElementById('status'); s.textContent = t; s.className = 'status ' + cls;
}
function step() {
if (done) return;
if (pos >= input.length) { finish(); return; }
const sym = input[pos];
const next = dfa.delta[cur] && dfa.delta[cur][sym];
if (!next) { // dead state (should not happen for complete DFAs, but guard anyway)
done = true;
setStatus(fmt('✗ No transition on ""%sym%"" from %cur% — rejected.', { sym: sym, cur: cur }), 'bad');
return;
}
cur = next; pos++;
renderDiagram(); renderTape();
if (pos >= input.length) { finish(); return; }
setStatus(fmt('Read ""%sym%"" → now in %cur%', { sym: sym, cur: cur }), 'info');
}
function finish() {
done = true;
renderDiagram();
if (dfa.accept.has(cur))
setStatus(fmt('✓ Accepted — ended in %cur% (accepting state).', { cur: cur }), 'ok');
else
setStatus(fmt('✗ Rejected — ended in %cur% (non-accepting state).', { cur: cur }), 'bad');
}
function run() {
if (done) reset();
// step through all remaining symbols
while (!done) step();
}
document.getElementById('btnStep').addEventListener('click', step);
document.getElementById('btnRun').addEventListener('click', run);
document.getElementById('btnReset').addEventListener('click', reset);
load();
<div class="controls">
<label for="dfaSelect">DFA:</label>
<select id="dfaSelect">
<option value="even_a">Even number of a’s (over {a,b})</option>
<option value="ends_01">Ends with 01 (over {0,1})</option>
<option value="div3">Binary number divisible by 3</option>
</select>
</div>
<div class="controls">
<label for="inputStr">Input string:</label>
<input id="inputStr" type="text" value="aaba" maxlength="20" spellcheck="false" autocomplete="off"/>
</div>
<div id="diagram"></div>
<div class="tape-row" id="tapeRow"></div>
<div class="status" id="status">Press Step or Run.</div>
<div class="btns">
<button id="btnStep">Step →</button>
<button id="btnRun">Run</button>
<button id="btnReset" class="ghost">Reset</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.controls { display: flex; align-items: center; gap: .5rem; margin-bottom: .45rem; flex-wrap: wrap; }
label { font-size: .85rem; font-weight: 600; color: #444; white-space: nowrap; }
select, input { font: .9rem system-ui, sans-serif; padding: .3rem .5rem; border: 1px solid #cdd9e3;
border-radius: 6px; background: #f5f8fa; color: #1d3557; }
input { width: 140px; letter-spacing: .05em; }
#diagram { display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
background: #f0f4f8; border-radius: 10px; padding: 10px 12px; margin: .5rem 0; min-height: 68px; }
.state-box { display: flex; flex-direction: column; align-items: center; gap: 3px; }
.state-circle { width: 46px; height: 46px; border-radius: 50%; border: 2px solid #4a6fa5;
display: flex; align-items: center; justify-content: center;
font: 700 14px ui-monospace, monospace; color: #1d3557;
background: #fff; transition: all .2s; }
.state-circle.accept { border-width: 4px; border-color: #0a7d33; }
.state-circle.current { background: #ffd166; border-color: #d4a000; color: #7a5500; transform: scale(1.13); }
.state-circle.current.accept { border-color: #0a7d33; }
.state-circle.dead { background: #fce8e8; border-color: #e63946; }
.state-label { font-size: .7rem; color: #666; max-width: 60px; text-align: center; word-break: break-word; }
.arrow { font-size: 1.1rem; color: #4a6fa5; flex-shrink: 0; }
.arrow-label { font-size: .75rem; color: #555; text-align: center; margin-top: -4px; }
.tape-row { display: flex; gap: 4px; margin: .4rem 0; flex-wrap: wrap; }
.sym { width: 32px; height: 32px; display: flex; align-items: center; justify-content: center;
font: 700 14px ui-monospace, monospace; border-radius: 6px;
background: #dde4ec; border: 1px solid #b8c4d0; color: #1d3557; }
.sym.read { background: #ffd166; border-color: #c8970a; color: #7a5500; }
.sym.past { background: #c5e8cc; border-color: #5aa96a; color: #1a5c2b; }
.status { font-size: 1rem; font-weight: 600; margin: .4rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; } .status.bad { color: #c92f3c; } .status.info { color: #1d3557; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
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; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Finite Automata demo: step through a DFA symbol by symbol.
// Each DFA is defined as { states, alphabet, start, accept, delta }.
// delta[state][symbol] -> nextState (undefined => dead/reject trap)
const DFAS = {
even_a: {
label: 'Even number of a’s',
states: ['q0','q1'],
stateLabels: { q0: 'even a', q1: 'odd a' },
alphabet: ['a','b'],
start: 'q0',
accept: new Set(['q0']),
delta: { q0: { a:'q1', b:'q0' }, q1: { a:'q0', b:'q1' } }
},
ends_01: {
label: 'Ends with 01',
states: ['A','B','C'],
stateLabels: { A: 'start', B: 'saw 0', C: 'saw 01' },
alphabet: ['0','1'],
start: 'A',
accept: new Set(['C']),
delta: { A: { '0':'B', '1':'A' }, B: { '0':'B', '1':'C' }, C: { '0':'B', '1':'A' } }
},
div3: {
label: 'Divisible by 3',
states: ['r0','r1','r2'],
stateLabels: { r0: 'rem 0', r1: 'rem 1', r2: 'rem 2' },
alphabet: ['0','1'],
start: 'r0',
accept: new Set(['r0']),
delta: { r0: { '0':'r0', '1':'r1' }, r1: { '0':'r2', '1':'r0' }, r2: { '0':'r1', '1':'r2' } }
}
};
let dfa, input, pos, cur, done;
function load() {
dfa = DFAS[document.getElementById('dfaSelect').value];
// set a sensible default input for the chosen DFA
const defaults = { even_a:'aaba', ends_01:'1001', div3:'1001' };
const sel = document.getElementById('dfaSelect').value;
if (!userTyped) document.getElementById('inputStr').value = defaults[sel] || '';
reset();
}
let userTyped = false;
document.getElementById('inputStr').addEventListener('input', () => { userTyped = true; reset(); });
document.getElementById('dfaSelect').addEventListener('change', () => { userTyped = false; load(); });
function reset() {
dfa = DFAS[document.getElementById('dfaSelect').value];
input = document.getElementById('inputStr').value.replace(/[^a-zA-Z01]/g,'');
pos = 0; cur = dfa.start; done = false;
renderDiagram(); renderTape(); setStatus('Press Step or Run.', 'info');
}
function renderDiagram() {
const d = document.getElementById('diagram'); d.innerHTML = '';
dfa.states.forEach((s, i) => {
const box = document.createElement('div'); box.className = 'state-box';
const circle = document.createElement('div');
circle.className = 'state-circle' +
(dfa.accept.has(s) ? ' accept' : '') +
(s === cur && !done ? ' current' : '') +
(s === cur && done && !dfa.accept.has(s) ? ' dead' : '');
circle.textContent = s;
const lbl = document.createElement('div'); lbl.className = 'state-label';
lbl.textContent = dfa.stateLabels[s] || '';
box.appendChild(circle); box.appendChild(lbl); d.appendChild(box);
if (i < dfa.states.length - 1) {
const arr = document.createElement('div'); arr.className = 'state-box';
const a = document.createElement('div'); a.className = 'arrow'; a.textContent = '→';
arr.appendChild(a); d.appendChild(arr);
}
});
}
function renderTape() {
const row = document.getElementById('tapeRow'); row.innerHTML = '';
[...input].forEach((ch, i) => {
const sym = document.createElement('div');
sym.className = 'sym' + (i === pos && !done ? ' read' : i < pos ? ' past' : '');
sym.textContent = ch; row.appendChild(sym);
});
}
function setStatus(t, cls) {
const s = document.getElementById('status'); s.textContent = t; s.className = 'status ' + cls;
}
function step() {
if (done) return;
if (pos >= input.length) { finish(); return; }
const sym = input[pos];
const next = dfa.delta[cur] && dfa.delta[cur][sym];
if (!next) { // dead state (should not happen for complete DFAs, but guard anyway)
done = true;
setStatus(fmt('✗ No transition on ""%sym%"" from %cur% — rejected.', { sym: sym, cur: cur }), 'bad');
return;
}
cur = next; pos++;
renderDiagram(); renderTape();
if (pos >= input.length) { finish(); return; }
setStatus(fmt('Read ""%sym%"" → now in %cur%', { sym: sym, cur: cur }), 'info');
}
function finish() {
done = true;
renderDiagram();
if (dfa.accept.has(cur))
setStatus(fmt('✓ Accepted — ended in %cur% (accepting state).', { cur: cur }), 'ok');
else
setStatus(fmt('✗ Rejected — ended in %cur% (non-accepting state).', { cur: cur }), 'bad');
}
function run() {
if (done) reset();
// step through all remaining symbols
while (!done) step();
}
document.getElementById('btnStep').addEventListener('click', step);
document.getElementById('btnRun').addEventListener('click', run);
document.getElementById('btnReset').addEventListener('click', reset);
load();
Notice what happens when the string is rejected: the machine did not "get stuck" — it simply ended in a non-accepting state. The DFA always reads every symbol; there is no early exit. That is the nature of determinism: for every (state, symbol) pair there is exactly one next state, so the path is always fully determined.
Comments
Loading comments...