human-sudoku-solver 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,203 @@
1
+ # human-sudoku-solver
2
+
3
+ [![npm version](https://img.shields.io/npm/v/human-sudoku-solver)](https://www.npmjs.com/package/human-sudoku-solver)
4
+
5
+ A TypeScript library that solves Sudoku puzzles using human techniques. Rather than brute-force backtracking, it applies logical solving methods in the same order a human would — making it suitable for hint systems, puzzle analysis, and step-by-step walkthroughs.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install human-sudoku-solver
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ### Solve a puzzle
16
+
17
+ ```typescript
18
+ import { humanSolve } from 'human-sudoku-solver';
19
+
20
+ // 81-cell flat array, row-major order. 0 = empty, 1–9 = digit.
21
+ const grid = [
22
+ 2,0,1,0,8,0,0,0,0,
23
+ 9,0,0,1,7,0,0,4,0,
24
+ 0,7,0,0,0,1,5,2,8,
25
+ // ...
26
+ ];
27
+
28
+ const { grid: solved, steps } = humanSolve(grid);
29
+
30
+ console.log(`Solved in ${steps.length} steps`);
31
+ steps.forEach(step => console.log(step.technique, step.explanation));
32
+ ```
33
+
34
+ ### Step-by-step solving
35
+
36
+ ```typescript
37
+ import { buildCandidates, findHint, applyHint } from 'human-sudoku-solver';
38
+
39
+ let grid = [/* initial puzzle */];
40
+ let candidates = buildCandidates(grid);
41
+
42
+ while (true) {
43
+ const hint = findHint(grid, candidates);
44
+ if (!hint) break;
45
+
46
+ console.log(hint.technique); // e.g. 'nakedSingle'
47
+ console.log(hint.eureka); // e.g. 'r1c3=5'
48
+ console.log(hint.explanation); // human-readable description
49
+
50
+ ({ grid, candidates } = applyHint(grid, candidates, hint));
51
+ }
52
+ ```
53
+
54
+ ### Parse puzzle text format
55
+
56
+ ```typescript
57
+ import { puzzleTextToPuzzle, puzzleToGrid, humanSolve } from 'human-sudoku-solver';
58
+
59
+ // 81-character string: '.' or '0' = empty, '1'–'9' = digit
60
+ const puzzle = puzzleTextToPuzzle('2.1.8....9..17..4..7....528...');
61
+ const grid = puzzleToGrid(puzzle);
62
+ const { grid: solved } = humanSolve(grid);
63
+ ```
64
+
65
+ ## API
66
+
67
+ ### Core functions
68
+
69
+ #### `humanSolve(grid: Grid)`
70
+
71
+ Solves a puzzle to completion using human techniques.
72
+
73
+ Returns `{ grid: Grid; candidates: Candidates; steps: HintResult[] }`.
74
+
75
+ #### `findHint(grid: Grid, candidates: Candidates)`
76
+
77
+ Finds the next logical step without applying it.
78
+
79
+ Returns `HintResult | null`.
80
+
81
+ #### `applyHint(grid: Grid, candidates: Candidates, hint: HintResult)`
82
+
83
+ Applies a hint to the current state.
84
+
85
+ Returns `{ grid: Grid; candidates: Candidates }`.
86
+
87
+ #### `buildCandidates(grid: Grid)`
88
+
89
+ Builds the initial candidate sets for all empty cells.
90
+
91
+ Returns `Candidates` — an array of 81 `Set<number>` values (one per cell).
92
+
93
+ #### `isGridInvalid(grid: Grid)`
94
+
95
+ Returns `true` if the grid contains duplicate digits in any row, column, or box.
96
+
97
+ ### Format conversion
98
+
99
+ #### `puzzleTextToPuzzle(text: string)`
100
+
101
+ Converts an 81-character puzzle string to a `Puzzle<number>`.
102
+
103
+ #### `puzzleToPuzzleText(puzzle: Puzzle<number | Notes>)`
104
+
105
+ Converts a `Puzzle` back to an 81-character string.
106
+
107
+ #### `puzzleToGrid(puzzle: Puzzle<number | Notes>)`
108
+
109
+ Converts a `Puzzle` to a flat 81-cell `Grid`.
110
+
111
+ #### `gridToPuzzle(grid: Grid)`
112
+
113
+ Converts a flat `Grid` to a `Puzzle<number>`.
114
+
115
+ ## Types
116
+
117
+ ### `Grid`
118
+
119
+ Flat array of 81 numbers in row-major order. `0` = empty, `1`–`9` = digit.
120
+
121
+ ```
122
+ cell index = row * 9 + col
123
+ ```
124
+
125
+ ### `HintResult`
126
+
127
+ ```typescript
128
+ interface HintResult {
129
+ technique: Technique;
130
+ placements: Placement[]; // cells to fill: { cell, digit }
131
+ eliminations: Elimination[]; // candidates to remove: { cell, digit }
132
+ patternCells: number[]; // cells involved in the pattern (for highlighting)
133
+ eureka?: string; // standard Sudoku notation
134
+ explanation?: string; // human-readable description
135
+ chainPath?: ChainNode[]; // populated for chain techniques
136
+ hiddenDigits?: number[]; // populated for hidden group techniques
137
+ patternDigits?: number[]; // populated for some techniques (e.g. UR floor digits)
138
+ als1Cells?: number[]; // populated for ALS XZ
139
+ als2Cells?: number[]; // populated for ALS XZ
140
+ stemCell?: number; // populated for Death Blossom
141
+ petalCells?: number[][]; // populated for Death Blossom
142
+ }
143
+ ```
144
+
145
+ ### `ChainNode`
146
+
147
+ ```typescript
148
+ interface ChainNode {
149
+ cell: number;
150
+ digit: number;
151
+ isOn: boolean; // true = candidate is ON in this node
152
+ cells?: number[]; // grouped node: multiple cells in same house
153
+ linkToNext?: 'strong' | 'weak';
154
+ }
155
+ ```
156
+
157
+ ### `Technique`
158
+
159
+ The full set of supported techniques, in the order they are attempted:
160
+
161
+ | Technique | Description |
162
+ |---|---|
163
+ | `nakedSingle` | Only one candidate remains in a cell |
164
+ | `hiddenSingleBox` / `hiddenSingleRow` / `hiddenSingleCol` | Digit can only go in one cell within a house |
165
+ | `hiddenPair` / `hiddenTriple` / `hiddenQuad` | N digits confined to N cells in a house |
166
+ | `lockedCandidatePointing` | Box candidates for a digit align in one row/col, eliminating from that row/col outside the box |
167
+ | `lockedCandidateClaiming` | Row/col candidates for a digit align in one box, eliminating from that box |
168
+ | `nakedPair` / `nakedTriple` / `nakedQuad` | N cells in a house share exactly N candidates |
169
+ | `xWing` / `swordfish` / `jellyfish` | Fish patterns of size 2, 3, 4 |
170
+ | `skyscraper` | Two rows/cols share a digit in exactly two columns/rows |
171
+ | `twoStringKite` | Intersecting conjugate pairs in a box |
172
+ | `yWing` / `xyzWing` | Pivot cell with two/three bivalue wings |
173
+ | `finnedXWing` / `finnedSwordfish` / `finnedJellyfish` | Fish with extra fin candidates |
174
+ | `wWing` | Two bivalue cells connected by a strong link on a shared digit |
175
+ | `emptyRectangle` | Eliminates via an empty rectangle in a box |
176
+ | `uniqueRectangleType1`–`Type4` | Avoids deadly pattern in a rectangle of four cells |
177
+ | `bug` | Bivalue Universal Grave — all candidates bivalue except one |
178
+ | `xyChain` | Chain of bivalue cells |
179
+ | `aic` / `aicRing` | Alternating Inference Chain |
180
+ | `groupedAIC` | AIC with grouped nodes (multiple cells as one node) |
181
+ | `alsXZ` | Almost Locked Sets linked by a restricted common candidate |
182
+ | `sueDeCoq` | Sue de Coq pattern in a box/line intersection |
183
+ | `deathBlossom` | ALS petals hanging off a stem cell |
184
+ | `nishio` | Contradiction-based elimination |
185
+ | `nishioNet` | Nishio extended to a network |
186
+ | `cellRegionForcingChain` / `cellRegionForcingNet` | All candidates in a cell/region force the same conclusion |
187
+ | `forcingChain` | Multiple chains from the same candidate converge |
188
+
189
+ ## Development
190
+
191
+ ```bash
192
+ npm install # install dependencies
193
+ npm test # run tests
194
+ npm run build # compile to dist/
195
+ npm run lint # lint
196
+ npm run typecheck # type-check without emitting
197
+ ```
198
+
199
+ Output is an ES module at `dist/index.mjs` with TypeScript declarations at `dist/index.d.mts`.
200
+
201
+ ## License
202
+
203
+ [GPL-3.0](LICENSE)
@@ -0,0 +1,92 @@
1
+ //#region src/types/Notes.d.ts
2
+ interface Notes {
3
+ [key: number]: boolean;
4
+ }
5
+ //#endregion
6
+ //#region src/types/Value.d.ts
7
+ type Value = number | boolean | Notes | undefined;
8
+ //#endregion
9
+ //#region src/types/PuzzleBox.d.ts
10
+ interface PuzzleBox<T extends Value = number | Notes> {
11
+ 0: T[];
12
+ 1: T[];
13
+ 2: T[];
14
+ }
15
+ //#endregion
16
+ //#region src/types/PuzzleRow.d.ts
17
+ interface PuzzleRow<T extends Value = number | Notes> {
18
+ 0: PuzzleBox<T>;
19
+ 1: PuzzleBox<T>;
20
+ 2: PuzzleBox<T>;
21
+ }
22
+ //#endregion
23
+ //#region src/types/Puzzle.d.ts
24
+ interface Puzzle<T extends Value = number | Notes> {
25
+ 0: PuzzleRow<T>;
26
+ 1: PuzzleRow<T>;
27
+ 2: PuzzleRow<T>;
28
+ }
29
+ //#endregion
30
+ //#region src/types/Placement.d.ts
31
+ interface Placement {
32
+ cell: number;
33
+ digit: number;
34
+ }
35
+ //#endregion
36
+ //#region src/types/Elimination.d.ts
37
+ interface Elimination {
38
+ cell: number;
39
+ digit: number;
40
+ }
41
+ //#endregion
42
+ //#region src/types/ChainNode.d.ts
43
+ interface ChainNode {
44
+ cell: number;
45
+ digit: number;
46
+ isOn: boolean;
47
+ cells?: number[];
48
+ linkToNext?: 'strong' | 'weak';
49
+ }
50
+ //#endregion
51
+ //#region src/types/Technique.d.ts
52
+ type Technique = 'nakedSingle' | 'hiddenSingleBox' | 'hiddenSingleRow' | 'hiddenSingleCol' | 'nakedPair' | 'nakedTriple' | 'nakedQuad' | 'hiddenPair' | 'hiddenTriple' | 'hiddenQuad' | 'lockedCandidatePointing' | 'lockedCandidateClaiming' | 'xWing' | 'swordfish' | 'jellyfish' | 'skyscraper' | 'twoStringKite' | 'finnedXWing' | 'finnedSwordfish' | 'finnedJellyfish' | 'emptyRectangle' | 'wWing' | 'yWing' | 'xyzWing' | 'uniqueRectangleType1' | 'uniqueRectangleType2' | 'uniqueRectangleType3' | 'uniqueRectangleType4' | 'uniqueRectangleType5' | 'bug' | 'xyChain' | 'aic' | 'aicRing' | 'groupedAIC' | 'alsXZ' | 'sueDeCoq' | 'deathBlossom' | 'nishio' | 'nishioNet' | 'cellRegionForcingChain' | 'cellRegionForcingNet' | 'forcingChain';
53
+ //#endregion
54
+ //#region src/types/HintResult.d.ts
55
+ interface HintResult {
56
+ technique: Technique;
57
+ placements: Placement[];
58
+ eliminations: Elimination[];
59
+ patternCells: number[];
60
+ hiddenDigits?: number[];
61
+ patternDigits?: number[];
62
+ chainPath?: ChainNode[];
63
+ stemCell?: number;
64
+ petalCells?: number[][];
65
+ als1Cells?: number[];
66
+ als2Cells?: number[];
67
+ eureka?: string;
68
+ explanation?: string;
69
+ }
70
+ //#endregion
71
+ //#region src/humanSolver.d.ts
72
+ type Grid = number[];
73
+ type Candidates = Set<number>[];
74
+ declare const puzzleToGrid: (puzzle: Puzzle<number | Notes>) => Grid;
75
+ declare const gridToPuzzle: (grid: Grid) => Puzzle<number>;
76
+ declare const buildCandidates: (grid: Grid) => Candidates;
77
+ declare const isGridInvalid: (grid: Grid) => boolean;
78
+ declare const applyHint: (grid: Grid, candidates: Candidates, hint: Pick<HintResult, 'placements' | 'eliminations'>) => {
79
+ grid: Grid;
80
+ candidates: Candidates;
81
+ };
82
+ declare const findHint: (grid: Grid, candidates: Candidates) => HintResult | null;
83
+ declare const humanSolve: (initialGrid: Grid) => {
84
+ grid: Grid;
85
+ candidates: Candidates;
86
+ steps: HintResult[];
87
+ };
88
+ //#endregion
89
+ //#region src/types/PuzzleRowOrColumn.d.ts
90
+ type PuzzleRowOrColumn = 0 | 1 | 2;
91
+ //#endregion
92
+ export { type ChainNode, type Elimination, type HintResult, type Notes, type Placement, type Puzzle, type PuzzleBox, type PuzzleRow, type PuzzleRowOrColumn, type Technique, type Value, applyHint, buildCandidates, findHint, gridToPuzzle, humanSolve, isGridInvalid, puzzleToGrid };