Files
sudoku-ai/docs/solver-visual-guide.html
ddidderr 8db952dcd4 docs: add solver visual guide
Add a seven-page visual PDF that explains how the Sudoku solver parses input,
builds row/column/block constraints, stores candidates as bit masks, applies
naked and hidden singles, and orders recursive search branches.

The guide is authored as plain HTML/CSS/SVG so the diagrams stay reviewable and
can be regenerated without a separate design tool. The README now links to the
PDF artifact for quick discovery.

Test Plan:
- cargo test
- pdfinfo docs/solver-visual-guide.pdf
- Raster-inspected exported PDF pages with pdftoppm

Refs: none
2026-04-25 21:32:14 +02:00

775 lines
34 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>sudoku-ai solver visual guide</title>
<style>
@page {
size: A4 landscape;
margin: 0;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
color: #152033;
background: #f5f0e8;
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
.page {
position: relative;
width: 297mm;
height: 210mm;
overflow: hidden;
page-break-after: always;
background:
linear-gradient(135deg, rgba(255, 255, 255, 0.94), rgba(244, 239, 230, 0.94)),
radial-gradient(circle at 12% 14%, rgba(255, 183, 67, 0.22), transparent 30%),
radial-gradient(circle at 90% 86%, rgba(27, 158, 142, 0.18), transparent 35%);
padding: 18mm 20mm;
}
.page:last-child {
page-break-after: auto;
}
.kicker {
margin: 0 0 5mm;
color: #0c7f78;
font-size: 10pt;
font-weight: 800;
letter-spacing: 0.12em;
text-transform: uppercase;
}
h1,
h2 {
margin: 0;
color: #101827;
font-weight: 900;
letter-spacing: 0;
}
h1 {
max-width: 180mm;
font-size: 40pt;
line-height: 1.02;
}
h2 {
max-width: 200mm;
font-size: 27pt;
line-height: 1.08;
}
p {
margin: 0;
color: #405064;
font-size: 12pt;
line-height: 1.42;
}
.lead {
max-width: 165mm;
margin-top: 8mm;
color: #334357;
font-size: 15pt;
line-height: 1.42;
}
.mono {
font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
font-size: 0.92em;
}
.title-grid {
display: grid;
grid-template-columns: 1fr 104mm;
gap: 17mm;
align-items: center;
height: 116mm;
margin-top: 8mm;
}
.badges {
display: flex;
flex-wrap: wrap;
gap: 3mm;
margin-top: 11mm;
}
.badge {
padding: 2.2mm 3.5mm;
border: 0.45mm solid rgba(16, 24, 39, 0.11);
border-radius: 99px;
background: rgba(255, 255, 255, 0.72);
color: #203146;
font-size: 10pt;
font-weight: 800;
}
.flow {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 4mm;
margin-top: 14mm;
}
.flow-step,
.card,
.stat,
.legend-item {
border: 0.45mm solid rgba(16, 24, 39, 0.11);
border-radius: 2mm;
background: rgba(255, 255, 255, 0.78);
box-shadow: 0 1.5mm 6mm rgba(20, 29, 42, 0.08);
}
.flow-step {
position: relative;
min-height: 26mm;
padding: 4mm;
}
.flow-step:not(:last-child)::after {
content: "";
position: absolute;
top: 11mm;
right: -4.6mm;
width: 5.2mm;
height: 5.2mm;
border-top: 1mm solid #df6e49;
border-right: 1mm solid #df6e49;
transform: rotate(45deg);
}
.flow-step strong {
display: block;
margin-bottom: 1.8mm;
color: #111827;
font-size: 10.5pt;
}
.flow-step span {
color: #536477;
font-size: 8.5pt;
line-height: 1.25;
}
.two-col {
display: grid;
grid-template-columns: 118mm 1fr;
gap: 12mm;
align-items: start;
margin-top: 12mm;
}
.three-col {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8mm;
margin-top: 12mm;
}
.card {
padding: 6mm;
}
.card h3 {
margin: 0 0 3mm;
color: #102030;
font-size: 15pt;
line-height: 1.12;
}
.card p,
.card li {
color: #4b5c70;
font-size: 10.4pt;
line-height: 1.35;
}
ul {
margin: 3mm 0 0;
padding-left: 5mm;
}
li {
margin: 1.8mm 0;
}
.code {
white-space: pre;
border-radius: 2mm;
background: #101827;
color: #e7f2f0;
padding: 5mm;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
font-size: 9.8pt;
line-height: 1.42;
box-shadow: 0 1.5mm 6mm rgba(16, 24, 39, 0.16);
}
.svg-frame {
border: 0.45mm solid rgba(16, 24, 39, 0.10);
border-radius: 2mm;
background: rgba(255, 255, 255, 0.72);
box-shadow: 0 1.5mm 6mm rgba(20, 29, 42, 0.08);
}
.caption {
margin-top: 3mm;
color: #627084;
font-size: 9.2pt;
line-height: 1.32;
}
.stats {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 5mm;
margin-top: 10mm;
}
.stat {
padding: 4.5mm;
}
.stat b {
display: block;
color: #132133;
font-size: 20pt;
line-height: 1;
}
.stat span {
color: #5d6c7d;
font-size: 9.3pt;
}
.pill {
display: inline-block;
padding: 1.4mm 2.5mm;
border-radius: 99px;
color: #102030;
background: #ffcf66;
font-size: 9pt;
font-weight: 850;
}
.page-number {
position: absolute;
right: 14mm;
bottom: 4mm;
color: #8994a2;
font-size: 8pt;
font-weight: 800;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.dark {
color: #eaf3f2;
background:
linear-gradient(135deg, #101827 0%, #163343 58%, #0c7f78 100%);
}
.dark h1,
.dark h2,
.dark .kicker {
color: #fffaf0;
}
.dark p,
.dark .lead {
color: #d4e1df;
}
.dark .badge {
border-color: rgba(255, 255, 255, 0.14);
background: rgba(255, 255, 255, 0.10);
color: #f4fbfa;
}
.dark .page-number {
color: rgba(255, 255, 255, 0.58);
}
.dark .card h3 {
color: #101827;
}
.dark .card p,
.dark .card li {
color: #4b5c70;
}
.dark .flow-step strong {
color: #101827;
}
.dark .flow-step span {
color: #536477;
}
.callout {
margin-top: 9mm;
padding: 5mm 6mm;
border-left: 2mm solid #df6e49;
border-radius: 0 2mm 2mm 0;
background: rgba(255, 255, 255, 0.76);
color: #35465a;
font-size: 12pt;
line-height: 1.4;
}
</style>
</head>
<body>
<section class="page dark">
<p class="kicker">sudoku-ai visual guide</p>
<div class="title-grid">
<div>
<h1>How the solver turns blanks into certainty</h1>
<p class="lead">The engine is a compact constraint solver: it models every empty cell as a bit mask, repeatedly applies deterministic Sudoku deductions, then searches only when deduction stalls.</p>
<div class="badges">
<span class="badge">plain text input</span>
<span class="badge">row / column / block units</span>
<span class="badge"><span class="mono">u128</span> candidates</span>
<span class="badge">naked singles</span>
<span class="badge">hidden singles</span>
<span class="badge">guided backtracking</span>
</div>
</div>
<svg width="390" height="390" viewBox="0 0 390 390" aria-label="Sudoku grid with solver highlights">
<defs>
<linearGradient id="cellGlow" x1="0" x2="1" y1="0" y2="1">
<stop stop-color="#ffcf66"/>
<stop offset="1" stop-color="#df6e49"/>
</linearGradient>
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="8" stdDeviation="10" flood-color="#06101a" flood-opacity="0.32"/>
</filter>
</defs>
<rect x="17" y="17" width="356" height="356" rx="14" fill="#fdfbf7" filter="url(#shadow)"/>
<g transform="translate(31 31)">
<rect width="328" height="328" rx="8" fill="#fffaf0"/>
<g fill="#f1f5f4">
<rect x="0" y="0" width="109.3" height="109.3"/>
<rect x="218.7" y="0" width="109.3" height="109.3"/>
<rect x="109.3" y="109.3" width="109.3" height="109.3"/>
<rect x="0" y="218.7" width="109.3" height="109.3"/>
<rect x="218.7" y="218.7" width="109.3" height="109.3"/>
</g>
<rect x="145.8" y="145.8" width="36.4" height="36.4" fill="url(#cellGlow)" opacity="0.92"/>
<g stroke="#cad3d6" stroke-width="1">
<path d="M36.4 0V328M72.9 0V328M145.8 0V328M182.2 0V328M255.1 0V328M291.6 0V328"/>
<path d="M0 36.4H328M0 72.9H328M0 145.8H328M0 182.2H328M0 255.1H328M0 291.6H328"/>
</g>
<g stroke="#152033" stroke-width="4">
<path d="M109.3 0V328M218.7 0V328M0 109.3H328M0 218.7H328"/>
<rect width="328" height="328" fill="none"/>
</g>
<g fill="#152033" font-size="24" font-weight="850" text-anchor="middle" dominant-baseline="middle">
<text x="18.2" y="18.2">1</text><text x="163.9" y="18.2">3</text><text x="200.4" y="18.2">4</text><text x="309.8" y="18.2">8</text>
<text x="54.7" y="54.7">7</text><text x="127.5" y="54.7">6</text><text x="163.9" y="54.7">8</text><text x="273.3" y="54.7">3</text>
<text x="91.1" y="91.1">8</text><text x="127.5" y="91.1">2</text><text x="163.9" y="91.1">1</text><text x="236.9" y="91.1">7</text><text x="309.8" y="91.1">4</text>
<text x="54.7" y="127.5">5</text><text x="91.1" y="127.5">4</text><text x="163.9" y="127.5">9</text><text x="236.9" y="127.5">6</text><text x="273.3" y="127.5">8</text>
<text x="18.2" y="163.9">9</text><text x="54.7" y="163.9">1</text><text x="127.5" y="163.9">5</text><text x="200.4" y="163.9">8</text><text x="273.3" y="163.9">2</text>
<text x="54.7" y="200.4">8</text><text x="127.5" y="200.4">3</text><text x="309.8" y="200.4">5</text>
<text x="18.2" y="236.9">3</text><text x="91.1" y="236.9">5</text><text x="127.5" y="236.9">9</text><text x="200.4" y="236.9">6</text><text x="236.9" y="236.9">8</text><text x="273.3" y="236.9">7</text><text x="309.8" y="236.9">1</text>
<text x="91.1" y="273.3">6</text><text x="273.3" y="273.3">4</text>
<text x="91.1" y="309.8">1</text><text x="163.9" y="309.8">7</text><text x="236.9" y="309.8">2</text>
</g>
<g fill="#0c7f78" font-size="8.8" font-weight="800" text-anchor="middle" dominant-baseline="middle">
<text x="163.9" y="154.8">2 4 6</text>
<text x="163.9" y="171.5">7</text>
</g>
</g>
</svg>
</div>
<div class="flow">
<div class="flow-step"><strong>Parse</strong><span>Read whitespace-separated rows and validate the shape.</span></div>
<div class="flow-step"><strong>Model</strong><span>Precompute units and peers for each cell.</span></div>
<div class="flow-step"><strong>Mask</strong><span>Track every candidate set as bits in a <span class="mono">u128</span>.</span></div>
<div class="flow-step"><strong>Deduce</strong><span>Propagate placements, naked singles, hidden singles.</span></div>
<div class="flow-step"><strong>Branch</strong><span>Pick the tightest unsolved cell and order values by impact.</span></div>
<div class="flow-step"><strong>Solve</strong><span>Return the first complete state that survives constraints.</span></div>
</div>
<div class="page-number">01 / overview</div>
</section>
<section class="page">
<p class="kicker">1. Input becomes a constraint model</p>
<h2>The grid is square, but the solver thinks in units and peers</h2>
<p class="lead">After parsing, each cell knows the three units it belongs to: one row, one column, and one block. The union of those units, excluding the cell itself, becomes its peer list.</p>
<div class="two-col">
<div>
<div class="code">0 2 3 0
0 0 0 1
1 0 0 0
0 4 2 0</div>
<p class="caption">Blank cells are written as <span class="mono">0</span>. A 4x4 puzzle uses 2x2 blocks; a 9x9 puzzle uses 3x3 blocks; the same machinery handles both.</p>
<div class="stats">
<div class="stat"><b>3</b><span>unit kinds per cell</span></div>
<div class="stat"><b>27</b><span>units in a 9x9 puzzle</span></div>
<div class="stat"><b>20</b><span>peers for a 9x9 cell</span></div>
<div class="stat"><b>1</b><span>state object to search</span></div>
</div>
</div>
<div>
<svg class="svg-frame" width="510" height="360" viewBox="0 0 510 360" aria-label="Row column block peers">
<rect width="510" height="360" rx="8" fill="#fffaf7"/>
<g transform="translate(32 30)">
<g>
<rect x="0" y="0" width="270" height="270" rx="7" fill="#fff"/>
<g fill="#e3f4f0">
<rect x="0" y="90" width="270" height="30"/>
<rect x="120" y="0" width="30" height="270"/>
</g>
<rect x="90" y="90" width="90" height="90" fill="#ffe7b1"/>
<rect x="120" y="90" width="30" height="30" fill="#df6e49"/>
<g stroke="#c8d1d5" stroke-width="1">
<path d="M30 0V270M60 0V270M120 0V270M150 0V270M210 0V270M240 0V270"/>
<path d="M0 30H270M0 60H270M0 120H270M0 150H270M0 210H270M0 240H270"/>
</g>
<g stroke="#142236" stroke-width="3">
<path d="M90 0V270M180 0V270M0 90H270M0 180H270"/>
<rect width="270" height="270" fill="none"/>
</g>
<g fill="#142236" font-size="16" font-weight="800" text-anchor="middle" dominant-baseline="middle">
<text x="135" y="105">cell</text>
</g>
</g>
<g transform="translate(315 8)" font-size="14" font-weight="800">
<rect x="0" y="0" width="140" height="58" rx="8" fill="#e3f4f0" stroke="#99cfc6"/>
<text x="18" y="24" fill="#0c7f78">row peers</text>
<text x="18" y="43" fill="#597080" font-size="11" font-weight="650">same horizontal unit</text>
<rect x="0" y="78" width="140" height="58" rx="8" fill="#e3f4f0" stroke="#99cfc6"/>
<text x="18" y="102" fill="#0c7f78">column peers</text>
<text x="18" y="121" fill="#597080" font-size="11" font-weight="650">same vertical unit</text>
<rect x="0" y="156" width="140" height="58" rx="8" fill="#ffe7b1" stroke="#e8bd58"/>
<text x="18" y="180" fill="#835214">block peers</text>
<text x="18" y="199" fill="#806743" font-size="11" font-weight="650">same sub-square</text>
<rect x="0" y="234" width="140" height="36" rx="8" fill="#df6e49"/>
<text x="18" y="257" fill="#fff">selected cell</text>
</g>
</g>
</svg>
<p class="caption">Peers are precomputed once, so assigning a value later is just a quick walk over affected cells.</p>
</div>
</div>
<div class="page-number">02 / model</div>
</section>
<section class="page">
<p class="kicker">2. Candidates are bits, not lists</p>
<h2>A cells possible values fit into one <span class="mono">u128</span></h2>
<p class="lead">Each value maps to one bit. Eliminating a candidate clears its bit; placing a value replaces the whole mask with exactly that bit.</p>
<div class="two-col">
<div>
<svg class="svg-frame" width="470" height="390" viewBox="0 0 470 390" aria-label="Bit mask candidate diagram">
<rect width="470" height="390" rx="8" fill="#fffaf7"/>
<g transform="translate(32 34)">
<text x="0" y="0" fill="#152033" font-size="18" font-weight="900">9x9 full mask</text>
<text x="0" y="26" fill="#5d6c7d" font-size="13">values 1 through 9 are initially possible</text>
<g transform="translate(0 56)">
<g font-size="12" font-weight="850" text-anchor="middle">
<text x="22" y="-12" fill="#506074">1</text><text x="68" y="-12" fill="#506074">2</text><text x="114" y="-12" fill="#506074">3</text>
<text x="160" y="-12" fill="#506074">4</text><text x="206" y="-12" fill="#506074">5</text><text x="252" y="-12" fill="#506074">6</text>
<text x="298" y="-12" fill="#506074">7</text><text x="344" y="-12" fill="#506074">8</text><text x="390" y="-12" fill="#506074">9</text>
</g>
<g>
<rect x="0" y="0" width="44" height="44" rx="8" fill="#0c7f78"/><rect x="46" y="0" width="44" height="44" rx="8" fill="#0c7f78"/>
<rect x="92" y="0" width="44" height="44" rx="8" fill="#0c7f78"/><rect x="138" y="0" width="44" height="44" rx="8" fill="#0c7f78"/>
<rect x="184" y="0" width="44" height="44" rx="8" fill="#0c7f78"/><rect x="230" y="0" width="44" height="44" rx="8" fill="#0c7f78"/>
<rect x="276" y="0" width="44" height="44" rx="8" fill="#0c7f78"/><rect x="322" y="0" width="44" height="44" rx="8" fill="#0c7f78"/>
<rect x="368" y="0" width="44" height="44" rx="8" fill="#0c7f78"/>
</g>
<g fill="#fff" font-size="16" font-weight="900" text-anchor="middle" dominant-baseline="middle">
<text x="22" y="22">1</text><text x="68" y="22">1</text><text x="114" y="22">1</text><text x="160" y="22">1</text><text x="206" y="22">1</text>
<text x="252" y="22">1</text><text x="298" y="22">1</text><text x="344" y="22">1</text><text x="390" y="22">1</text>
</g>
</g>
<g transform="translate(0 155)">
<path d="M0 0H412" stroke="#d3d9dd" stroke-width="2"/>
<text x="0" y="38" fill="#152033" font-size="18" font-weight="900">After peers place 1, 5, 8</text>
<g transform="translate(0 66)">
<g>
<rect x="0" y="0" width="44" height="44" rx="8" fill="#dbe2e6"/><rect x="46" y="0" width="44" height="44" rx="8" fill="#0c7f78"/>
<rect x="92" y="0" width="44" height="44" rx="8" fill="#0c7f78"/><rect x="138" y="0" width="44" height="44" rx="8" fill="#0c7f78"/>
<rect x="184" y="0" width="44" height="44" rx="8" fill="#dbe2e6"/><rect x="230" y="0" width="44" height="44" rx="8" fill="#0c7f78"/>
<rect x="276" y="0" width="44" height="44" rx="8" fill="#0c7f78"/><rect x="322" y="0" width="44" height="44" rx="8" fill="#dbe2e6"/>
<rect x="368" y="0" width="44" height="44" rx="8" fill="#0c7f78"/>
</g>
<g fill="#fff" font-size="16" font-weight="900" text-anchor="middle" dominant-baseline="middle">
<text x="68" y="22">1</text><text x="114" y="22">1</text><text x="160" y="22">1</text><text x="252" y="22">1</text><text x="298" y="22">1</text><text x="390" y="22">1</text>
</g>
<g fill="#7c8794" font-size="16" font-weight="900" text-anchor="middle" dominant-baseline="middle">
<text x="22" y="22">0</text><text x="206" y="22">0</text><text x="344" y="22">0</text>
</g>
</g>
</g>
<g transform="translate(0 298)">
<rect x="0" y="0" width="412" height="45" rx="8" fill="#101827"/>
<text x="18" y="29" fill="#f6efe4" font-size="15" font-family="monospace">mask &amp; !value_bit(value)</text>
</g>
</g>
</svg>
</div>
<div>
<div class="card">
<h3>Why masks work well here</h3>
<p>Bit masks make candidate checks tiny: membership is <span class="mono">mask &amp; bit != 0</span>, removal is <span class="mono">mask &amp;= !bit</span>, and a naked single is <span class="mono">count_ones() == 1</span>.</p>
</div>
<div class="three-col" style="grid-template-columns: 1fr; gap: 3.5mm; margin-top: 4mm;">
<div class="card">
<h3>Full mask</h3>
<p><span class="mono">(1 &lt;&lt; size) - 1</span> turns on every legal value bit.</p>
</div>
<div class="card">
<h3>Placed value</h3>
<p>Once a cell is assigned, its candidate mask becomes exactly <span class="mono">value_bit(value)</span>.</p>
</div>
<div class="card">
<h3>Contradiction</h3>
<p>If an unsolved cells mask becomes zero, that path is rejected.</p>
</div>
</div>
</div>
</div>
<div class="page-number">03 / candidates</div>
</section>
<section class="page">
<p class="kicker">3. Deduction runs until it stalls</p>
<h2>Every assignment immediately removes pressure from the board</h2>
<p class="lead">The solver loops over simple, strong rules. If any rule places a value, it starts another pass because that placement may unlock more forced moves.</p>
<div class="two-col">
<svg class="svg-frame" width="510" height="440" viewBox="0 0 510 440" aria-label="Deduction loop diagram">
<rect width="510" height="440" rx="8" fill="#fffaf7"/>
<g transform="translate(58 42)">
<path d="M196 6C286 6 360 80 360 170C360 260 286 334 196 334C106 334 32 260 32 170C32 80 106 6 196 6Z" fill="#f1f6f4" stroke="#c8d7d3" stroke-width="2"/>
<path d="M276 28C330 58 366 115 366 180" fill="none" stroke="#df6e49" stroke-width="8" stroke-linecap="round"/>
<path d="M116 312C62 282 26 225 26 160" fill="none" stroke="#0c7f78" stroke-width="8" stroke-linecap="round"/>
<polygon points="365,181 354,162 376,162" fill="#df6e49" transform="rotate(88 365 181)"/>
<polygon points="27,159 16,178 38,178" fill="#0c7f78" transform="rotate(268 27 159)"/>
<g>
<rect x="118" y="16" width="156" height="58" rx="10" fill="#101827"/>
<text x="196" y="40" fill="#fffaf0" font-size="16" font-weight="900" text-anchor="middle">assign value</text>
<text x="196" y="61" fill="#b6c6c4" font-size="12" text-anchor="middle">set value and exact mask</text>
</g>
<g>
<rect x="266" y="140" width="168" height="64" rx="10" fill="#e3f4f0" stroke="#99cfc6"/>
<text x="350" y="166" fill="#0c7f78" font-size="16" font-weight="900" text-anchor="middle">propagate</text>
<text x="350" y="187" fill="#557071" font-size="12" text-anchor="middle">clear this value from peers</text>
</g>
<g>
<rect x="118" y="266" width="156" height="64" rx="10" fill="#ffe7b1" stroke="#e7bd58"/>
<text x="196" y="292" fill="#7a4c13" font-size="16" font-weight="900" text-anchor="middle">naked singles</text>
<text x="196" y="313" fill="#806743" font-size="12" text-anchor="middle">one remaining candidate</text>
</g>
<g>
<rect x="-30" y="140" width="168" height="64" rx="10" fill="#fbe5dd" stroke="#dfb29f"/>
<text x="54" y="166" fill="#a74428" font-size="16" font-weight="900" text-anchor="middle">hidden singles</text>
<text x="54" y="187" fill="#76564a" font-size="12" text-anchor="middle">one place in a unit</text>
</g>
<circle cx="196" cy="170" r="46" fill="#fff" stroke="#cdd7da" stroke-width="2"/>
<text x="196" y="162" fill="#152033" font-size="15" font-weight="900" text-anchor="middle">repeat</text>
<text x="196" y="184" fill="#5e7080" font-size="12" text-anchor="middle">until stable</text>
</g>
</svg>
<div>
<div class="card">
<h3>Naked single</h3>
<p>A cell with one remaining candidate is forced. The solver detects this with <span class="mono">count_ones() == 1</span>, assigns it, and propagates again.</p>
</div>
<div class="card" style="margin-top: 6mm;">
<h3>Hidden single</h3>
<p>For each row, column, and block, the solver checks every value. If only one unsolved cell in that unit can still hold the value, that cell is forced.</p>
</div>
<div class="callout">If a unit has no legal place for a value, or a candidate mask goes empty, that path is rejected as a contradiction.</div>
</div>
</div>
<div class="page-number">04 / deduction</div>
</section>
<section class="page">
<p class="kicker">4. Search begins only after deduction stalls</p>
<h2>The branch point is chosen to make guessing as constrained as possible</h2>
<p class="lead">When no rule can place another value, the solver picks one unresolved cell and tries each candidate in a cloned state.</p>
<div class="two-col">
<div>
<svg class="svg-frame" width="510" height="415" viewBox="0 0 510 415" aria-label="Branch cell selection diagram">
<rect width="510" height="415" rx="8" fill="#fffaf7"/>
<g transform="translate(34 32)">
<text x="0" y="0" fill="#152033" font-size="18" font-weight="900">Choose branch cell</text>
<text x="0" y="25" fill="#607080" font-size="13">Fewest candidates wins; ties prefer more unsolved peers.</text>
<g transform="translate(0 55)">
<rect x="0" y="0" width="110" height="110" rx="8" fill="#e3f4f0" stroke="#99cfc6"/>
<text x="55" y="38" fill="#0c7f78" font-size="24" font-weight="900" text-anchor="middle">{2,7}</text>
<text x="55" y="68" fill="#405064" font-size="13" font-weight="800" text-anchor="middle">2 candidates</text>
<text x="55" y="91" fill="#607080" font-size="11" text-anchor="middle">13 open peers</text>
<rect x="134" y="0" width="110" height="110" rx="8" fill="#ffffff" stroke="#ccd5d9"/>
<text x="189" y="38" fill="#334357" font-size="21" font-weight="900" text-anchor="middle">{1,5,9}</text>
<text x="189" y="68" fill="#405064" font-size="13" font-weight="800" text-anchor="middle">3 candidates</text>
<text x="189" y="91" fill="#607080" font-size="11" text-anchor="middle">18 open peers</text>
<rect x="268" y="0" width="110" height="110" rx="8" fill="#ffe7b1" stroke="#e7bd58"/>
<text x="323" y="38" fill="#7a4c13" font-size="24" font-weight="900" text-anchor="middle">{4,6}</text>
<text x="323" y="68" fill="#405064" font-size="13" font-weight="800" text-anchor="middle">2 candidates</text>
<text x="323" y="91" fill="#607080" font-size="11" text-anchor="middle">17 open peers</text>
<text x="323" y="131" fill="#df6e49" font-size="13" font-weight="900" text-anchor="middle">selected</text>
</g>
<g transform="translate(0 230)">
<text x="0" y="0" fill="#152033" font-size="18" font-weight="900">Order selected candidates</text>
<text x="0" y="25" fill="#607080" font-size="13">Impact = peers that would lose this value as a candidate.</text>
<g transform="translate(0 52)">
<rect x="0" y="0" width="390" height="34" rx="8" fill="#f0f3f5"/>
<rect x="0" y="0" width="326" height="34" rx="8" fill="#df6e49"/>
<text x="13" y="22" fill="#fff" font-size="13" font-weight="900">try 6 first</text>
<text x="372" y="22" fill="#334357" font-size="13" font-weight="900" text-anchor="end">17</text>
</g>
<g transform="translate(0 98)">
<rect x="0" y="0" width="390" height="34" rx="8" fill="#f0f3f5"/>
<rect x="0" y="0" width="231" height="34" rx="8" fill="#0c7f78"/>
<text x="13" y="22" fill="#fff" font-size="13" font-weight="900">then 4</text>
<text x="372" y="22" fill="#334357" font-size="13" font-weight="900" text-anchor="end">12</text>
</g>
</g>
</g>
</svg>
</div>
<div>
<div class="card">
<h3>Cell heuristic</h3>
<p>The solver scans unresolved cells and keeps the cell with the fewest candidate bits. If two cells are equally tight, it picks the one touching more unsolved peers.</p>
</div>
<div class="card" style="margin-top: 6mm;">
<h3>Candidate heuristic</h3>
<p>For that cell, values are sorted by impact. A value has higher impact when more peer cells currently contain that same value as a candidate.</p>
</div>
<div class="card" style="margin-top: 6mm;">
<h3>Tie break</h3>
<p>If candidate impacts are equal, the smaller value is tried first for deterministic output.</p>
</div>
</div>
</div>
<div class="page-number">05 / search</div>
</section>
<section class="page">
<p class="kicker">5. Backtracking is just recursive state cloning</p>
<h2>Every assumption either solves the board or collapses quickly</h2>
<p class="lead">Search clones the current state, assigns one candidate, and immediately re-enters deduction. Failed branches vanish; successful branches return the solved grid.</p>
<div class="two-col">
<svg class="svg-frame" width="515" height="410" viewBox="0 0 515 410" aria-label="Backtracking tree">
<rect width="515" height="410" rx="8" fill="#fffaf7"/>
<g transform="translate(44 38)">
<rect x="145" y="0" width="150" height="58" rx="10" fill="#101827"/>
<text x="220" y="25" fill="#fffaf0" font-size="15" font-weight="900" text-anchor="middle">deduced state</text>
<text x="220" y="45" fill="#b7c6c5" font-size="11" text-anchor="middle">not solved, no forced moves</text>
<path d="M220 58C220 92 118 99 118 132" fill="none" stroke="#b7c2c8" stroke-width="2"/>
<path d="M220 58C220 92 322 99 322 132" fill="none" stroke="#b7c2c8" stroke-width="2"/>
<rect x="38" y="132" width="160" height="60" rx="10" fill="#fbe5dd" stroke="#dfb29f"/>
<text x="118" y="157" fill="#a74428" font-size="15" font-weight="900" text-anchor="middle">try value 6</text>
<text x="118" y="178" fill="#76564a" font-size="11" text-anchor="middle">contradiction during deduction</text>
<rect x="242" y="132" width="160" height="60" rx="10" fill="#e3f4f0" stroke="#99cfc6"/>
<text x="322" y="157" fill="#0c7f78" font-size="15" font-weight="900" text-anchor="middle">try value 4</text>
<text x="322" y="178" fill="#557071" font-size="11" text-anchor="middle">deduction continues</text>
<path d="M322 192C322 226 232 232 232 264" fill="none" stroke="#b7c2c8" stroke-width="2"/>
<path d="M322 192C322 226 411 232 411 264" fill="none" stroke="#b7c2c8" stroke-width="2"/>
<rect x="152" y="264" width="160" height="60" rx="10" fill="#ffe7b1" stroke="#e7bd58"/>
<text x="232" y="289" fill="#7a4c13" font-size="15" font-weight="900" text-anchor="middle">more branching</text>
<text x="232" y="310" fill="#806743" font-size="11" text-anchor="middle">same rules, deeper tree</text>
<rect x="330" y="264" width="130" height="60" rx="10" fill="#0c7f78"/>
<text x="395" y="289" fill="#fff" font-size="13" font-weight="900" text-anchor="middle">complete grid</text>
<text x="395" y="310" fill="#d5eeea" font-size="10.5" text-anchor="middle">return solution</text>
<g stroke="#df6e49" stroke-width="4" stroke-linecap="round">
<path d="M78 214L158 244"/>
<path d="M158 214L78 244"/>
</g>
</g>
</svg>
<div>
<div class="card">
<h3>The core rhythm</h3>
<ul>
<li>Run deduction before every search decision.</li>
<li>If all cells are filled, return the state.</li>
<li>Otherwise choose one branch cell.</li>
<li>Try candidates in impact order.</li>
<li>Discard any branch that hits a contradiction.</li>
</ul>
</div>
<div class="callout"><span class="pill">Key idea</span><br>Most of the “intelligence” is in shrinking the search tree before guessing and making each guess remove as much uncertainty as possible.</div>
</div>
</div>
<div class="page-number">06 / backtracking</div>
</section>
<section class="page dark">
<p class="kicker">Implementation map</p>
<h2>Where to look in the code</h2>
<p class="lead">The solver is small enough that the visual model maps directly onto a few functions in <span class="mono">src/lib.rs</span>.</p>
<div class="three-col">
<div class="card">
<h3>Parsing</h3>
<p><span class="mono">Puzzle::parse</span> validates square dimensions, block layout, and value ranges before solving starts.</p>
</div>
<div class="card">
<h3>Constraint model</h3>
<p><span class="mono">Solver::new</span> creates row, column, and block units; <span class="mono">build_peers</span> turns them into peer lists.</p>
</div>
<div class="card">
<h3>Candidate masks</h3>
<p><span class="mono">full_mask</span> and <span class="mono">value_bit</span> encode possible values into a single integer.</p>
</div>
<div class="card">
<h3>Propagation</h3>
<p><span class="mono">assign</span> places a value, checks duplicate peers, and clears that value from every unsolved peer.</p>
</div>
<div class="card">
<h3>Deduction</h3>
<p><span class="mono">deduce</span> repeatedly applies naked singles and hidden singles until no rule progresses.</p>
</div>
<div class="card">
<h3>Search</h3>
<p><span class="mono">search</span> clones states, tries ordered candidates, and returns the first branch that reaches a complete solution.</p>
</div>
</div>
<div class="flow" style="grid-template-columns: repeat(4, 1fr); margin-top: 18mm;">
<div class="flow-step"><strong>Correctness guard</strong><span>Contradictions stop invalid givens and impossible branches early.</span></div>
<div class="flow-step"><strong>Performance trick</strong><span>Peer lists and bit masks make the hot path small.</span></div>
<div class="flow-step"><strong>Search control</strong><span>Fewest candidates narrows the branch factor.</span></div>
<div class="flow-step"><strong>Impact ordering</strong><span>Higher-impact values test stronger assumptions first.</span></div>
</div>
<div class="page-number">07 / code map</div>
</section>
</body>
</html>