Here is a small 5×5 nonogram. The numbers above each column and beside each row are run-length clues: they list the lengths of the filled blocks, in order, with at least one empty cell between blocks. Click the cells to fill them until every clue is satisfied.
<p class="hint">The numbers are run-length clues: each row and column lists the lengths of its filled blocks, in order, with a gap between blocks. Click cells to fill them so that <b>every</b> clue matches. Checking is instant — finding a solution is the hard part.</p>
<div id="grid" class="grid"></div>
<div class="status" id="status">Click cells to paint, then check.</div>
<div class="btns">
<button id="check" type="button">Check picture</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: inline-grid; gap: 3px; margin: .4rem 0; }
.corner { width: 100%; height: 100%; }
.colclue, .rowclue { display: flex; align-items: flex-end; justify-content: center;
font: 700 12px ui-monospace, monospace; color: #1d3557; padding: 2px;
line-height: 1.15; text-align: center; min-width: 40px; min-height: 28px; }
.rowclue { align-items: center; justify-content: flex-end; padding-right: 6px; }
.cell { width: 40px; height: 40px; border-radius: 6px; background: #d7dde3;
border: 1px solid #b9c2cb; cursor: pointer; transition: background .08s; }
.cell:hover { background: #c6cdd4; }
.cell.on { background: #1d3557; border-color: #14233b; }
.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] ?? ''));
// Nonogram "consistency": given row + column run-length clues, is there a way to fill the grid so every line's blocks match its clue? Checking one grid is a quick scan; finding one in general is NP-complete (Ueda & Nagao, 1996) — brute force here tries 2^(N*N) grids.
const N = 5;
// Clues for the hidden picture (a small "plus / cross" shape).
const ROW = [[1],[1],[5],[1],[1]];
const COL = [[1],[1],[5],[1],[1]];
let cells = Array.from({length:N},()=>Array(N).fill(0));
// Run-lengths of filled blocks in a 0/1 line.
function runs(line){
const out=[]; let c=0;
for(const v of line){ if(v){c++;} else if(c){out.push(c);c=0;} }
if(c) out.push(c);
return out;
}
const eq = (a,b)=> a.length===b.length && a.every((x,i)=>x===b[i]);
// Empty line: no filled cells -> empty clue [].
function lineOK(line,clue){
const r=runs(line);
const cl=(clue.length===1&&clue[0]===0)?[]:clue;
return eq(r,cl);
}
function gridValid(g){
for(let r=0;r<N;r++) if(!lineOK(g[r],ROW[r])) return false;
for(let c=0;c<N;c++) if(!lineOK(g.map(row=>row[c]),COL[c])) return false;
return true;
}
function render(){
const grid=document.getElementById('grid');
grid.style.gridTemplateColumns = '60px repeat('+N+', 40px)';
grid.innerHTML='';
// top-left corner
const corner=document.createElement('div'); corner.className='corner'; grid.appendChild(corner);
// column clues
for(let c=0;c<N;c++){ const d=document.createElement('div'); d.className='colclue';
d.innerHTML=COL[c].join('<br>'); grid.appendChild(d); }
// rows
for(let r=0;r<N;r++){
const rc=document.createElement('div'); rc.className='rowclue';
rc.textContent=ROW[r].join(' '); grid.appendChild(rc);
for(let c=0;c<N;c++){
const cell=document.createElement('div');
cell.className='cell'+(cells[r][c]?' on':'');
cell.addEventListener('click',()=>{ cells[r][c]=cells[r][c]?0:1; render();
setStatus('Click cells to paint, then check.',''); });
grid.appendChild(cell);
}
}
}
function setStatus(t,cls){ const s=document.getElementById('status'); s.textContent=t; s.className='status '+cls; }
function check(){
if(gridValid(cells)) setStatus('✓ Every row and column clue matches — solved!','ok');
else setStatus('✗ Some line does not match its clue. Keep deducing.','bad');
}
// Brute force: try all 2^(N*N) fillings until one satisfies every clue.
function solve(){
const total=N*N, limit=1<<total; let steps=0, found=null;
for(let mask=0; mask<limit; mask++){ steps++;
const g=Array.from({length:N},()=>Array(N).fill(0));
for(let i=0;i<total;i++) if(mask&(1<<i)) g[(i/N)|0][i%N]=1;
if(gridValid(g)){ found=g; break; }
}
if(found){ cells=found; render();
setStatus(fmt('Found a solution after checking %steps% grids.', { steps: steps.toLocaleString() }),'ok');
console.log('Solved after',steps,'of',limit,'grids');
} else setStatus('No grid satisfies these clues.','bad');
}
document.getElementById('check').addEventListener('click',check);
document.getElementById('solve').addEventListener('click',solve);
document.getElementById('reset').addEventListener('click',()=>{ cells=Array.from({length:N},()=>Array(N).fill(0)); render(); setStatus('Click cells to paint, then check.',''); });
render();
<p class="hint">The numbers are run-length clues: each row and column lists the lengths of its filled blocks, in order, with a gap between blocks. Click cells to fill them so that <b>every</b> clue matches. Checking is instant — finding a solution is the hard part.</p>
<div id="grid" class="grid"></div>
<div class="status" id="status">Click cells to paint, then check.</div>
<div class="btns">
<button id="check" type="button">Check picture</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: inline-grid; gap: 3px; margin: .4rem 0; }
.corner { width: 100%; height: 100%; }
.colclue, .rowclue { display: flex; align-items: flex-end; justify-content: center;
font: 700 12px ui-monospace, monospace; color: #1d3557; padding: 2px;
line-height: 1.15; text-align: center; min-width: 40px; min-height: 28px; }
.rowclue { align-items: center; justify-content: flex-end; padding-right: 6px; }
.cell { width: 40px; height: 40px; border-radius: 6px; background: #d7dde3;
border: 1px solid #b9c2cb; cursor: pointer; transition: background .08s; }
.cell:hover { background: #c6cdd4; }
.cell.on { background: #1d3557; border-color: #14233b; }
.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] ?? ''));
// Nonogram "consistency": given row + column run-length clues, is there a way to fill the grid so every line's blocks match its clue? Checking one grid is a quick scan; finding one in general is NP-complete (Ueda & Nagao, 1996) — brute force here tries 2^(N*N) grids.
const N = 5;
// Clues for the hidden picture (a small "plus / cross" shape).
const ROW = [[1],[1],[5],[1],[1]];
const COL = [[1],[1],[5],[1],[1]];
let cells = Array.from({length:N},()=>Array(N).fill(0));
// Run-lengths of filled blocks in a 0/1 line.
function runs(line){
const out=[]; let c=0;
for(const v of line){ if(v){c++;} else if(c){out.push(c);c=0;} }
if(c) out.push(c);
return out;
}
const eq = (a,b)=> a.length===b.length && a.every((x,i)=>x===b[i]);
// Empty line: no filled cells -> empty clue [].
function lineOK(line,clue){
const r=runs(line);
const cl=(clue.length===1&&clue[0]===0)?[]:clue;
return eq(r,cl);
}
function gridValid(g){
for(let r=0;r<N;r++) if(!lineOK(g[r],ROW[r])) return false;
for(let c=0;c<N;c++) if(!lineOK(g.map(row=>row[c]),COL[c])) return false;
return true;
}
function render(){
const grid=document.getElementById('grid');
grid.style.gridTemplateColumns = '60px repeat('+N+', 40px)';
grid.innerHTML='';
// top-left corner
const corner=document.createElement('div'); corner.className='corner'; grid.appendChild(corner);
// column clues
for(let c=0;c<N;c++){ const d=document.createElement('div'); d.className='colclue';
d.innerHTML=COL[c].join('<br>'); grid.appendChild(d); }
// rows
for(let r=0;r<N;r++){
const rc=document.createElement('div'); rc.className='rowclue';
rc.textContent=ROW[r].join(' '); grid.appendChild(rc);
for(let c=0;c<N;c++){
const cell=document.createElement('div');
cell.className='cell'+(cells[r][c]?' on':'');
cell.addEventListener('click',()=>{ cells[r][c]=cells[r][c]?0:1; render();
setStatus('Click cells to paint, then check.',''); });
grid.appendChild(cell);
}
}
}
function setStatus(t,cls){ const s=document.getElementById('status'); s.textContent=t; s.className='status '+cls; }
function check(){
if(gridValid(cells)) setStatus('✓ Every row and column clue matches — solved!','ok');
else setStatus('✗ Some line does not match its clue. Keep deducing.','bad');
}
// Brute force: try all 2^(N*N) fillings until one satisfies every clue.
function solve(){
const total=N*N, limit=1<<total; let steps=0, found=null;
for(let mask=0; mask<limit; mask++){ steps++;
const g=Array.from({length:N},()=>Array(N).fill(0));
for(let i=0;i<total;i++) if(mask&(1<<i)) g[(i/N)|0][i%N]=1;
if(gridValid(g)){ found=g; break; }
}
if(found){ cells=found; render();
setStatus(fmt('Found a solution after checking %steps% grids.', { steps: steps.toLocaleString() }),'ok');
console.log('Solved after',steps,'of',limit,'grids');
} else setStatus('No grid satisfies these clues.','bad');
}
document.getElementById('check').addEventListener('click',check);
document.getElementById('solve').addEventListener('click',solve);
document.getElementById('reset').addEventListener('click',()=>{ cells=Array.from({length:N},()=>Array(N).fill(0)); render(); setStatus('Click cells to paint, then check.',''); });
render();
Notice the asymmetry. Checking a finished grid is effortless: read each row and column, list its blocks, compare to the clue. Finding a grid that satisfies all the clues at once is the hard part — press Solve it and the computer simply tries every way to fill the grid. A 5×5 grid has 225 ≈ 33 million possibilities; every extra row roughly multiplies the count by 32. This is the same easy-to-check, hard-to-solve tension at the heart of P vs NP.
Comments
Loading comments...