sudoku-ai visual guide
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.
1. Input becomes a constraint model
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.
Blank cells are written as 0. A 4x4 puzzle uses 2x2 blocks; a 9x9 puzzle uses 3x3 blocks; the same machinery handles both.
Peers are precomputed once, so assigning a value later is just a quick walk over affected cells.
2. Candidates are bits, not lists
Each value maps to one bit. Eliminating a candidate clears its bit; placing a value replaces the whole mask with exactly that bit.
Bit masks make candidate checks tiny: membership is mask & bit != 0, removal is mask &= !bit, and a naked single is count_ones() == 1.
(1 << size) - 1 turns on every legal value bit.
Once a cell is assigned, its candidate mask becomes exactly value_bit(value).
If an unsolved cell’s mask becomes zero, that path is rejected.
3. Deduction runs until it stalls
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.
A cell with one remaining candidate is forced. The solver detects this with count_ones() == 1, assigns it, and propagates again.
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.
4. Search begins only after deduction stalls
When no rule can place another value, the solver picks one unresolved cell and tries each candidate in a cloned state.
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.
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.
If candidate impacts are equal, the smaller value is tried first for deterministic output.
5. Backtracking is just recursive state cloning
Search clones the current state, assigns one candidate, and immediately re-enters deduction. Failed branches vanish; successful branches return the solved grid.
Implementation map
The solver is small enough that the visual model maps directly onto a few functions in src/lib.rs.
Puzzle::parse validates square dimensions, block layout, and value ranges before solving starts.
Solver::new creates row, column, and block units; build_peers turns them into peer lists.
full_mask and value_bit encode possible values into a single integer.
assign places a value, checks duplicate peers, and clears that value from every unsolved peer.
deduce repeatedly applies naked singles and hidden singles until no rule progresses.
search clones states, tries ordered candidates, and returns the first branch that reaches a complete solution.