Flip the variables and watch the clauses light up. Notice how easy it is to check a guess — one glance tells you which clauses are red. The hard part is finding an assignment that turns them all green, and with n variables there are 2n assignments to consider. Stuck? Press Brute-force search to try them one by one.
<p class="hint">A logic formula is a big <b>AND</b> of clauses; each clause is an <b>OR</b> of
variables (some negated, shown with ¬). The formula is satisfied only when <b>every clause</b>
is true. Flip the variables below and watch the clauses turn green. Can you make them all green?</p>
<div id="formula" class="formula"></div>
<div class="vars" id="vars"></div>
<p id="status" class="status"></p>
<div class="bar-btns">
<button id="solve" type="button">Brute-force search</button>
<button id="reset" type="button" class="ghost">All false</button>
</div>
<p id="note" class="note"></p>
* { 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; }
.formula { display: flex; flex-wrap: wrap; align-items: center; gap: .35rem; margin: .5rem 0; font: 600 15px ui-monospace, monospace; }
.clause { padding: .3rem .55rem; border-radius: 8px; border: 2px solid #ccc; background: #f4f4f4; transition: all .12s; white-space: nowrap; }
.clause.sat { background: #e7f6ee; border-color: #2a9d8f; color: #0a7d33; }
.clause.unsat { background: #fdecec; border-color: #e63946; color: #c0392b; }
.and { color: #999; font-weight: 700; padding: 0 .1rem; }
.vars { display: flex; flex-wrap: wrap; gap: .5rem; margin: .6rem 0; }
.var { padding: .45rem .8rem; border-radius: 8px; border: 2px solid #bbb; background: #fff; cursor: pointer; font: 700 15px ui-monospace, monospace; min-width: 70px; transition: all .12s; }
.var.t { background: #457b9d; color: #fff; border-color: #457b9d; }
.var.f { background: #eee; color: #555; }
.status { font-size: 1rem; font-weight: 700; min-height: 1.3em; margin: .3rem 0; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c0392b; }
.bar-btns { display: flex; gap: .5rem; flex-wrap: wrap; margin: .3rem 0; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #457b9d; background: #457b9d; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #457b9d; }
.note { font-size: .85rem; color: #666; margin: .3rem 0 0; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Boolean satisfiability (SAT) in conjunctive normal form: an AND of clauses,
// each an OR of literals. CHECKING an assignment is one pass over the clauses;
// FINDING a satisfying one means searching up to 2^n assignments.
const VARS = ['a', 'b', 'c', 'd'];
// literal = { v: variable index, neg: is-negated }
const CLAUSES = [
[{ v: 0, neg: false }, { v: 1, neg: false }, { v: 2, neg: false }], // a ∨ b ∨ c
[{ v: 0, neg: true }, { v: 1, neg: true }], // ¬a ∨ ¬b
[{ v: 1, neg: false }, { v: 2, neg: true }, { v: 3, neg: false }], // b ∨ ¬c ∨ d
[{ v: 1, neg: true }, { v: 2, neg: false }], // ¬b ∨ c
[{ v: 3, neg: true }, { v: 0, neg: false }], // ¬d ∨ a
];
let assign = VARS.map(() => false);
const litTrue = (lit, asg) => lit.neg ? !asg[lit.v] : asg[lit.v];
const clauseSat = (cl, asg) => cl.some(l => litTrue(l, asg));
const litText = lit => (lit.neg ? '¬' : '') + VARS[lit.v];
function buildFormula() {
const box = document.getElementById('formula');
box.innerHTML = '';
CLAUSES.forEach((cl, i) => {
if (i) { const a = document.createElement('span'); a.className = 'and'; a.textContent = '∧'; box.appendChild(a); }
const el = document.createElement('span');
el.className = 'clause'; el.dataset.i = i;
el.textContent = '(' + cl.map(litText).join(' ∨ ') + ')';
box.appendChild(el);
});
}
function buildVars() {
const box = document.getElementById('vars');
box.innerHTML = '';
VARS.forEach((name, i) => {
const b = document.createElement('button');
b.className = 'var'; b.dataset.i = i;
b.addEventListener('click', () => { assign[i] = !assign[i]; render(); });
box.appendChild(b);
});
}
function countSolutions() {
let n = 0;
for (let m = 0; m < (1 << VARS.length); m++) {
const asg = VARS.map((_, i) => Boolean(m & (1 << i)));
if (CLAUSES.every(cl => clauseSat(cl, asg))) n++;
}
return n;
}
const TOTAL_SOLUTIONS = countSolutions();
function render() {
let satCount = 0;
CLAUSES.forEach((cl, i) => {
const ok = clauseSat(cl, assign);
if (ok) satCount++;
const el = document.querySelector(`.clause[data-i="${i}"]`);
el.classList.toggle('sat', ok);
el.classList.toggle('unsat', !ok);
});
VARS.forEach((name, i) => {
const b = document.querySelector(`.var[data-i="${i}"]`);
b.textContent = `${name} = ${assign[i] ? 'T' : 'F'}`;
b.classList.toggle('t', assign[i]);
b.classList.toggle('f', !assign[i]);
});
const st = document.getElementById('status');
if (satCount === CLAUSES.length) {
st.textContent = fmt('All %length% clauses true — the formula is SATISFIED! 🎉', { length: CLAUSES.length });
st.className = 'status ok';
} else {
st.textContent = fmt('%satCount% of %length% clauses satisfied — keep flipping.', { satCount: satCount, length: CLAUSES.length });
st.className = 'status bad';
}
document.getElementById('note').textContent =
fmt('This formula has %TOTAL_SOLUTIONS% satisfying assignment(s) out of %v% possible.', { TOTAL_SOLUTIONS: TOTAL_SOLUTIONS, v: 1 << VARS.length });
}
function bruteForce() {
const total = 1 << VARS.length;
let steps = 0, found = -1;
for (let m = 0; m < total; m++) {
steps++;
const asg = VARS.map((_, i) => Boolean(m & (1 << i)));
if (CLAUSES.every(cl => clauseSat(cl, asg))) { found = m; break; }
}
const st = document.getElementById('status');
if (found >= 0) {
assign = VARS.map((_, i) => Boolean(found & (1 << i)));
render();
st.textContent = fmt('Found a satisfying assignment after checking %steps% of %total% — the formula is SATISFIABLE.', { steps: steps, total: total });
st.className = 'status ok';
} else {
st.textContent = fmt('Checked all %total% assignments — UNSATISFIABLE.', { total: total });
st.className = 'status bad';
}
console.log(fmt('SAT brute force: checked %steps%/%total%, %v%', { steps: steps, total: total, v: found >= 0 ? 'SAT' : 'UNSAT' }));
}
document.getElementById('solve').addEventListener('click', bruteForce);
document.getElementById('reset').addEventListener('click', () => { assign = VARS.map(() => false); render(); });
buildFormula();
buildVars();
render();
<p class="hint">A logic formula is a big <b>AND</b> of clauses; each clause is an <b>OR</b> of
variables (some negated, shown with ¬). The formula is satisfied only when <b>every clause</b>
is true. Flip the variables below and watch the clauses turn green. Can you make them all green?</p>
<div id="formula" class="formula"></div>
<div class="vars" id="vars"></div>
<p id="status" class="status"></p>
<div class="bar-btns">
<button id="solve" type="button">Brute-force search</button>
<button id="reset" type="button" class="ghost">All false</button>
</div>
<p id="note" class="note"></p>
* { 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; }
.formula { display: flex; flex-wrap: wrap; align-items: center; gap: .35rem; margin: .5rem 0; font: 600 15px ui-monospace, monospace; }
.clause { padding: .3rem .55rem; border-radius: 8px; border: 2px solid #ccc; background: #f4f4f4; transition: all .12s; white-space: nowrap; }
.clause.sat { background: #e7f6ee; border-color: #2a9d8f; color: #0a7d33; }
.clause.unsat { background: #fdecec; border-color: #e63946; color: #c0392b; }
.and { color: #999; font-weight: 700; padding: 0 .1rem; }
.vars { display: flex; flex-wrap: wrap; gap: .5rem; margin: .6rem 0; }
.var { padding: .45rem .8rem; border-radius: 8px; border: 2px solid #bbb; background: #fff; cursor: pointer; font: 700 15px ui-monospace, monospace; min-width: 70px; transition: all .12s; }
.var.t { background: #457b9d; color: #fff; border-color: #457b9d; }
.var.f { background: #eee; color: #555; }
.status { font-size: 1rem; font-weight: 700; min-height: 1.3em; margin: .3rem 0; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c0392b; }
.bar-btns { display: flex; gap: .5rem; flex-wrap: wrap; margin: .3rem 0; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #457b9d; background: #457b9d; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #457b9d; }
.note { font-size: .85rem; color: #666; margin: .3rem 0 0; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Boolean satisfiability (SAT) in conjunctive normal form: an AND of clauses,
// each an OR of literals. CHECKING an assignment is one pass over the clauses;
// FINDING a satisfying one means searching up to 2^n assignments.
const VARS = ['a', 'b', 'c', 'd'];
// literal = { v: variable index, neg: is-negated }
const CLAUSES = [
[{ v: 0, neg: false }, { v: 1, neg: false }, { v: 2, neg: false }], // a ∨ b ∨ c
[{ v: 0, neg: true }, { v: 1, neg: true }], // ¬a ∨ ¬b
[{ v: 1, neg: false }, { v: 2, neg: true }, { v: 3, neg: false }], // b ∨ ¬c ∨ d
[{ v: 1, neg: true }, { v: 2, neg: false }], // ¬b ∨ c
[{ v: 3, neg: true }, { v: 0, neg: false }], // ¬d ∨ a
];
let assign = VARS.map(() => false);
const litTrue = (lit, asg) => lit.neg ? !asg[lit.v] : asg[lit.v];
const clauseSat = (cl, asg) => cl.some(l => litTrue(l, asg));
const litText = lit => (lit.neg ? '¬' : '') + VARS[lit.v];
function buildFormula() {
const box = document.getElementById('formula');
box.innerHTML = '';
CLAUSES.forEach((cl, i) => {
if (i) { const a = document.createElement('span'); a.className = 'and'; a.textContent = '∧'; box.appendChild(a); }
const el = document.createElement('span');
el.className = 'clause'; el.dataset.i = i;
el.textContent = '(' + cl.map(litText).join(' ∨ ') + ')';
box.appendChild(el);
});
}
function buildVars() {
const box = document.getElementById('vars');
box.innerHTML = '';
VARS.forEach((name, i) => {
const b = document.createElement('button');
b.className = 'var'; b.dataset.i = i;
b.addEventListener('click', () => { assign[i] = !assign[i]; render(); });
box.appendChild(b);
});
}
function countSolutions() {
let n = 0;
for (let m = 0; m < (1 << VARS.length); m++) {
const asg = VARS.map((_, i) => Boolean(m & (1 << i)));
if (CLAUSES.every(cl => clauseSat(cl, asg))) n++;
}
return n;
}
const TOTAL_SOLUTIONS = countSolutions();
function render() {
let satCount = 0;
CLAUSES.forEach((cl, i) => {
const ok = clauseSat(cl, assign);
if (ok) satCount++;
const el = document.querySelector(`.clause[data-i="${i}"]`);
el.classList.toggle('sat', ok);
el.classList.toggle('unsat', !ok);
});
VARS.forEach((name, i) => {
const b = document.querySelector(`.var[data-i="${i}"]`);
b.textContent = `${name} = ${assign[i] ? 'T' : 'F'}`;
b.classList.toggle('t', assign[i]);
b.classList.toggle('f', !assign[i]);
});
const st = document.getElementById('status');
if (satCount === CLAUSES.length) {
st.textContent = fmt('All %length% clauses true — the formula is SATISFIED! 🎉', { length: CLAUSES.length });
st.className = 'status ok';
} else {
st.textContent = fmt('%satCount% of %length% clauses satisfied — keep flipping.', { satCount: satCount, length: CLAUSES.length });
st.className = 'status bad';
}
document.getElementById('note').textContent =
fmt('This formula has %TOTAL_SOLUTIONS% satisfying assignment(s) out of %v% possible.', { TOTAL_SOLUTIONS: TOTAL_SOLUTIONS, v: 1 << VARS.length });
}
function bruteForce() {
const total = 1 << VARS.length;
let steps = 0, found = -1;
for (let m = 0; m < total; m++) {
steps++;
const asg = VARS.map((_, i) => Boolean(m & (1 << i)));
if (CLAUSES.every(cl => clauseSat(cl, asg))) { found = m; break; }
}
const st = document.getElementById('status');
if (found >= 0) {
assign = VARS.map((_, i) => Boolean(found & (1 << i)));
render();
st.textContent = fmt('Found a satisfying assignment after checking %steps% of %total% — the formula is SATISFIABLE.', { steps: steps, total: total });
st.className = 'status ok';
} else {
st.textContent = fmt('Checked all %total% assignments — UNSATISFIABLE.', { total: total });
st.className = 'status bad';
}
console.log(fmt('SAT brute force: checked %steps%/%total%, %v%', { steps: steps, total: total, v: found >= 0 ? 'SAT' : 'UNSAT' }));
}
document.getElementById('solve').addEventListener('click', bruteForce);
document.getElementById('reset').addEventListener('click', () => { assign = VARS.map(() => false); render(); });
buildFormula();
buildVars();
render();
With four variables there are only sixteen possibilities, so brute force finishes instantly. But double the variables to a few hundred — common in real formulas — and 2n becomes more assignments than there are atoms in the universe. That is the wall SAT puts in front of us.
Comments
Loading comments...