Let's shrink it to a 4×4 so the idea fits in one glance: every row, column and 2×2 box must hold 1, 2, 3 and 4. Click a blank cell to cycle its number and try to complete the grid.
<p class="hint">A mini 4×4 Sudoku: every row, every column and every 2×2 box must contain 1, 2, 3 and 4 exactly once. Click a blank cell to cycle its number. Checking a grid is instant — searching for the solution is the hard part.</p>
<div id="grid" class="grid"></div>
<div class="status" id="status">Fill the blanks, then check.</div>
<div class="btns">
<button id="check" type="button">Check</button>
<button id="solve" type="button">Solve it</button>
<button id="reset" type="button" class="ghost">Clear</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; }
.grid { display: grid; grid-template-columns: repeat(4, 50px); gap: 0; width: max-content; margin: .4rem 0;
border: 3px solid #1d3557; }
.cell { width: 50px; height: 50px; display: flex; align-items: center; justify-content: center;
font: 700 22px ui-monospace, monospace; border: 1px solid #c8d2db; }
/* thick lines between the 2x2 boxes */
.cell.r0 { border-top: none; } .cell.r3 { border-bottom: none; }
.cell.c0 { border-left: none; } .cell.c3 { border-right: none; }
.cell.bx { border-right: 3px solid #1d3557; }
.cell.by { border-bottom: 3px solid #1d3557; }
.given { background: #e8eef3; color: #1d3557; }
.blank { background: #fff; color: #457b9d; cursor: pointer; }
.blank:hover { background: #f1f6fa; }
.cell.bad { background: #fde2e4; color: #c92f3c; }
.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] ?? ''));
// 4x4 Sudoku. Verifying a grid is a quick scan of rows, columns and boxes.
// Solving means searching the tree of choices — backtracking here, but on a
// full 9x9 (or n x n) board this search is NP-complete.
const N = 4, B = 2;
// 0 = blank. Givens have a unique completion.
const PUZZLE = [
[1,2,0,4],
[0,4,1,0],
[0,1,4,0],
[4,0,0,1],
];
const grid = PUZZLE.map(r => r.slice());
const given = PUZZLE.map(r => r.map(v => v !== 0));
function boxOK(g){ // returns Set of "r,c" cells that violate a constraint
const bad = new Set();
const scan = (cells) => { const seen = {};
cells.forEach(([r,c]) => { const v = g[r][c]; if(!v) return;
if(seen[v]) { bad.add(r+','+c); bad.add(seen[v]); } else seen[v] = r+','+c; }); };
for(let r=0;r<N;r++) scan([...Array(N)].map((_,c)=>[r,c]));
for(let c=0;c<N;c++) scan([...Array(N)].map((_,r)=>[r,c]));
for(let br=0;br<N;br+=B) for(let bc=0;bc<N;bc+=B){
const cells=[]; for(let r=0;r<B;r++)for(let c=0;c<B;c++)cells.push([br+r,bc+c]); scan(cells); }
return bad;
}
function render(bad){
bad = bad || new Set();
const el = document.getElementById('grid'); el.innerHTML='';
for(let r=0;r<N;r++)for(let c=0;c<N;c++){
const d=document.createElement('div');
let cls='cell r'+r+' c'+c+(given[r][c]?' given':' blank');
if(c===1) cls+=' bx'; if(r===1) cls+=' by';
if(bad.has(r+','+c)) cls+=' bad';
d.className=cls; d.textContent=grid[r][c]||'';
if(!given[r][c]) d.addEventListener('click',()=>{ grid[r][c]=(grid[r][c]+1)%(N+1); render(); setStatus('Fill the blanks, then check.',''); });
el.appendChild(d);
}
}
function setStatus(t,cls){ const s=document.getElementById('status'); s.textContent=t; s.className='status '+cls; }
function check(){
const bad=boxOK(grid); render(bad);
const full=grid.every(row=>row.every(v=>v));
if(bad.size) setStatus('✗ A number repeats in a row, column or box.','bad');
else if(full) setStatus('✓ Solved! Every line and box holds 1–4.','ok');
else setStatus('No conflicts yet — but not finished.','');
}
function solve(){
let steps=0;
const blanks=[]; for(let r=0;r<N;r++)for(let c=0;c<N;c++) if(!given[r][c]) blanks.push([r,c]);
const ok=(r,c,v)=>{ for(let i=0;i<N;i++){ if(grid[r][i]===v||grid[i][c]===v) return false; }
const br=r-r%B, bc=c-c%B; for(let i=0;i<B;i++)for(let j=0;j<B;j++) if(grid[br+i][bc+j]===v) return false; return true; };
const bt=(k)=>{ if(k===blanks.length) return true; const [r,c]=blanks[k];
for(let v=1;v<=N;v++){ steps++; if(ok(r,c,v)){ grid[r][c]=v; if(bt(k+1)) return true; grid[r][c]=0; } } return false; };
blanks.forEach(([r,c])=>grid[r][c]=0);
const found=bt(0); render();
setStatus(found?(fmt('Solved by backtracking in %steps% tries.', { steps: steps })):'No solution.', found?'ok':'bad');
console.log(found?(fmt('Solution found, %steps% placements tried', { steps: steps })):'unsolvable');
}
document.getElementById('check').addEventListener('click',check);
document.getElementById('solve').addEventListener('click',solve);
document.getElementById('reset').addEventListener('click',()=>{ for(let r=0;r<N;r++)for(let c=0;c<N;c++) if(!given[r][c]) grid[r][c]=0; render(); setStatus('Fill the blanks, then check.',''); });
render();
<p class="hint">A mini 4×4 Sudoku: every row, every column and every 2×2 box must contain 1, 2, 3 and 4 exactly once. Click a blank cell to cycle its number. Checking a grid is instant — searching for the solution is the hard part.</p>
<div id="grid" class="grid"></div>
<div class="status" id="status">Fill the blanks, then check.</div>
<div class="btns">
<button id="check" type="button">Check</button>
<button id="solve" type="button">Solve it</button>
<button id="reset" type="button" class="ghost">Clear</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; }
.grid { display: grid; grid-template-columns: repeat(4, 50px); gap: 0; width: max-content; margin: .4rem 0;
border: 3px solid #1d3557; }
.cell { width: 50px; height: 50px; display: flex; align-items: center; justify-content: center;
font: 700 22px ui-monospace, monospace; border: 1px solid #c8d2db; }
/* thick lines between the 2x2 boxes */
.cell.r0 { border-top: none; } .cell.r3 { border-bottom: none; }
.cell.c0 { border-left: none; } .cell.c3 { border-right: none; }
.cell.bx { border-right: 3px solid #1d3557; }
.cell.by { border-bottom: 3px solid #1d3557; }
.given { background: #e8eef3; color: #1d3557; }
.blank { background: #fff; color: #457b9d; cursor: pointer; }
.blank:hover { background: #f1f6fa; }
.cell.bad { background: #fde2e4; color: #c92f3c; }
.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] ?? ''));
// 4x4 Sudoku. Verifying a grid is a quick scan of rows, columns and boxes.
// Solving means searching the tree of choices — backtracking here, but on a
// full 9x9 (or n x n) board this search is NP-complete.
const N = 4, B = 2;
// 0 = blank. Givens have a unique completion.
const PUZZLE = [
[1,2,0,4],
[0,4,1,0],
[0,1,4,0],
[4,0,0,1],
];
const grid = PUZZLE.map(r => r.slice());
const given = PUZZLE.map(r => r.map(v => v !== 0));
function boxOK(g){ // returns Set of "r,c" cells that violate a constraint
const bad = new Set();
const scan = (cells) => { const seen = {};
cells.forEach(([r,c]) => { const v = g[r][c]; if(!v) return;
if(seen[v]) { bad.add(r+','+c); bad.add(seen[v]); } else seen[v] = r+','+c; }); };
for(let r=0;r<N;r++) scan([...Array(N)].map((_,c)=>[r,c]));
for(let c=0;c<N;c++) scan([...Array(N)].map((_,r)=>[r,c]));
for(let br=0;br<N;br+=B) for(let bc=0;bc<N;bc+=B){
const cells=[]; for(let r=0;r<B;r++)for(let c=0;c<B;c++)cells.push([br+r,bc+c]); scan(cells); }
return bad;
}
function render(bad){
bad = bad || new Set();
const el = document.getElementById('grid'); el.innerHTML='';
for(let r=0;r<N;r++)for(let c=0;c<N;c++){
const d=document.createElement('div');
let cls='cell r'+r+' c'+c+(given[r][c]?' given':' blank');
if(c===1) cls+=' bx'; if(r===1) cls+=' by';
if(bad.has(r+','+c)) cls+=' bad';
d.className=cls; d.textContent=grid[r][c]||'';
if(!given[r][c]) d.addEventListener('click',()=>{ grid[r][c]=(grid[r][c]+1)%(N+1); render(); setStatus('Fill the blanks, then check.',''); });
el.appendChild(d);
}
}
function setStatus(t,cls){ const s=document.getElementById('status'); s.textContent=t; s.className='status '+cls; }
function check(){
const bad=boxOK(grid); render(bad);
const full=grid.every(row=>row.every(v=>v));
if(bad.size) setStatus('✗ A number repeats in a row, column or box.','bad');
else if(full) setStatus('✓ Solved! Every line and box holds 1–4.','ok');
else setStatus('No conflicts yet — but not finished.','');
}
function solve(){
let steps=0;
const blanks=[]; for(let r=0;r<N;r++)for(let c=0;c<N;c++) if(!given[r][c]) blanks.push([r,c]);
const ok=(r,c,v)=>{ for(let i=0;i<N;i++){ if(grid[r][i]===v||grid[i][c]===v) return false; }
const br=r-r%B, bc=c-c%B; for(let i=0;i<B;i++)for(let j=0;j<B;j++) if(grid[br+i][bc+j]===v) return false; return true; };
const bt=(k)=>{ if(k===blanks.length) return true; const [r,c]=blanks[k];
for(let v=1;v<=N;v++){ steps++; if(ok(r,c,v)){ grid[r][c]=v; if(bt(k+1)) return true; grid[r][c]=0; } } return false; };
blanks.forEach(([r,c])=>grid[r][c]=0);
const found=bt(0); render();
setStatus(found?(fmt('Solved by backtracking in %steps% tries.', { steps: steps })):'No solution.', found?'ok':'bad');
console.log(found?(fmt('Solution found, %steps% placements tried', { steps: steps })):'unsolvable');
}
document.getElementById('check').addEventListener('click',check);
document.getElementById('solve').addEventListener('click',solve);
document.getElementById('reset').addEventListener('click',()=>{ for(let r=0;r<N;r++)for(let c=0;c<N;c++) if(!given[r][c]) grid[r][c]=0; render(); setStatus('Fill the blanks, then check.',''); });
render();
Checking is instant — a quick scan for any repeated digit. To solve, press the button and watch backtracking at work: it tries a number, moves on, and rewinds whenever it hits a contradiction. On 4×4 that's a handful of tries; on 9×9 a few hundred; but as the grid grows, the search tree explodes.
Comments
Loading comments...