@johnmorrisdotca/kazu 1.0.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.
Files changed (76) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/LICENSE +21 -0
  3. package/README.md +568 -0
  4. package/dist/cells.d.ts +28 -0
  5. package/dist/cells.js +65 -0
  6. package/dist/check.d.ts +15 -0
  7. package/dist/check.js +189 -0
  8. package/dist/clock.d.ts +2 -0
  9. package/dist/clock.js +9 -0
  10. package/dist/conflicts.d.ts +14 -0
  11. package/dist/conflicts.js +79 -0
  12. package/dist/draw-entry.d.ts +14 -0
  13. package/dist/draw-entry.js +10 -0
  14. package/dist/draw.d.ts +47 -0
  15. package/dist/draw.js +219 -0
  16. package/dist/element-define.d.ts +1 -0
  17. package/dist/element-define.js +13 -0
  18. package/dist/element.d.ts +48 -0
  19. package/dist/element.js +150 -0
  20. package/dist/game.d.ts +65 -0
  21. package/dist/game.js +120 -0
  22. package/dist/generate.d.ts +11 -0
  23. package/dist/generate.js +35 -0
  24. package/dist/geometry.d.ts +33 -0
  25. package/dist/geometry.js +27 -0
  26. package/dist/givens.d.ts +30 -0
  27. package/dist/givens.js +43 -0
  28. package/dist/groupSolve.d.ts +68 -0
  29. package/dist/groupSolve.js +284 -0
  30. package/dist/hint.d.ts +39 -0
  31. package/dist/hint.js +158 -0
  32. package/dist/index.d.ts +36 -0
  33. package/dist/index.js +31 -0
  34. package/dist/jigsaw.d.ts +21 -0
  35. package/dist/jigsaw.js +144 -0
  36. package/dist/kinds.d.ts +51 -0
  37. package/dist/kinds.js +40 -0
  38. package/dist/layout.d.ts +63 -0
  39. package/dist/layout.js +128 -0
  40. package/dist/moreOrLess.d.ts +7 -0
  41. package/dist/moreOrLess.js +90 -0
  42. package/dist/moreOrLessCode.d.ts +22 -0
  43. package/dist/moreOrLessCode.js +52 -0
  44. package/dist/moreOrLessSolve.d.ts +45 -0
  45. package/dist/moreOrLessSolve.js +180 -0
  46. package/dist/mount.d.ts +117 -0
  47. package/dist/mount.js +558 -0
  48. package/dist/names.d.ts +41 -0
  49. package/dist/names.js +163 -0
  50. package/dist/numberPlace.d.ts +14 -0
  51. package/dist/numberPlace.js +123 -0
  52. package/dist/play-entry.d.ts +9 -0
  53. package/dist/play-entry.js +8 -0
  54. package/dist/playStyle.d.ts +11 -0
  55. package/dist/playStyle.js +47 -0
  56. package/dist/progress.d.ts +29 -0
  57. package/dist/progress.js +96 -0
  58. package/dist/random.d.ts +25 -0
  59. package/dist/random.js +42 -0
  60. package/dist/solve.d.ts +22 -0
  61. package/dist/solve.js +60 -0
  62. package/dist/strings.d.ts +17 -0
  63. package/dist/strings.js +143 -0
  64. package/dist/style.d.ts +13 -0
  65. package/dist/style.js +61 -0
  66. package/dist/sumCages.d.ts +60 -0
  67. package/dist/sumCages.js +190 -0
  68. package/dist/towers.d.ts +3 -0
  69. package/dist/towers.js +48 -0
  70. package/dist/towersCode.d.ts +31 -0
  71. package/dist/towersCode.js +79 -0
  72. package/dist/towersSolve.d.ts +65 -0
  73. package/dist/towersSolve.js +276 -0
  74. package/dist/version.d.ts +2 -0
  75. package/dist/version.js +2 -0
  76. package/package.json +104 -0
@@ -0,0 +1,35 @@
1
+ import { isKazuKind, isKazuLevel, isKazuSize } from "./kinds.js";
2
+ import { generateJigsaw } from "./jigsaw.js";
3
+ import { generateMoreOrLess } from "./moreOrLess.js";
4
+ import { generateDiagonal, generateNumberPlace } from "./numberPlace.js";
5
+ import { isKazuSeed } from "./random.js";
6
+ import { generateSumCages } from "./sumCages.js";
7
+ import { generateTowers } from "./towers.js";
8
+ const GENERATORS = {
9
+ "number-place": generateNumberPlace,
10
+ jigsaw: generateJigsaw,
11
+ diagonal: generateDiagonal,
12
+ "sum-cages": generateSumCages,
13
+ "more-or-less": generateMoreOrLess,
14
+ towers: generateTowers,
15
+ };
16
+ /**
17
+ * A puzzle of any of the six, from a seed: the one door. The same kind, size, level and seed make the
18
+ * same puzzle in every browser and every Node, for ever, which is what lets a solve be kept as those
19
+ * four and a race be handed one number. Every puzzle has exactly one answer, and an easy one yields
20
+ * to singles alone.
21
+ *
22
+ * Throws a RangeError for a kind, size, level or seed it does not make, rather than a puzzle made
23
+ * from something else.
24
+ */
25
+ export function generateKazu(kind, size, level, seed) {
26
+ if (!isKazuKind(kind))
27
+ throw new RangeError(`Kazu has no puzzle called ${String(kind)}.`);
28
+ if (!isKazuSize(kind, size))
29
+ throw new RangeError(`Kazu makes no ${kind} at ${size}×${size}.`);
30
+ if (!isKazuLevel(level))
31
+ throw new RangeError(`Kazu has no level called ${String(level)}.`);
32
+ if (!isKazuSeed(seed))
33
+ throw new RangeError(`${String(seed)} is not a seed: a whole number from 1 to 2147483647.`);
34
+ return GENERATORS[kind](size, level, seed);
35
+ }
@@ -0,0 +1,33 @@
1
+ import type { KazuKind } from "./kinds.ts";
2
+ /**
3
+ * WHERE EVERYTHING IS IN A DRAWING, so a page of your own can play it: the drawing is a square of
4
+ * `side` units, every cell `cell` units across, and a Towers square sits inside a ring one cell deep
5
+ * that holds its clues. Pure arithmetic, no page needed.
6
+ */
7
+ export type KazuGeometry = {
8
+ /** The side of the drawing, in its own units (the viewBox is `0 0 side side`). */
9
+ side: number;
10
+ /** The side of one cell. */
11
+ cell: number;
12
+ /** Where the grid's top left corner is. */
13
+ origin: number;
14
+ /** How many cells deep the ring round the grid is: 1 for Towers, which keeps its clues there, 0 for the rest. */
15
+ ring: number;
16
+ size: number;
17
+ /** The top left corner of a cell. */
18
+ corner: (index: number) => {
19
+ x: number;
20
+ y: number;
21
+ };
22
+ /** The middle of a cell. */
23
+ centre: (index: number) => {
24
+ x: number;
25
+ y: number;
26
+ };
27
+ /** The cell a point is over, or -1 for a point outside the grid. */
28
+ cellAt: (x: number, y: number) => number;
29
+ };
30
+ /** The units of a cell, and the margin round the drawing. */
31
+ export declare const KAZU_CELL = 100;
32
+ /** Where everything is in the drawing of a puzzle of this kind and side. */
33
+ export declare function kazuGeometry(kind: KazuKind, size: number): KazuGeometry;
@@ -0,0 +1,27 @@
1
+ /** The units of a cell, and the margin round the drawing. */
2
+ export const KAZU_CELL = 100;
3
+ const MARGIN = 10;
4
+ /** Where everything is in the drawing of a puzzle of this kind and side. */
5
+ export function kazuGeometry(kind, size) {
6
+ const ring = kind === "towers" ? 1 : 0;
7
+ const origin = MARGIN + ring * KAZU_CELL;
8
+ const side = 2 * MARGIN + (size + 2 * ring) * KAZU_CELL;
9
+ const corner = (index) => ({ x: origin + (index % size) * KAZU_CELL, y: origin + Math.floor(index / size) * KAZU_CELL });
10
+ return {
11
+ side,
12
+ cell: KAZU_CELL,
13
+ origin,
14
+ ring,
15
+ size,
16
+ corner,
17
+ centre: (index) => {
18
+ const at = corner(index);
19
+ return { x: at.x + KAZU_CELL / 2, y: at.y + KAZU_CELL / 2 };
20
+ },
21
+ cellAt: (x, y) => {
22
+ const col = Math.floor((x - origin) / KAZU_CELL);
23
+ const row = Math.floor((y - origin) / KAZU_CELL);
24
+ return col < 0 || row < 0 || col >= size || row >= size ? -1 : row * size + col;
25
+ },
26
+ };
27
+ }
@@ -0,0 +1,30 @@
1
+ import type { KazuKind } from "./kinds.ts";
2
+ import { type Layout } from "./layout.ts";
3
+ import { type Mark } from "./moreOrLessCode.ts";
4
+ import { type Cage } from "./sumCages.ts";
5
+ import { type TowerClues } from "./towersCode.ts";
6
+ /**
7
+ * WHAT A PUZZLE WAS PRINTED WITH, read from its givens code: the printed cells, and for a Jigsaw its
8
+ * regions, for Sum Cages its cages, for More or Less its marks, for Towers its clues. Each kind
9
+ * writes them after the cells. The drawing, the play and the hint all read a puzzle through this.
10
+ */
11
+ export type KazuGivens = {
12
+ kind: KazuKind;
13
+ size: number;
14
+ /** The printed numbers, row-major; 0 where nothing is printed. */
15
+ cells: number[];
16
+ /** The region each cell is drawn in (a box, or a Jigsaw's own region), which the heavy rules go round; null for More or Less and Towers. */
17
+ regions: number[] | null;
18
+ /** Whether the two long diagonals are groups too (Diagonal). */
19
+ diagonals: boolean;
20
+ /** Sum Cages: the cages and their sums. */
21
+ cages: Cage[] | null;
22
+ /** More or Less: which of two neighbouring cells is bigger. */
23
+ marks: Mark[];
24
+ /** Towers: the clues round the edge. */
25
+ clues: TowerClues | null;
26
+ };
27
+ /** The givens of a puzzle, or null for a code that is not one of that kind and side. Null rather than a puzzle with holes. */
28
+ export declare function readGivens(kind: KazuKind, size: number, code: string): KazuGivens | null;
29
+ /** The groups that must each hold every number once, for the four kinds made of groups; null for More or Less and Towers, which only have rows and columns. */
30
+ export declare function layoutOfGivens(givens: KazuGivens): Layout | null;
package/dist/givens.js ADDED
@@ -0,0 +1,43 @@
1
+ import { decodeCells } from "./cells.js";
2
+ import { decodeJigsaw } from "./jigsaw.js";
3
+ import { boxedLayout, cagedLayout, KAZU_BOXES, regionLayout } from "./layout.js";
4
+ import { decodeMoreOrLess } from "./moreOrLessCode.js";
5
+ import { decodeKiller } from "./sumCages.js";
6
+ import { decodeTowers } from "./towersCode.js";
7
+ /** The givens of a puzzle, or null for a code that is not one of that kind and side. Null rather than a puzzle with holes. */
8
+ export function readGivens(kind, size, code) {
9
+ const plain = { kind, size, regions: null, diagonals: false, cages: null, marks: [], clues: null };
10
+ const boxed = KAZU_BOXES[size] === undefined ? null : boxedLayout(size).region;
11
+ if (kind === "number-place" || kind === "diagonal") {
12
+ const cells = decodeCells(code, size);
13
+ return cells === null || boxed === null ? null : { ...plain, cells, regions: boxed, diagonals: kind === "diagonal" };
14
+ }
15
+ if (kind === "jigsaw") {
16
+ const read = decodeJigsaw(code, size);
17
+ return read === null ? null : { ...plain, cells: read.cells, regions: read.regions };
18
+ }
19
+ if (kind === "sum-cages") {
20
+ const read = decodeKiller(code, size);
21
+ return read === null || boxed === null ? null : { ...plain, cells: read.cells, regions: boxed, cages: read.cages };
22
+ }
23
+ if (kind === "more-or-less") {
24
+ const read = decodeMoreOrLess(code, size);
25
+ return read === null ? null : { ...plain, cells: read.cells, marks: read.marks };
26
+ }
27
+ if (kind === "towers") {
28
+ const read = decodeTowers(code, size);
29
+ return read === null ? null : { ...plain, cells: read.cells, clues: read.clues };
30
+ }
31
+ return null;
32
+ }
33
+ /** The groups that must each hold every number once, for the four kinds made of groups; null for More or Less and Towers, which only have rows and columns. */
34
+ export function layoutOfGivens(givens) {
35
+ const { kind, size } = givens;
36
+ if (kind === "number-place" || kind === "diagonal")
37
+ return boxedLayout(size, kind === "diagonal");
38
+ if (kind === "jigsaw")
39
+ return givens.regions === null ? null : regionLayout(size, givens.regions);
40
+ if (kind === "sum-cages")
41
+ return givens.cages === null ? null : cagedLayout(size, givens.cages);
42
+ return null;
43
+ }
@@ -0,0 +1,68 @@
1
+ import type { Layout } from "./layout.ts";
2
+ /**
3
+ * The solver for every puzzle made of groups: counting, singles, and how deep a guess goes.
4
+ *
5
+ * Three questions, one small engine, for Sudoku, Diagonal, Jigsaw and Sum Cages alike.
6
+ *
7
+ * - `countSolutions` says whether a puzzle has exactly one answer, which is the whole difference
8
+ * between a puzzle and a guessing game. It stops at two, so "many" costs no more than "two".
9
+ * - `applySingles` fills what pure reasoning fills: a cell with one candidate, or a value with one
10
+ * place left in a group.
11
+ * - `guessDepth` is the level: 0 when singles finish it, 1 when one guess and singles do, more when
12
+ * more. A level is what the solver needed, not how many givens were printed: twenty-four givens
13
+ * can be an easy grid.
14
+ *
15
+ * It reads a `Layout`, the groups that must each hold every number once, so classic Sudoku, Diagonal
16
+ * and Jigsaw are one solver with three lists of groups (see `layout.ts`).
17
+ */
18
+ /** A grid as cells, row-major; 0 is empty, 1..size is a value. */
19
+ export type Grid = number[];
20
+ /** The bitmask of values each group already holds. */
21
+ export declare function used(grid: Grid, layout: Layout): number[];
22
+ export declare function candidatesAt(layout: Layout, index: number, taken: number[], work: Grid): number;
23
+ export declare function bitCount(mask: number): number;
24
+ export declare function lowestBit(mask: number): number;
25
+ /**
26
+ * How many solutions the grid has, up to `limit`. Most-constrained cell
27
+ * first, so a grid with one answer is confirmed in a few hundred steps.
28
+ */
29
+ export declare function countSolutions(grid: Grid, layout: Layout, limit?: number): number;
30
+ /**
31
+ * The same count, giving up after `budget` steps — null then, never a number,
32
+ * because "I stopped looking" is not "there are none". A Killer's generator
33
+ * asks this of layouts that are sometimes slow to settle, and draws another
34
+ * rather than keeping a browser waiting.
35
+ */
36
+ export declare function countSolutionsWithin(grid: Grid, layout: Layout, limit: number, budget: number, first?: (answer: Grid) => void): number | null;
37
+ /**
38
+ * THE ANSWER, WORKED OUT FROM THE GIVENS: a finished puzzle kept before its
39
+ * grid was, drawn solved rather than as dealt. Every puzzle made here has
40
+ * exactly one answer, so the search stops at two and hands back the first
41
+ * only when there was no second. Null when there is none, or more than one,
42
+ * or the search ran past `budget`: a grid this cannot vouch for is never drawn
43
+ * as though it were the one that was solved.
44
+ */
45
+ export declare function solutionOf(grid: Grid, layout: Layout, budget?: number): Grid | null;
46
+ export type SinglesResult = {
47
+ grid: Grid;
48
+ solved: boolean;
49
+ contradiction: boolean;
50
+ };
51
+ /**
52
+ * Fill every cell that reasoning fills, until nothing more can be: naked
53
+ * singles (one candidate in a cell) and hidden singles (one cell for a value
54
+ * in a group). Returns a new grid; the input is left as it was.
55
+ */
56
+ export declare function applySingles(grid: Grid, layout: Layout): SinglesResult;
57
+ /**
58
+ * How many guesses, each followed by every single it lets loose, a solver
59
+ * needs to finish the grid: 0 when singles do it all, `Infinity` when the grid
60
+ * has no answer. Meant for a grid already known to have exactly one.
61
+ */
62
+ export declare function guessDepth(grid: Grid, layout: Layout): number;
63
+ /**
64
+ * A full grid for this layout, drawn at random, or null when the search runs
65
+ * past `budget` steps — which for a jigsaw means these regions are a poor
66
+ * layout to fill, and the caller draws others.
67
+ */
68
+ export declare function fillLayout(layout: Layout, random: () => number, budget?: number): Grid | null;
@@ -0,0 +1,284 @@
1
+ const ALL = (size) => (1 << (size + 1)) - 2; // bits 1..size set
2
+ /** The bitmask of values each group already holds. */
3
+ export function used(grid, layout) {
4
+ const taken = new Array(layout.groups.length).fill(0);
5
+ grid.forEach((value, index) => {
6
+ if (value === 0)
7
+ return;
8
+ for (const group of layout.groupsOf[index])
9
+ taken[group] |= 1 << value;
10
+ });
11
+ return taken;
12
+ }
13
+ export function candidatesAt(layout, index, taken, work) {
14
+ let blocked = 0;
15
+ for (const group of layout.groupsOf[index])
16
+ blocked |= taken[group];
17
+ const open = ALL(layout.size) & ~blocked;
18
+ return layout.cages === undefined ? open : open & cageAllows(layout, index, work, open);
19
+ }
20
+ /**
21
+ * The values a cell may take and still leave its cage able to reach its sum:
22
+ * whatever is left of the sum after this cell, made of the cage's other empty
23
+ * cells with numbers not yet in it. Checked by bounds — the smallest and
24
+ * largest those cells could add to — which is exact for the cell that fills
25
+ * the cage and a sound pruning before it.
26
+ */
27
+ function cageAllows(layout, index, work, open) {
28
+ const cage = layout.cages[layout.cageOf[index]];
29
+ let placed = 0;
30
+ let inCage = 0;
31
+ let empties = 0;
32
+ for (const cell of cage.cells) {
33
+ const value = work[cell];
34
+ if (value === 0)
35
+ empties += 1;
36
+ else {
37
+ placed += value;
38
+ inCage |= 1 << value;
39
+ }
40
+ }
41
+ const others = empties - 1;
42
+ let allowed = 0;
43
+ for (let left = open; left !== 0; left &= left - 1) {
44
+ const value = lowestBit(left);
45
+ const rest = cage.sum - placed - value;
46
+ if (others === 0 ? rest === 0 : reachable(rest, others, ALL(layout.size) & ~inCage & ~(1 << value), layout.size))
47
+ allowed |= 1 << value;
48
+ }
49
+ return allowed;
50
+ }
51
+ /** Whether `count` different values from `from` can add to `sum`, by the smallest and largest they could. */
52
+ function reachable(sum, count, from, size) {
53
+ let low = 0;
54
+ let taken = 0;
55
+ for (let value = 1; value <= size && taken < count; value += 1) {
56
+ if ((from & (1 << value)) !== 0) {
57
+ low += value;
58
+ taken += 1;
59
+ }
60
+ }
61
+ if (taken < count)
62
+ return false;
63
+ let high = 0;
64
+ taken = 0;
65
+ for (let value = size; value >= 1 && taken < count; value -= 1) {
66
+ if ((from & (1 << value)) !== 0) {
67
+ high += value;
68
+ taken += 1;
69
+ }
70
+ }
71
+ return low <= sum && sum <= high;
72
+ }
73
+ function place(layout, taken, index, value) {
74
+ for (const group of layout.groupsOf[index])
75
+ taken[group] |= 1 << value;
76
+ }
77
+ function lift(layout, taken, index, value) {
78
+ for (const group of layout.groupsOf[index])
79
+ taken[group] &= ~(1 << value);
80
+ }
81
+ export function bitCount(mask) {
82
+ let count = 0;
83
+ for (let m = mask; m !== 0; m &= m - 1)
84
+ count += 1;
85
+ return count;
86
+ }
87
+ export function lowestBit(mask) {
88
+ return 31 - Math.clz32(mask & -mask);
89
+ }
90
+ /** The empty cell with fewest candidates, or -1 when none is empty. A cell with none gives mask 0. */
91
+ function mostConstrained(work, layout, taken) {
92
+ let best = -1;
93
+ let bestMask = 0;
94
+ let bestCount = layout.size + 1;
95
+ for (let index = 0; index < work.length; index += 1) {
96
+ if (work[index] !== 0)
97
+ continue;
98
+ const mask = candidatesAt(layout, index, taken, work);
99
+ const count = bitCount(mask);
100
+ if (count < bestCount) {
101
+ best = index;
102
+ bestMask = mask;
103
+ bestCount = count;
104
+ if (count <= 1)
105
+ break;
106
+ }
107
+ }
108
+ return { index: best, mask: bestMask };
109
+ }
110
+ /**
111
+ * How many solutions the grid has, up to `limit`. Most-constrained cell
112
+ * first, so a grid with one answer is confirmed in a few hundred steps.
113
+ */
114
+ export function countSolutions(grid, layout, limit = 2) {
115
+ return countSolutionsWithin(grid, layout, limit, Infinity);
116
+ }
117
+ /**
118
+ * The same count, giving up after `budget` steps — null then, never a number,
119
+ * because "I stopped looking" is not "there are none". A Killer's generator
120
+ * asks this of layouts that are sometimes slow to settle, and draws another
121
+ * rather than keeping a browser waiting.
122
+ */
123
+ export function countSolutionsWithin(grid, layout, limit, budget, first) {
124
+ const work = [...grid];
125
+ const taken = used(work, layout);
126
+ let found = 0;
127
+ let steps = 0;
128
+ const step = () => {
129
+ if (found >= limit || steps > budget)
130
+ return;
131
+ steps += 1;
132
+ const { index, mask } = mostConstrained(work, layout, taken);
133
+ if (index === -1) {
134
+ if (found === 0)
135
+ first?.([...work]);
136
+ found += 1;
137
+ return;
138
+ }
139
+ for (let left = mask; left !== 0; left &= left - 1) {
140
+ const value = lowestBit(left);
141
+ work[index] = value;
142
+ place(layout, taken, index, value);
143
+ step();
144
+ lift(layout, taken, index, value);
145
+ work[index] = 0;
146
+ if (found >= limit || steps > budget)
147
+ return;
148
+ }
149
+ };
150
+ step();
151
+ return steps > budget && found < limit ? null : found;
152
+ }
153
+ /**
154
+ * THE ANSWER, WORKED OUT FROM THE GIVENS: a finished puzzle kept before its
155
+ * grid was, drawn solved rather than as dealt. Every puzzle made here has
156
+ * exactly one answer, so the search stops at two and hands back the first
157
+ * only when there was no second. Null when there is none, or more than one,
158
+ * or the search ran past `budget`: a grid this cannot vouch for is never drawn
159
+ * as though it were the one that was solved.
160
+ */
161
+ export function solutionOf(grid, layout, budget = 2000000) {
162
+ let answer = null;
163
+ const found = countSolutionsWithin(grid, layout, 2, budget, (first) => (answer = first));
164
+ return found === 1 ? answer : null;
165
+ }
166
+ /**
167
+ * Fill every cell that reasoning fills, until nothing more can be: naked
168
+ * singles (one candidate in a cell) and hidden singles (one cell for a value
169
+ * in a group). Returns a new grid; the input is left as it was.
170
+ */
171
+ export function applySingles(grid, layout) {
172
+ const work = [...grid];
173
+ let changed = true;
174
+ while (changed) {
175
+ changed = false;
176
+ const taken = used(work, layout);
177
+ // Naked singles.
178
+ for (let index = 0; index < work.length; index += 1) {
179
+ if (work[index] !== 0)
180
+ continue;
181
+ const mask = candidatesAt(layout, index, taken, work);
182
+ if (mask === 0)
183
+ return { grid: work, solved: false, contradiction: true };
184
+ if (bitCount(mask) === 1) {
185
+ const value = lowestBit(mask);
186
+ work[index] = value;
187
+ place(layout, taken, index, value);
188
+ changed = true;
189
+ }
190
+ }
191
+ // Hidden singles, group by group. Only a group that holds every number (a row, a column, a box, a
192
+ // region, a diagonal) has one: a cage holds some of them, so a number with one place left in a cage
193
+ // is no reason to put it there.
194
+ for (let group = 0; group < layout.groups.length; group += 1) {
195
+ if (layout.groups[group].length !== layout.size)
196
+ continue;
197
+ for (let value = 1; value <= layout.size; value += 1) {
198
+ const bit = 1 << value;
199
+ if ((taken[group] & bit) !== 0)
200
+ continue;
201
+ let at = -1;
202
+ let places = 0;
203
+ for (const index of layout.groups[group]) {
204
+ if (work[index] !== 0)
205
+ continue;
206
+ if ((candidatesAt(layout, index, taken, work) & bit) !== 0) {
207
+ at = index;
208
+ places += 1;
209
+ if (places > 1)
210
+ break;
211
+ }
212
+ }
213
+ if (places === 0)
214
+ return { grid: work, solved: false, contradiction: true };
215
+ if (places === 1) {
216
+ work[at] = value;
217
+ place(layout, taken, at, value);
218
+ changed = true;
219
+ }
220
+ }
221
+ }
222
+ }
223
+ return { grid: work, solved: work.every((value) => value !== 0), contradiction: false };
224
+ }
225
+ /**
226
+ * How many guesses, each followed by every single it lets loose, a solver
227
+ * needs to finish the grid: 0 when singles do it all, `Infinity` when the grid
228
+ * has no answer. Meant for a grid already known to have exactly one.
229
+ */
230
+ export function guessDepth(grid, layout) {
231
+ const singles = applySingles(grid, layout);
232
+ if (singles.contradiction)
233
+ return Infinity;
234
+ if (singles.solved)
235
+ return 0;
236
+ const work = singles.grid;
237
+ const { index, mask } = mostConstrained(work, layout, used(work, layout));
238
+ let deepest = Infinity;
239
+ for (let left = mask; left !== 0; left &= left - 1) {
240
+ const next = [...work];
241
+ next[index] = lowestBit(left);
242
+ const depth = guessDepth(next, layout);
243
+ if (depth < deepest)
244
+ deepest = depth;
245
+ }
246
+ return deepest === Infinity ? Infinity : deepest + 1;
247
+ }
248
+ /**
249
+ * A full grid for this layout, drawn at random, or null when the search runs
250
+ * past `budget` steps — which for a jigsaw means these regions are a poor
251
+ * layout to fill, and the caller draws others.
252
+ */
253
+ export function fillLayout(layout, random, budget = 200000) {
254
+ const work = new Array(layout.size * layout.size).fill(0);
255
+ const taken = used(work, layout);
256
+ let steps = 0;
257
+ const step = () => {
258
+ steps += 1;
259
+ if (steps > budget)
260
+ return false;
261
+ const { index, mask } = mostConstrained(work, layout, taken);
262
+ if (index === -1)
263
+ return true;
264
+ const values = [];
265
+ for (let left = mask; left !== 0; left &= left - 1)
266
+ values.push(lowestBit(left));
267
+ for (let i = values.length - 1; i > 0; i -= 1) {
268
+ const j = Math.floor(random() * (i + 1));
269
+ [values[i], values[j]] = [values[j], values[i]];
270
+ }
271
+ for (const value of values) {
272
+ work[index] = value;
273
+ place(layout, taken, index, value);
274
+ if (step())
275
+ return true;
276
+ lift(layout, taken, index, value);
277
+ work[index] = 0;
278
+ if (steps > budget)
279
+ return false;
280
+ }
281
+ return false;
282
+ };
283
+ return step() ? work : null;
284
+ }
package/dist/hint.d.ts ADDED
@@ -0,0 +1,39 @@
1
+ import type { KazuKind } from "./kinds.ts";
2
+ /**
3
+ * A HINT: the next cell a person could fill in by looking, and why.
4
+ *
5
+ * It reasons from what is right on the grid so far (the printed numbers and every entry that agrees
6
+ * with the answer; a wrong entry is treated as empty, so a hint never builds on a mistake) the way a
7
+ * person does, one step at a time:
8
+ *
9
+ * 1. a cell that only one number fits (`only-number`): every other number is already in its row,
10
+ * column, box, region, diagonal or cage, or a cage's sum, a Futoshiki mark or a Skyscrapers clue
11
+ * rules it out (`by` says which, when one did);
12
+ * 2. a number that fits only one cell of a group (`only-place`): the row, column, box, region,
13
+ * diagonal or cage it must go in, and where.
14
+ *
15
+ * When neither is left (a hard puzzle asks for a guess here) it says so (`answer`): the tightest
16
+ * cell, whose number is the answer's, with no reason a single step gives. Null when every cell is
17
+ * already right.
18
+ */
19
+ export type KazuHint = {
20
+ cell: number;
21
+ value: number;
22
+ why: "only-number" | "only-place" | "answer";
23
+ /** `only-place`: the group the number has one place in. */
24
+ group?: {
25
+ type: "row" | "column" | "box" | "region" | "diagonal";
26
+ index: number;
27
+ };
28
+ /** `only-number`: what ruled the others out beyond the groups, if anything did. */
29
+ by?: "cage" | "marks" | "clues";
30
+ /** Whether the cell holds a wrong number now, which this one replaces. */
31
+ replaces: boolean;
32
+ };
33
+ /**
34
+ * The next cell a person could fill in, and why (see `KazuHint`). `entries` is what the player has
35
+ * written, row-major (0 for empty; printed cells are ignored). `answer` is the puzzle's solution as a
36
+ * cells code; left out, it is worked out from the givens. Null when every cell is right, or when
37
+ * the givens are not a puzzle with exactly one answer: there is nothing true to say.
38
+ */
39
+ export declare function hintKazu(kind: KazuKind, size: number, givens: string, entries: readonly number[], answer?: string): KazuHint | null;