nmx.sudokugen.library 0.0.0-stage → 1.1.0
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/LICENSE +21 -0
- package/README.md +154 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/random.d.ts +23 -0
- package/dist/random.js +67 -0
- package/dist/solver.d.ts +194 -0
- package/dist/solver.js +665 -0
- package/dist/sudoku.d.ts +107 -0
- package/dist/sudoku.js +244 -0
- package/dist/utility.d.ts +31 -0
- package/dist/utility.js +76 -0
- package/package.json +44 -5
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2026 New Media XP
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,155 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Sudoku Gen Library
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`sudoku` `javascript` `typescript` `library` `npm`
|
|
4
|
+
|
|
5
|
+
© 2024-2026 New Media XP. Licensed under the [MIT License](https://github.com/newmediaxp/sudokugen/blob/main/LICENSE).
|
|
6
|
+
|
|
|
7
|
+
[website](https://www.newmediaxp.com)
|
|
8
|
+
|
|
|
9
|
+
[email](mailto:contact@newmediaxp.com)
|
|
10
|
+
|
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Description
|
|
15
|
+
|
|
16
|
+
A library written in TypeScript to generate and solve sudoku puzzles, from rank 2 (4×4) up to rank 5 (25×25).
|
|
17
|
+
|
|
18
|
+
* Every created puzzle has exactly one solution.
|
|
19
|
+
* A seed reproduces the same puzzle, and gives the same puzzle as the
|
|
20
|
+
[C# library](https://www.nuget.org/packages/NMX.SudokuGen.Library) does for that seed.
|
|
21
|
+
* It can also check a board for conflicts and shuffle a puzzle into an equivalent one.
|
|
22
|
+
|
|
23
|
+
It ships as plain ES modules with type declarations and has no dependencies,
|
|
24
|
+
so it runs in browsers, Node.js, Deno and Bun.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
npm install nmx.sudokugen.library
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
In a browser without a bundler, import it from a CDN:
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
import { Sudoku } from "https://cdn.jsdelivr.net/npm/nmx.sudokugen.library@1/+esm";
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## API
|
|
43
|
+
|
|
44
|
+
```js
|
|
45
|
+
import { Sudoku, Conflict, getPuzzleCode } from "nmx.sudokugen.library";
|
|
46
|
+
|
|
47
|
+
const a_sudoku = Sudoku.create(3, 40); // rank 3 (9×9), up to 40 blanks
|
|
48
|
+
const a_seeded = Sudoku.create(3, 40, { seed: 777 }); // the same seed gives the same puzzle
|
|
49
|
+
const a_solved = Sudoku.solve(a_sudoku.puzzle); // throws unless the puzzle has exactly one solution
|
|
50
|
+
const a_code = getPuzzleCode(a_sudoku.puzzle); // e.g. "390560027.001809405...."
|
|
51
|
+
|
|
52
|
+
const a_board = [...a_sudoku.puzzle]; // the board being played
|
|
53
|
+
a_board[0] = 5;
|
|
54
|
+
if (a_sudoku.findConflicts(a_board, 0) & Conflict.Row) console.log("5 is already in the row");
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### `Sudoku`
|
|
58
|
+
|
|
59
|
+
Immutable puzzle/solution pair. Grids are flat, row-by-row arrays of `squares` values, `0` = blank.
|
|
60
|
+
Get one from `Sudoku.create`, `Sudoku.solve` or `shuffle`; the constructor is private.
|
|
61
|
+
|
|
62
|
+
#### Methods
|
|
63
|
+
|
|
64
|
+
| Member | Signature | Description |
|
|
65
|
+
|-----------------|---------------------------------------------------------------------------|-------------|
|
|
66
|
+
| `create` | `static create(rank, blanks, { seed?, exhaustive? } = {}): Sudoku` | New random puzzle with a unique solution. `blanks = Sudoku.maxBlanks` (-1) requests as many as possible. `seed` (any 32-bit integer) reproduces the puzzle. `exhaustive` skips the search budget for an exact blank count (can run very long at high ranks). |
|
|
67
|
+
| `solve` | `static solve(puzzle: ArrayLike<number>): Sudoku` | Solves a puzzle, rank inferred from its length. Takes arrays and typed arrays. Throws if the input is invalid, conflicting, or has no unique solution. |
|
|
68
|
+
| `shuffle` | `shuffle(seed?: number): Sudoku` | Returns an equivalent variant (same rank, difficulty and blank count, different look). The original is untouched. The same seed gives the same variant. |
|
|
69
|
+
| `findConflicts` | `findConflicts(board, index, input = board[index], excludeIndex = true): number` | Which of `index`'s units already hold `input`, as `Conflict` flags. Leave out `input` to check the value placed at `index`, or pass one to test a candidate before placing it. `excludeIndex = true` ignores the square's current value; `false` searches it too. A blank `input` (`0`) never conflicts. |
|
|
70
|
+
|
|
71
|
+
#### Properties
|
|
72
|
+
|
|
73
|
+
| Member | Type | Description |
|
|
74
|
+
|-----------------------|---------------------|--------------------------------------------------------------------------|
|
|
75
|
+
| `puzzle` | `readonly number[]` | The puzzle grid, `0` = blank. Frozen: copy it (`[...puzzle]`) to edit. |
|
|
76
|
+
| `solution` | `readonly number[]` | The unique completed solution. Frozen. |
|
|
77
|
+
| `removed` | `number` | Actual blank count (can be less than `blanks` when more would break uniqueness). |
|
|
78
|
+
| `rank` | `number` | Segment size, 2 (4×4) to 5 (25×25). |
|
|
79
|
+
| `rows` | `number` | Squares per row, column and segment (`rank²`). |
|
|
80
|
+
| `squares` | `number` | Total squares (`rows²`). |
|
|
81
|
+
| `remove` | `number` | Requested blank count (the `blanks` argument to `create`, or `squares` for `maxBlanks`). |
|
|
82
|
+
| `minRank` / `maxRank` | `static number` | Allowed rank bounds, `2` and `5`. |
|
|
83
|
+
| `maxBlanks` | `static number` | Sentinel for `create`'s `blanks`: `-1` means "as many as possible". |
|
|
84
|
+
|
|
85
|
+
### `Conflict`
|
|
86
|
+
|
|
87
|
+
Frozen bit flags, returned by `findConflicts`. Test them with `&`.
|
|
88
|
+
|
|
89
|
+
| Value | Meaning |
|
|
90
|
+
|-----------|------------------------------------------|
|
|
91
|
+
| `None` | No conflict (`0`). |
|
|
92
|
+
| `Row` | Value repeats in the row (`1`). |
|
|
93
|
+
| `Column` | Value repeats in the column (`2`). |
|
|
94
|
+
| `Segment` | Value repeats in the segment, or box (`4`). |
|
|
95
|
+
|
|
96
|
+
A square that is rule-clean but still wrong (`board[i] !== solution[i]`) returns `None`, so compare against `solution` to catch those.
|
|
97
|
+
|
|
98
|
+
### Puzzle codes
|
|
99
|
+
|
|
100
|
+
| Function | Signature | Description |
|
|
101
|
+
|-----------------|---------------------------------------------------|---------------------------------------------------------------------------------------------------------|
|
|
102
|
+
| `getPuzzleCode` | `getPuzzleCode(grid: ArrayLike<number>): string` | Encodes a grid as a puzzle code: one char per square (`0` blank, `1`-`9`, `A` = 10…), `.` between rows. |
|
|
103
|
+
| `getPuzzleArr` | `getPuzzleArr(code: string): number[]` | Parses a puzzle code back into a grid array; unrecognised characters are ignored. |
|
|
104
|
+
|
|
105
|
+
### Notes
|
|
106
|
+
|
|
107
|
+
* **Errors** — `create`, `shuffle` and `findConflicts` throw a `RangeError` for an argument outside its range,
|
|
108
|
+
such as a rank of 6 or a seed that isn't a 32-bit integer. `solve` throws an `Error` for any puzzle it can't solve
|
|
109
|
+
uniquely, with the reason in the message.
|
|
110
|
+
* **Speed** — every call is synchronous. On a desktop machine a rank 3 create takes about a millisecond,
|
|
111
|
+
a rank 4 create with `maxBlanks` around a tenth of a second, and a rank 5 create several seconds.
|
|
112
|
+
In a browser, run rank 4 and 5 creates in a [Web Worker](https://developer.mozilla.org/docs/Web/API/Web_Workers_API)
|
|
113
|
+
so the page stays responsive:
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
// sudoku-worker.js, started with new Worker("sudoku-worker.js", { type: "module" })
|
|
117
|
+
import { Sudoku } from "nmx.sudokugen.library";
|
|
118
|
+
|
|
119
|
+
self.onmessage = (p_event) => {
|
|
120
|
+
const a_sudoku = Sudoku.create(p_event.data.rank, p_event.data.blanks);
|
|
121
|
+
self.postMessage({ puzzle: a_sudoku.puzzle, solution: a_sudoku.solution });
|
|
122
|
+
};
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
* **State** — instances share no state, so any number of them can be created and used independently.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Details
|
|
130
|
+
|
|
131
|
+
* Dependencies
|
|
132
|
+
|
|
133
|
+
* None. Needs an ES2022 runtime: current browsers, Node.js 20 or later, Deno, Bun.
|
|
134
|
+
|
|
135
|
+
* Other languages
|
|
136
|
+
|
|
137
|
+
* C# library: [NMX.SudokuGen.Library](https://www.nuget.org/packages/NMX.SudokuGen.Library)
|
|
138
|
+
|
|
139
|
+
* Downloads
|
|
140
|
+
|
|
141
|
+
* [npm](https://www.npmjs.com/package/nmx.sudokugen.library)
|
|
142
|
+
|
|
143
|
+
* Source
|
|
144
|
+
|
|
145
|
+
* [GitHub](https://github.com/newmediaxp/sudokugen/tree/main/js/nmx.sudokugen.library)
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Contact
|
|
150
|
+
|
|
151
|
+
* Link: <https://www.newmediaxp.com/contact>
|
|
152
|
+
* Email-1: <contact@newmediaxp.com>
|
|
153
|
+
* Email-2: <animaxneil@gmail.com>
|
|
154
|
+
|
|
155
|
+
---
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/random.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knuth's subtractive generator, step for step as .NET's seeded System.Random runs it,
|
|
3
|
+
* so a seed gives the same puzzle here as in the C# library.
|
|
4
|
+
* The Int32Array stores wrap like C#'s unchecked int arithmetic, which large seeds rely on.
|
|
5
|
+
*/
|
|
6
|
+
export declare class Random {
|
|
7
|
+
private readonly seedArray;
|
|
8
|
+
private iNext;
|
|
9
|
+
private iNextP;
|
|
10
|
+
/**
|
|
11
|
+
* @param p_seed Any 32-bit integer; like .NET, a seed and its negation give the same sequence.
|
|
12
|
+
*/
|
|
13
|
+
constructor(p_seed: number);
|
|
14
|
+
/**
|
|
15
|
+
* An integer within p_min..p_max - 1, as .NET's Random.Next(min, max) returns it.
|
|
16
|
+
* Only ranges up to 2^31 - 1 wide; .NET draws wider ones differently.
|
|
17
|
+
*/
|
|
18
|
+
next(p_min: number, p_max: number): number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A seed for the unseeded calls; their results only need to differ, not to be unpredictable.
|
|
22
|
+
*/
|
|
23
|
+
export declare function randomSeed(): number;
|
package/dist/random.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Copyright (c) 2024-2026 New Media XP. Licensed under the MIT License.
|
|
2
|
+
const mBig = 2147483647, mSeed = 161803398, sampleScale = 1 / mBig;
|
|
3
|
+
/**
|
|
4
|
+
* Knuth's subtractive generator, step for step as .NET's seeded System.Random runs it,
|
|
5
|
+
* so a seed gives the same puzzle here as in the C# library.
|
|
6
|
+
* The Int32Array stores wrap like C#'s unchecked int arithmetic, which large seeds rely on.
|
|
7
|
+
*/
|
|
8
|
+
export class Random {
|
|
9
|
+
seedArray = new Int32Array(56);
|
|
10
|
+
iNext = 0;
|
|
11
|
+
iNextP = 21;
|
|
12
|
+
/**
|
|
13
|
+
* @param p_seed Any 32-bit integer; like .NET, a seed and its negation give the same sequence.
|
|
14
|
+
*/
|
|
15
|
+
constructor(p_seed) {
|
|
16
|
+
const a_seedArray = this.seedArray;
|
|
17
|
+
let a_mj = mSeed - (p_seed === -2147483648 ? mBig : Math.abs(p_seed)), a_mk = 1;
|
|
18
|
+
a_seedArray[55] = a_mj;
|
|
19
|
+
for (let a_ii = 0, i = 1; i < 55; ++i) {
|
|
20
|
+
// the range [1..55] is special (Knuth), so position 0 stays unused
|
|
21
|
+
a_ii += 21;
|
|
22
|
+
if (a_ii >= 55)
|
|
23
|
+
a_ii -= 55;
|
|
24
|
+
a_seedArray[a_ii] = a_mk;
|
|
25
|
+
a_mk = (a_mj - a_mk) | 0;
|
|
26
|
+
if (a_mk < 0)
|
|
27
|
+
a_mk = (a_mk + mBig) | 0;
|
|
28
|
+
a_mj = a_seedArray[a_ii];
|
|
29
|
+
}
|
|
30
|
+
for (let k = 1; k < 5; ++k)
|
|
31
|
+
for (let i = 1; i < 56; ++i) {
|
|
32
|
+
let a_n = i + 30;
|
|
33
|
+
if (a_n >= 55)
|
|
34
|
+
a_n -= 55;
|
|
35
|
+
a_seedArray[i] -= a_seedArray[1 + a_n];
|
|
36
|
+
if (a_seedArray[i] < 0)
|
|
37
|
+
a_seedArray[i] += mBig;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* An integer within p_min..p_max - 1, as .NET's Random.Next(min, max) returns it.
|
|
42
|
+
* Only ranges up to 2^31 - 1 wide; .NET draws wider ones differently.
|
|
43
|
+
*/
|
|
44
|
+
next(p_min, p_max) {
|
|
45
|
+
const a_seedArray = this.seedArray;
|
|
46
|
+
let a_iNext = this.iNext + 1, a_iNextP = this.iNextP + 1;
|
|
47
|
+
if (a_iNext >= 56)
|
|
48
|
+
a_iNext = 1;
|
|
49
|
+
if (a_iNextP >= 56)
|
|
50
|
+
a_iNextP = 1;
|
|
51
|
+
let a_sample = (a_seedArray[a_iNext] - a_seedArray[a_iNextP]) | 0;
|
|
52
|
+
if (a_sample === mBig)
|
|
53
|
+
--a_sample;
|
|
54
|
+
if (a_sample < 0)
|
|
55
|
+
a_sample += mBig;
|
|
56
|
+
a_seedArray[a_iNext] = a_sample;
|
|
57
|
+
this.iNext = a_iNext;
|
|
58
|
+
this.iNextP = a_iNextP;
|
|
59
|
+
return Math.trunc(a_sample * sampleScale * (p_max - p_min)) + p_min;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* A seed for the unseeded calls; their results only need to differ, not to be unpredictable.
|
|
64
|
+
*/
|
|
65
|
+
export function randomSeed() {
|
|
66
|
+
return (Math.random() * 4294967296) | 0;
|
|
67
|
+
}
|
package/dist/solver.d.ts
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import type { Random } from "./random.ts";
|
|
2
|
+
/**
|
|
3
|
+
* The search engine behind Sudoku.create and Sudoku.solve: a port of the C# Sudoku class's
|
|
4
|
+
* private state and methods, with the same names (lower camel case) so changes port line by line.
|
|
5
|
+
* Each instance serves one create or solve call; the Sudoku it yields keeps only the grids.
|
|
6
|
+
* Grids are flat row-by-row arrays of squares values, 0 = blank; masks hold bit v for value v.
|
|
7
|
+
*/
|
|
8
|
+
export declare class Solver {
|
|
9
|
+
readonly rank: number;
|
|
10
|
+
readonly rows: number;
|
|
11
|
+
readonly squares: number;
|
|
12
|
+
readonly remove: number;
|
|
13
|
+
removed: number;
|
|
14
|
+
readonly puzzle: Int32Array;
|
|
15
|
+
readonly solution: Int32Array;
|
|
16
|
+
private readonly squaresInSegmentRow;
|
|
17
|
+
private readonly trialBudget;
|
|
18
|
+
private readonly altSolution;
|
|
19
|
+
private readonly randomRows;
|
|
20
|
+
private readonly rowMask;
|
|
21
|
+
private readonly colMask;
|
|
22
|
+
private readonly segMask;
|
|
23
|
+
private readonly rowOnceMask;
|
|
24
|
+
private readonly colOnceMask;
|
|
25
|
+
private readonly segOnceMask;
|
|
26
|
+
private readonly rowTwiceMask;
|
|
27
|
+
private readonly colTwiceMask;
|
|
28
|
+
private readonly segTwiceMask;
|
|
29
|
+
private readonly rowThriceMask;
|
|
30
|
+
private readonly colThriceMask;
|
|
31
|
+
private readonly segThriceMask;
|
|
32
|
+
private readonly candMask;
|
|
33
|
+
private readonly rowBlockMask;
|
|
34
|
+
private readonly colBlockMask;
|
|
35
|
+
private readonly rowBlockElim;
|
|
36
|
+
private readonly colBlockElim;
|
|
37
|
+
private readonly rowConfMask;
|
|
38
|
+
private readonly colConfMask;
|
|
39
|
+
private readonly nakedSquares;
|
|
40
|
+
private readonly nakedMasks;
|
|
41
|
+
private readonly fullMask;
|
|
42
|
+
private readonly rowOf;
|
|
43
|
+
private readonly colOf;
|
|
44
|
+
private readonly segOf;
|
|
45
|
+
private readonly rowBlockOf;
|
|
46
|
+
private readonly colBlockOf;
|
|
47
|
+
private readonly unitSquares;
|
|
48
|
+
private fillNodesLeft;
|
|
49
|
+
private bestIdx;
|
|
50
|
+
private bestCount;
|
|
51
|
+
private bestMask;
|
|
52
|
+
private eliminated;
|
|
53
|
+
/**
|
|
54
|
+
* @param p_rank Segment size, already validated.
|
|
55
|
+
* @param p_remove Blank squares to aim for, already resolved and validated.
|
|
56
|
+
* @param p_exhaustive Skips the per-trial node budget (see trialBudgetPerSquare).
|
|
57
|
+
*/
|
|
58
|
+
constructor(p_rank: number, p_remove: number, p_exhaustive: boolean);
|
|
59
|
+
/**
|
|
60
|
+
* Seeds the diagonal segments with random permutations. They never share a row or column,
|
|
61
|
+
* so they can be filled independently without backtracking.
|
|
62
|
+
*/
|
|
63
|
+
private set_DiagonalSegments;
|
|
64
|
+
/**
|
|
65
|
+
* True when puzzle stays uniquely solvable after blanking p_idx.
|
|
66
|
+
* The puzzle was unique before the removal, so a second solution would have to place another
|
|
67
|
+
* conflict-free candidate there; each is trialled for solvability under trialBudget.
|
|
68
|
+
* An exhausted trial counts as solvable, rejecting the removal — safe, but may undershoot max blanks.
|
|
69
|
+
* @param p_idx Index of the square just blanked in puzzle.
|
|
70
|
+
* @param p_removed The solution value the square held before blanking.
|
|
71
|
+
* @returns True when no alternative value at p_idx completes the puzzle.
|
|
72
|
+
*/
|
|
73
|
+
private uniqueWithout;
|
|
74
|
+
/**
|
|
75
|
+
* Blanks random squares one at a time, keeping only removals that preserve uniqueness (via uniqueWithout),
|
|
76
|
+
* until remove blanks are reached or no square can be removed.
|
|
77
|
+
*/
|
|
78
|
+
prune(p_random: Random): void;
|
|
79
|
+
/**
|
|
80
|
+
* Rebuilds rowMask, colMask and segMask from p_arr:
|
|
81
|
+
* bit v of a unit's mask is set when value v is placed in that unit.
|
|
82
|
+
* @param p_arr The grid the masks should describe.
|
|
83
|
+
*/
|
|
84
|
+
private loadMasks;
|
|
85
|
+
/**
|
|
86
|
+
* Recursive backtracker over the blank squares of p_arr, always working on the most constrained blank first,
|
|
87
|
+
* forced squares (one candidate left, or a hidden single) are placed without branching,
|
|
88
|
+
* and a grid is abandoned the moment any blank or needed unit value runs out of options.
|
|
89
|
+
* Deductions escalate lazily: only when nothing is forced are locked candidates derived
|
|
90
|
+
* (see derive_BlockEliminations) and the scan repeated on the narrowed candidates,
|
|
91
|
+
* then naked pairs (see search_NakedPairs) as the last rung before guessing,
|
|
92
|
+
* so easy grids never pay for the expensive deductions. All deductions keep every completion.
|
|
93
|
+
* When guessing is unavoidable, a two-homes value (see fillValuePair) is preferred
|
|
94
|
+
* over a square with 3+ candidates.
|
|
95
|
+
* p_mode decides the candidate order per square.
|
|
96
|
+
* The masks must describe p_arr on entry and are stale after a completed fill — enter via fillRemaining.
|
|
97
|
+
* @param p_arr The grid to fill, solution or altSolution.
|
|
98
|
+
* @param p_mode Candidate order per square.
|
|
99
|
+
* @returns True when the grid was completed, or the node budget ran out (leaving p_arr
|
|
100
|
+
* partially filled — see fillRemaining); a failed fill leaves it and the masks unchanged.
|
|
101
|
+
*/
|
|
102
|
+
private fillConstrained;
|
|
103
|
+
/**
|
|
104
|
+
* One deduction pass over the blanks of p_arr: refreshes candMask,
|
|
105
|
+
* rebuilds the once/twice/thrice unit masks and picks the most constrained square.
|
|
106
|
+
* Stops early on a naked single, leaving the unit masks incomplete — the callers' count guards cover that.
|
|
107
|
+
* Sets bestIdx (the most constrained blank square; -1 when the grid is full), bestCount
|
|
108
|
+
* (that square's candidate count) and bestMask (that square's candidate mask).
|
|
109
|
+
* @param p_arr The grid being filled.
|
|
110
|
+
* @param p_refine False derives candidates from the unit masks; true narrows the stored ones
|
|
111
|
+
* by rowBlockElim/colBlockElim (see derive_BlockEliminations).
|
|
112
|
+
* @returns False when some blank has no candidate left.
|
|
113
|
+
*/
|
|
114
|
+
private scan_Candidates;
|
|
115
|
+
/**
|
|
116
|
+
* Unit-level deductions over the masks of a completed scan_Candidates pass: fails the
|
|
117
|
+
* fill when a unit can no longer host a value it still needs, and redirects the fill to a hidden
|
|
118
|
+
* single (a value with one possible square left in a unit) when one exists, updating
|
|
119
|
+
* bestIdx, bestCount (to 1) and bestMask (to the value's bit).
|
|
120
|
+
* @param p_arr The grid being filled.
|
|
121
|
+
* @returns False when a needed value has no square left in some unit.
|
|
122
|
+
*/
|
|
123
|
+
private search_UnitForced;
|
|
124
|
+
/**
|
|
125
|
+
* Locked candidates: summarises candMask per block (line∩segment intersection) and
|
|
126
|
+
* collects into rowBlockElim/colBlockElim the values each block cannot host:
|
|
127
|
+
* a value confined to one block of a segment stays out of the block's line elsewhere (pointing),
|
|
128
|
+
* one confined to one block of a line stays out of the block's segment elsewhere (claiming).
|
|
129
|
+
* @param p_arr The grid being filled; candMask must describe its blanks.
|
|
130
|
+
*/
|
|
131
|
+
private derive_BlockEliminations;
|
|
132
|
+
/**
|
|
133
|
+
* Naked pairs: two blanks of a unit sharing the same two candidates lock those values between them,
|
|
134
|
+
* so both can be removed from every other square of the unit. Eliminations are applied directly to
|
|
135
|
+
* candMask; the caller rescans when any were made (eliminated is set true).
|
|
136
|
+
* @param p_arr The grid being filled; candMask must describe its blanks.
|
|
137
|
+
* @returns False when an elimination leaves a square without candidates
|
|
138
|
+
* (e.g. a third square of the unit held the same bare pair).
|
|
139
|
+
*/
|
|
140
|
+
private search_NakedPairs;
|
|
141
|
+
/**
|
|
142
|
+
* Locates the one blank square of a unit that can still take the value of p_valueBit.
|
|
143
|
+
* Only called after the unit scan in fillConstrained proved exactly one such square exists.
|
|
144
|
+
* @param p_arr The grid being filled.
|
|
145
|
+
* @param p_unitOf Square→unit lookup of the unit's type: rowOf, colOf or segOf.
|
|
146
|
+
* @param p_unit Index of the unit within its type.
|
|
147
|
+
* @param p_valueBit Bit of the hidden single's value.
|
|
148
|
+
* @returns Index of the found square.
|
|
149
|
+
*/
|
|
150
|
+
private search_HiddenSingle;
|
|
151
|
+
/**
|
|
152
|
+
* Binary branch of fillConstrained: places the value of p_valueBit in each of its two possible
|
|
153
|
+
* squares of a unit in turn and recurses — every completion must use one of them.
|
|
154
|
+
* In Uniqueness mode the square matching solution is tried last, preserving the steer-away order.
|
|
155
|
+
* @param p_arr The grid being filled.
|
|
156
|
+
* @param p_mode Candidate order per square, passed through to the recursion.
|
|
157
|
+
* @param p_unitOf Square→unit lookup of the unit's type: rowOf, colOf or segOf.
|
|
158
|
+
* @param p_unit Index of the unit within its type.
|
|
159
|
+
* @param p_valueBit Bit of the value with exactly two possible squares in the unit.
|
|
160
|
+
* @returns True when the grid was completed; see fillConstrained for the budget caveat.
|
|
161
|
+
*/
|
|
162
|
+
private fillValuePair;
|
|
163
|
+
/**
|
|
164
|
+
* Syncs the masks to p_arr via loadMasks, then runs fillConstrained over the whole grid.
|
|
165
|
+
* @param p_arr The grid to fill, solution or altSolution.
|
|
166
|
+
* @param p_mode Candidate order per square.
|
|
167
|
+
* @param p_nodeBudget Search nodes the fill may spend; exhaustive when omitted. An exhausted fill
|
|
168
|
+
* returns true but leaves p_arr partially filled — only safe on scratch grids (see uniqueWithout).
|
|
169
|
+
* @returns True when the grid was completed (or the budget ran out); a failed fill leaves p_arr unchanged.
|
|
170
|
+
*/
|
|
171
|
+
private fillRemaining;
|
|
172
|
+
/**
|
|
173
|
+
* Generates a complete random solution grid: seeds the diagonal segments,
|
|
174
|
+
* then backtrack-fills the rest in randomised order.
|
|
175
|
+
* @throws {Error} The grid could not be completed. Impossible for an empty grid,
|
|
176
|
+
* so this signals a bug in the fill logic itself.
|
|
177
|
+
*/
|
|
178
|
+
fillAll(p_random: Random): void;
|
|
179
|
+
/**
|
|
180
|
+
* Completes solution from the already-validated puzzle inputs by deterministic backtracking.
|
|
181
|
+
* @returns False when the puzzle has no solution, e.g. inputs that are conflict-free but still unsatisfiable.
|
|
182
|
+
*/
|
|
183
|
+
fillRest(): boolean;
|
|
184
|
+
/**
|
|
185
|
+
* True when every non-blank square of p_arr is within 1..rows and conflict-free.
|
|
186
|
+
* Repurposes the unit masks as scratch; every fill resyncs them anyway (see fillRemaining).
|
|
187
|
+
*/
|
|
188
|
+
valid(p_arr: Int32Array): boolean;
|
|
189
|
+
/**
|
|
190
|
+
* True when solution is the only completion of puzzle: the Uniqueness fill steers away from the known solution,
|
|
191
|
+
* so ending up identical proves uniqueness.
|
|
192
|
+
*/
|
|
193
|
+
unique(): boolean;
|
|
194
|
+
}
|