Here is a small quantified formula. The quantifier prefix ∃a ∀b ∃c sets the turn order: you choose a, then the opponent chooses b, then you choose c. The formula at the bottom decides the winner — if it ends up true, you win that leaf.
<p class="hint">The prefix <b>∃a ∀b ∃c</b> sets the turn order. <b>You</b> pick a (∃), the <b>opponent</b> picks b (∀), then <b>you</b> pick c (∃). The formula <b>φ = (¬a ∨ ¬c) ∧ (b ∨ ¬c) ∧ (¬b ∨ c)</b> at each leaf decides the winner — true means you win. The QBF is true iff you have a winning strategy.</p>
<div class="prefix" id="prefix"></div>
<div class="tree" id="tree"></div>
<div class="status" id="status">Pick a value for a to start playing.</div>
<div class="btns">
<button id="solve" type="button">Solve (minimax)</button>
<button id="reset" type="button" class="ghost">Reset</button>
</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 .7rem; line-height: 1.45; }
.prefix { font: 700 15px ui-monospace, monospace; color: #1d3557; margin: .2rem 0 .6rem; }
.tree { display: flex; flex-direction: column; gap: .55rem; margin: .3rem 0; }
.level { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.tag { font: 700 13px ui-monospace, monospace; width: 86px; flex: 0 0 86px; }
.tag.you { color: #0a7d33; }
.tag.opp { color: #c92f3c; }
.opt { font: 700 14px system-ui, sans-serif; padding: .35rem .8rem; border-radius: 8px;
border: 1px solid #adb1b8; background: #eef1f4; cursor: pointer; user-select: none; }
.opt:hover { background: #e2e6eb; }
.opt.sel.you { background: #0a7d33; color: #fff; border-color: #086628; }
.opt.sel.opp { background: #c92f3c; color: #fff; border-color: #a8222e; }
.opt.dim { opacity: .4; pointer-events: none; }
.leaf { font: 700 15px ui-monospace, monospace; padding: .35rem .8rem; border-radius: 8px;
border: 1px solid #cdd9e3; background: #e8eef3; color: #1d3557; }
.leaf.win { background: #0a7d33; color: #fff; border-color: #086628; }
.leaf.lose { background: #c92f3c; color: #fff; border-color: #a8222e; }
.status { font-size: 1rem; font-weight: 600; margin: .6rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
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; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// QBF: ∃a ∀b ∃c . φ(a,b,c), with
// φ = (¬a ∨ ¬c) ∧ (b ∨ ¬c) ∧ (¬b ∨ c)
// The formula is TRUE (you have a winning strategy) iff:
// OR over a ( AND over b ( OR over c ( φ ) ) )
// because ∃ = OR (your move, you pick the best) and ∀ = AND (opponent move,
// it must hold for every choice). Depth-first evaluation keeps only the
// current a,b,c on the stack -> polynomial space, even with 2^n leaves.
// Here the QBF is TRUE: play a = F, then answer b with c = b. (a = T loses.)
function phi(a,b,c){ return (!a||!c) && (b||!c) && (!b||c); }
// Player's partial choices: null until chosen.
let pick = { a: null, b: null, c: null };
function render(){
const tree = document.getElementById('tree'); tree.innerHTML = '';
document.getElementById('prefix').textContent =
'∃a ∀b ∃c . φ = (¬a ∨ ¬c) ∧ (b ∨ ¬c) ∧ (¬b ∨ c)';
addLevel('you', '∃ a (you)', 'a', pick.a, true);
addLevel('opp', '∀ b (opp)', 'b', pick.b, pick.a !== null);
addLevel('you', '∃ c (you)', 'c', pick.c, pick.b !== null);
const leaf = document.createElement('div');
leaf.className = 'level';
const tag = document.createElement('span'); tag.className = 'tag'; tag.textContent = 'φ =';
leaf.appendChild(tag);
if (pick.a !== null && pick.b !== null && pick.c !== null){
const v = phi(pick.a, pick.b, pick.c);
const lf = document.createElement('span');
lf.className = 'leaf ' + (v ? 'win' : 'lose');
lf.textContent = v ? 'TRUE → you win this leaf' : 'FALSE → you lose this leaf';
leaf.appendChild(lf);
} else {
const lf = document.createElement('span'); lf.className = 'leaf'; lf.textContent = '?';
leaf.appendChild(lf);
}
tree.appendChild(leaf);
}
function addLevel(side, label, key, chosen, active){
const tree = document.getElementById('tree');
const row = document.createElement('div'); row.className = 'level';
const tag = document.createElement('span'); tag.className = 'tag ' + side; tag.textContent = label;
row.appendChild(tag);
[false, true].forEach(val => {
const o = document.createElement('div');
o.className = 'opt ' + side + (chosen === val ? ' sel' : '') + (active ? '' : ' dim');
o.textContent = key + ' = ' + (val ? 'T' : 'F');
o.addEventListener('click', () => {
pick[key] = val;
if (key === 'a') { pick.b = null; pick.c = null; }
if (key === 'b') { pick.c = null; }
render(); judge();
});
row.appendChild(o);
});
tree.appendChild(row);
}
function setStatus(t, cls){ const s = document.getElementById('status'); s.textContent = t; s.className = 'status ' + cls; }
function judge(){
if (pick.a === null) { setStatus('Pick a value for a to start playing.', ''); return; }
if (pick.b === null) { setStatus('Opponent (∀) now picks b — and must lose for EVERY b.', ''); return; }
if (pick.c === null) { setStatus('Now pick c (∃) to try to make φ true.', ''); return; }
const v = phi(pick.a, pick.b, pick.c);
setStatus(v ? '✓ This leaf is TRUE — you survived this line.'
: '✗ This leaf is FALSE — this line loses. Try another c.', v ? 'ok' : 'bad');
}
// Full minimax: ∃ = OR, ∀ = AND. Returns boolean truth of the QBF.
function solve(){
let leaves = 0;
const existsA = [false,true].some(a => // ∃a
[false,true].every(b => // ∀b
[false,true].some(c => { leaves++; return phi(a,b,c); }) // ∃c
)
);
// Find a winning 'a' to display, if any.
let winA = null;
for (const a of [false,true]) {
const ok = [false,true].every(b => [false,true].some(c => phi(a,b,c)));
if (ok) { winA = a; break; }
}
if (existsA) {
setStatus(fmt('QBF is TRUE. Winning first move: a = %v%. Evaluated %leaves% leaves (≤ 2³ = 8).', { v: winA?'T':'F', leaves: leaves }), 'ok');
console.log('QBF true; winning a =', winA, '| leaves checked =', leaves);
} else {
setStatus(fmt('QBF is FALSE. No first move wins against every b. Leaves: %leaves%.', { leaves: leaves }), 'bad');
console.log('QBF false | leaves checked =', leaves);
}
}
document.getElementById('solve').addEventListener('click', solve);
document.getElementById('reset').addEventListener('click', () => {
pick = { a: null, b: null, c: null }; render(); judge();
});
render(); judge();
<p class="hint">The prefix <b>∃a ∀b ∃c</b> sets the turn order. <b>You</b> pick a (∃), the <b>opponent</b> picks b (∀), then <b>you</b> pick c (∃). The formula <b>φ = (¬a ∨ ¬c) ∧ (b ∨ ¬c) ∧ (¬b ∨ c)</b> at each leaf decides the winner — true means you win. The QBF is true iff you have a winning strategy.</p>
<div class="prefix" id="prefix"></div>
<div class="tree" id="tree"></div>
<div class="status" id="status">Pick a value for a to start playing.</div>
<div class="btns">
<button id="solve" type="button">Solve (minimax)</button>
<button id="reset" type="button" class="ghost">Reset</button>
</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 .7rem; line-height: 1.45; }
.prefix { font: 700 15px ui-monospace, monospace; color: #1d3557; margin: .2rem 0 .6rem; }
.tree { display: flex; flex-direction: column; gap: .55rem; margin: .3rem 0; }
.level { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.tag { font: 700 13px ui-monospace, monospace; width: 86px; flex: 0 0 86px; }
.tag.you { color: #0a7d33; }
.tag.opp { color: #c92f3c; }
.opt { font: 700 14px system-ui, sans-serif; padding: .35rem .8rem; border-radius: 8px;
border: 1px solid #adb1b8; background: #eef1f4; cursor: pointer; user-select: none; }
.opt:hover { background: #e2e6eb; }
.opt.sel.you { background: #0a7d33; color: #fff; border-color: #086628; }
.opt.sel.opp { background: #c92f3c; color: #fff; border-color: #a8222e; }
.opt.dim { opacity: .4; pointer-events: none; }
.leaf { font: 700 15px ui-monospace, monospace; padding: .35rem .8rem; border-radius: 8px;
border: 1px solid #cdd9e3; background: #e8eef3; color: #1d3557; }
.leaf.win { background: #0a7d33; color: #fff; border-color: #086628; }
.leaf.lose { background: #c92f3c; color: #fff; border-color: #a8222e; }
.status { font-size: 1rem; font-weight: 600; margin: .6rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
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; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// QBF: ∃a ∀b ∃c . φ(a,b,c), with
// φ = (¬a ∨ ¬c) ∧ (b ∨ ¬c) ∧ (¬b ∨ c)
// The formula is TRUE (you have a winning strategy) iff:
// OR over a ( AND over b ( OR over c ( φ ) ) )
// because ∃ = OR (your move, you pick the best) and ∀ = AND (opponent move,
// it must hold for every choice). Depth-first evaluation keeps only the
// current a,b,c on the stack -> polynomial space, even with 2^n leaves.
// Here the QBF is TRUE: play a = F, then answer b with c = b. (a = T loses.)
function phi(a,b,c){ return (!a||!c) && (b||!c) && (!b||c); }
// Player's partial choices: null until chosen.
let pick = { a: null, b: null, c: null };
function render(){
const tree = document.getElementById('tree'); tree.innerHTML = '';
document.getElementById('prefix').textContent =
'∃a ∀b ∃c . φ = (¬a ∨ ¬c) ∧ (b ∨ ¬c) ∧ (¬b ∨ c)';
addLevel('you', '∃ a (you)', 'a', pick.a, true);
addLevel('opp', '∀ b (opp)', 'b', pick.b, pick.a !== null);
addLevel('you', '∃ c (you)', 'c', pick.c, pick.b !== null);
const leaf = document.createElement('div');
leaf.className = 'level';
const tag = document.createElement('span'); tag.className = 'tag'; tag.textContent = 'φ =';
leaf.appendChild(tag);
if (pick.a !== null && pick.b !== null && pick.c !== null){
const v = phi(pick.a, pick.b, pick.c);
const lf = document.createElement('span');
lf.className = 'leaf ' + (v ? 'win' : 'lose');
lf.textContent = v ? 'TRUE → you win this leaf' : 'FALSE → you lose this leaf';
leaf.appendChild(lf);
} else {
const lf = document.createElement('span'); lf.className = 'leaf'; lf.textContent = '?';
leaf.appendChild(lf);
}
tree.appendChild(leaf);
}
function addLevel(side, label, key, chosen, active){
const tree = document.getElementById('tree');
const row = document.createElement('div'); row.className = 'level';
const tag = document.createElement('span'); tag.className = 'tag ' + side; tag.textContent = label;
row.appendChild(tag);
[false, true].forEach(val => {
const o = document.createElement('div');
o.className = 'opt ' + side + (chosen === val ? ' sel' : '') + (active ? '' : ' dim');
o.textContent = key + ' = ' + (val ? 'T' : 'F');
o.addEventListener('click', () => {
pick[key] = val;
if (key === 'a') { pick.b = null; pick.c = null; }
if (key === 'b') { pick.c = null; }
render(); judge();
});
row.appendChild(o);
});
tree.appendChild(row);
}
function setStatus(t, cls){ const s = document.getElementById('status'); s.textContent = t; s.className = 'status ' + cls; }
function judge(){
if (pick.a === null) { setStatus('Pick a value for a to start playing.', ''); return; }
if (pick.b === null) { setStatus('Opponent (∀) now picks b — and must lose for EVERY b.', ''); return; }
if (pick.c === null) { setStatus('Now pick c (∃) to try to make φ true.', ''); return; }
const v = phi(pick.a, pick.b, pick.c);
setStatus(v ? '✓ This leaf is TRUE — you survived this line.'
: '✗ This leaf is FALSE — this line loses. Try another c.', v ? 'ok' : 'bad');
}
// Full minimax: ∃ = OR, ∀ = AND. Returns boolean truth of the QBF.
function solve(){
let leaves = 0;
const existsA = [false,true].some(a => // ∃a
[false,true].every(b => // ∀b
[false,true].some(c => { leaves++; return phi(a,b,c); }) // ∃c
)
);
// Find a winning 'a' to display, if any.
let winA = null;
for (const a of [false,true]) {
const ok = [false,true].every(b => [false,true].some(c => phi(a,b,c)));
if (ok) { winA = a; break; }
}
if (existsA) {
setStatus(fmt('QBF is TRUE. Winning first move: a = %v%. Evaluated %leaves% leaves (≤ 2³ = 8).', { v: winA?'T':'F', leaves: leaves }), 'ok');
console.log('QBF true; winning a =', winA, '| leaves checked =', leaves);
} else {
setStatus(fmt('QBF is FALSE. No first move wins against every b. Leaves: %leaves%.', { leaves: leaves }), 'bad');
console.log('QBF false | leaves checked =', leaves);
}
}
document.getElementById('solve').addEventListener('click', solve);
document.getElementById('reset').addEventListener('click', () => {
pick = { a: null, b: null, c: null }; render(); judge();
});
render(); judge();
Comments
Loading comments...