@johnmorrisdotca/kazu 1.2.0 → 1.3.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 (101) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +58 -16
  3. package/dist/akari-entry.d.ts +1 -0
  4. package/dist/akari-entry.js +1 -0
  5. package/dist/akari.constants.d.ts +5 -0
  6. package/dist/akari.constants.js +5 -0
  7. package/dist/akari.types.d.ts +19 -0
  8. package/dist/akariGenerate.d.ts +8 -5
  9. package/dist/akariGenerate.js +88 -65
  10. package/dist/akariLogic.d.ts +10 -0
  11. package/dist/akariLogic.js +84 -0
  12. package/dist/akariRate.d.ts +6 -0
  13. package/dist/akariRate.js +34 -0
  14. package/dist/akariSolve.d.ts +1 -1
  15. package/dist/akariSolve.js +8 -96
  16. package/dist/akariTemplate.d.ts +7 -0
  17. package/dist/akariTemplate.js +78 -0
  18. package/dist/csp.d.ts +63 -0
  19. package/dist/csp.js +162 -0
  20. package/dist/fillomino-entry.d.ts +1 -0
  21. package/dist/fillomino-entry.js +1 -0
  22. package/dist/fillomino.constants.d.ts +6 -2
  23. package/dist/fillomino.constants.js +6 -2
  24. package/dist/fillomino.types.d.ts +18 -1
  25. package/dist/fillominoBuild.d.ts +10 -0
  26. package/dist/fillominoBuild.js +78 -0
  27. package/dist/fillominoGenerate.d.ts +7 -1
  28. package/dist/fillominoGenerate.js +75 -107
  29. package/dist/fillominoLogic.d.ts +14 -0
  30. package/dist/fillominoLogic.js +177 -0
  31. package/dist/fillominoMount.js +4 -1
  32. package/dist/fillominoRate.d.ts +3 -0
  33. package/dist/fillominoRate.js +57 -0
  34. package/dist/fillominoSolve.js +15 -88
  35. package/dist/hitori-entry.d.ts +1 -0
  36. package/dist/hitori-entry.js +1 -0
  37. package/dist/hitori.constants.d.ts +7 -1
  38. package/dist/hitori.constants.js +7 -1
  39. package/dist/hitori.types.d.ts +18 -1
  40. package/dist/hitoriBoard.js +2 -1
  41. package/dist/hitoriBuild.d.ts +12 -0
  42. package/dist/hitoriBuild.js +117 -0
  43. package/dist/hitoriGenerate.d.ts +10 -3
  44. package/dist/hitoriGenerate.js +59 -157
  45. package/dist/hitoriLogic.d.ts +12 -0
  46. package/dist/hitoriLogic.js +189 -0
  47. package/dist/hitoriRate.d.ts +3 -0
  48. package/dist/hitoriRate.js +32 -0
  49. package/dist/hitoriSolve.d.ts +4 -1
  50. package/dist/hitoriSolve.js +11 -62
  51. package/dist/kakuro-entry.d.ts +1 -0
  52. package/dist/kakuro-entry.js +1 -0
  53. package/dist/kakuro.constants.d.ts +6 -1
  54. package/dist/kakuro.constants.js +6 -1
  55. package/dist/kakuro.types.d.ts +19 -0
  56. package/dist/kakuroBuild.d.ts +30 -0
  57. package/dist/kakuroBuild.js +337 -0
  58. package/dist/kakuroGenerate.d.ts +10 -3
  59. package/dist/kakuroGenerate.js +54 -122
  60. package/dist/kakuroLogic.d.ts +13 -0
  61. package/dist/kakuroLogic.js +153 -0
  62. package/dist/kakuroRate.d.ts +3 -0
  63. package/dist/kakuroRate.js +40 -0
  64. package/dist/kakuroSolve.js +17 -69
  65. package/dist/kakuroTemplate.d.ts +3 -0
  66. package/dist/kakuroTemplate.js +130 -0
  67. package/dist/shikaku-entry.d.ts +1 -0
  68. package/dist/shikaku-entry.js +1 -0
  69. package/dist/shikaku.constants.d.ts +5 -2
  70. package/dist/shikaku.constants.js +5 -2
  71. package/dist/shikaku.types.d.ts +18 -1
  72. package/dist/shikakuBuild.d.ts +21 -0
  73. package/dist/shikakuBuild.js +135 -0
  74. package/dist/shikakuGenerate.d.ts +6 -1
  75. package/dist/shikakuGenerate.js +50 -35
  76. package/dist/shikakuLogic.d.ts +13 -0
  77. package/dist/shikakuLogic.js +84 -0
  78. package/dist/shikakuRate.d.ts +3 -0
  79. package/dist/shikakuRate.js +27 -0
  80. package/dist/shikakuSolve.d.ts +1 -1
  81. package/dist/shikakuSolve.js +23 -44
  82. package/dist/shikakuTemplate.d.ts +3 -0
  83. package/dist/shikakuTemplate.js +43 -0
  84. package/dist/slitherlink-entry.d.ts +1 -0
  85. package/dist/slitherlink-entry.js +1 -0
  86. package/dist/slitherlink.constants.d.ts +5 -0
  87. package/dist/slitherlink.constants.js +5 -0
  88. package/dist/slitherlink.types.d.ts +18 -0
  89. package/dist/slitherlinkGenerate.d.ts +10 -3
  90. package/dist/slitherlinkGenerate.js +161 -80
  91. package/dist/slitherlinkLogic.d.ts +10 -0
  92. package/dist/slitherlinkLogic.js +267 -0
  93. package/dist/slitherlinkRate.d.ts +3 -0
  94. package/dist/slitherlinkRate.js +26 -0
  95. package/dist/slitherlinkSolve.d.ts +1 -1
  96. package/dist/slitherlinkSolve.js +9 -117
  97. package/dist/slitherlinkTemplate.d.ts +3 -0
  98. package/dist/slitherlinkTemplate.js +101 -0
  99. package/dist/version.d.ts +1 -1
  100. package/dist/version.js +1 -1
  101. package/package.json +1 -1
@@ -0,0 +1,189 @@
1
+ import { hitoriNeighbors } from "./hitoriBoard.js";
2
+ /**
3
+ * Hitori as variables: one per square, slot 0 for left white and slot 1 for shaded.
4
+ * `strength` is how much of the rules the pruning uses: 0 has the duplicates, the ban on touching shades, the
5
+ * pair and the sandwich; 1 adds that every white square must stay in reach of the others; 2 adds that a square
6
+ * whose shading would cut the whites in two must stay white. A finished answer must be one piece, which the
7
+ * search checks whatever the strength.
8
+ */
9
+ export function hitoriModel(board, strength = 2) {
10
+ const size = board.size, total = size * size;
11
+ const neighbours = Array.from({ length: total }, (_, cell) => Int32Array.from(hitoriNeighbors(size, cell)));
12
+ const partners = Array.from({ length: total }, (_, cell) => {
13
+ const x = cell % size, y = Math.floor(cell / size), found = [];
14
+ for (let k = 0; k < size; k += 1) {
15
+ if (k !== x && board.numbers[y * size + k] === board.numbers[cell])
16
+ found.push(y * size + k);
17
+ if (k !== y && board.numbers[k * size + x] === board.numbers[cell])
18
+ found.push(k * size + x);
19
+ }
20
+ return Int32Array.from(found);
21
+ });
22
+ // A pair of equal numbers side by side shades every other equal number of that line; a number on both sides of a square keeps it white.
23
+ const pairs = [];
24
+ const sandwiches = [];
25
+ for (let y = 0; y < size; y += 1)
26
+ for (let x = 0; x < size; x += 1) {
27
+ const cell = y * size + x;
28
+ for (const [dx, dy] of [[1, 0], [0, 1]]) {
29
+ const nx = x + dx, ny = y + dy;
30
+ if (nx >= size || ny >= size)
31
+ continue;
32
+ const next = ny * size + nx;
33
+ if (board.numbers[cell] === board.numbers[next]) {
34
+ const others = partners[cell].filter(other => other !== next && (dx ? Math.floor(other / size) === y : other % size === x));
35
+ if (others.length)
36
+ pairs.push({ a: cell, b: next, others });
37
+ }
38
+ const fx = x + 2 * dx, fy = y + 2 * dy;
39
+ if (fx < size && fy < size && board.numbers[cell] === board.numbers[fy * size + fx])
40
+ sandwiches.push(next);
41
+ }
42
+ }
43
+ const starts = Int32Array.from({ length: total + 1 }, (_, i) => i * 2);
44
+ const disc = new Int32Array(total), low = new Int32Array(total), whitesBelow = new Int32Array(total);
45
+ const propagate = (alive) => {
46
+ let changed = true;
47
+ while (changed) {
48
+ changed = false;
49
+ for (let c = 0; c < total; c += 1) {
50
+ const white = alive[2 * c], shaded = alive[2 * c + 1];
51
+ if (!white && !shaded)
52
+ return false;
53
+ if (shaded && !white) {
54
+ const around = neighbours[c];
55
+ for (let k = 0; k < around.length; k += 1) {
56
+ const n = around[k];
57
+ if (alive[2 * n + 1]) {
58
+ alive[2 * n + 1] = 0;
59
+ changed = true;
60
+ if (!alive[2 * n])
61
+ return false;
62
+ }
63
+ }
64
+ }
65
+ if (white && !shaded) {
66
+ const same = partners[c];
67
+ for (let k = 0; k < same.length; k += 1) {
68
+ const p = same[k];
69
+ if (alive[2 * p]) {
70
+ alive[2 * p] = 0;
71
+ changed = true;
72
+ if (!alive[2 * p + 1])
73
+ return false;
74
+ }
75
+ }
76
+ }
77
+ if (shaded) {
78
+ // A square is shaded only to settle a duplicate, so some equal number in its row or column must stay white.
79
+ const same = partners[c];
80
+ let free = 0, last = -1;
81
+ for (let k = 0; k < same.length; k += 1)
82
+ if (alive[2 * same[k]]) {
83
+ free += 1;
84
+ last = same[k];
85
+ }
86
+ if (free === 0) {
87
+ if (!white)
88
+ return false;
89
+ alive[2 * c + 1] = 0;
90
+ changed = true;
91
+ }
92
+ else if (free === 1 && !white && alive[2 * last + 1]) {
93
+ alive[2 * last + 1] = 0;
94
+ changed = true;
95
+ }
96
+ }
97
+ }
98
+ for (const { a, b, others } of pairs) {
99
+ if (!alive[2 * a] && !alive[2 * b])
100
+ continue;
101
+ for (let k = 0; k < others.length; k += 1) {
102
+ const o = others[k];
103
+ if (alive[2 * o]) {
104
+ alive[2 * o] = 0;
105
+ changed = true;
106
+ if (!alive[2 * o + 1])
107
+ return false;
108
+ }
109
+ }
110
+ }
111
+ for (let k = 0; k < sandwiches.length; k += 1) {
112
+ const m = sandwiches[k];
113
+ if (alive[2 * m + 1]) {
114
+ alive[2 * m + 1] = 0;
115
+ changed = true;
116
+ if (!alive[2 * m])
117
+ return false;
118
+ }
119
+ }
120
+ if (strength > 0) {
121
+ const verdict = reach(alive);
122
+ if (verdict < 0)
123
+ return false;
124
+ if (verdict > 0)
125
+ changed = true;
126
+ }
127
+ }
128
+ return true;
129
+ };
130
+ const cutList = new Int32Array(total);
131
+ let clock = 0, cutCount = 0, whiteTotal = 0, current = new Uint8Array(0), power = 0;
132
+ const visit = (u, parent) => {
133
+ disc[u] = low[u] = ++clock;
134
+ let below = current[2 * u + 1] ? 0 : 1, cutWhites = 0, cutParts = 0;
135
+ const around = neighbours[u];
136
+ for (let k = 0; k < around.length; k += 1) {
137
+ const n = around[k];
138
+ if (!current[2 * n] || n === parent)
139
+ continue;
140
+ if (disc[n]) {
141
+ if (disc[n] < low[u])
142
+ low[u] = disc[n];
143
+ continue;
144
+ }
145
+ visit(n, u);
146
+ if (low[n] < low[u])
147
+ low[u] = low[n];
148
+ below += whitesBelow[n];
149
+ if (low[n] >= disc[u] && whitesBelow[n] > 0) {
150
+ cutParts += 1;
151
+ cutWhites += whitesBelow[n];
152
+ }
153
+ }
154
+ whitesBelow[u] = below;
155
+ if (power > 1 && current[2 * u] && current[2 * u + 1] && cutParts + (whiteTotal - cutWhites > 0 ? 1 : 0) >= 2)
156
+ cutList[cutCount++] = u;
157
+ };
158
+ /** -1 when the white squares cannot be one piece, 1 when a square was forced white, 0 when nothing moved. */
159
+ const reach = (alive) => {
160
+ let root = -1;
161
+ whiteTotal = 0;
162
+ for (let c = 0; c < total; c += 1)
163
+ if (!alive[2 * c + 1]) {
164
+ whiteTotal += 1;
165
+ if (root < 0)
166
+ root = c;
167
+ }
168
+ if (root < 0)
169
+ return 0;
170
+ if (!alive[2 * root])
171
+ return -1;
172
+ disc.fill(0);
173
+ clock = 0;
174
+ cutCount = 0;
175
+ current = alive;
176
+ power = strength;
177
+ visit(root, -1);
178
+ let seenWhites = 0;
179
+ for (let c = 0; c < total; c += 1)
180
+ if (!alive[2 * c + 1] && disc[c])
181
+ seenWhites += 1;
182
+ if (seenWhites !== whiteTotal)
183
+ return -1;
184
+ for (let k = 0; k < cutCount; k += 1)
185
+ alive[2 * cutList[k] + 1] = 0;
186
+ return cutCount ? 1 : 0;
187
+ };
188
+ return { csp: { starts, propagate } };
189
+ }
@@ -0,0 +1,3 @@
1
+ import type { HitoriBoard, HitoriRating } from "./hitori.types.ts";
2
+ /** Rates a board with one answer by solving it: with the plain rules, with the whites-stay-together rule, then by supposing. */
3
+ export declare function rateHitori(board: HitoriBoard): HitoriRating;
@@ -0,0 +1,32 @@
1
+ import { checkHitori, isHitoriBoard } from "./hitoriBoard.js";
2
+ import { hitoriModel } from "./hitoriLogic.js";
3
+ import { logicCsp, openSlots, solveCsp } from "./csp.js";
4
+ /** Rates a board with one answer by solving it: with the plain rules, with the whites-stay-together rule, then by supposing. */
5
+ export function rateHitori(board) {
6
+ if (!isHitoriBoard(board))
7
+ throw new RangeError("Invalid Hitori board");
8
+ const strong = hitoriModel(board, 2).csp;
9
+ const proof = solveCsp(strong, openSlots(strong), 2, 300000);
10
+ if (proof.count !== 1 || proof.exhausted)
11
+ throw new RangeError("Hitori rating needs a board with one answer");
12
+ const shaded = Array.from({ length: board.size ** 2 }, (_, cell) => proof.solution[2 * cell + 1] === 1);
13
+ const solvedBy = (strength, depth) => {
14
+ const { csp } = hitoriModel(board, strength);
15
+ const run = logicCsp(csp, openSlots(csp), depth);
16
+ return { ...run, solved: run.solved && checkHitori(board, Array.from({ length: board.size ** 2 }, (_, cell) => run.alive[2 * cell + 1] === 1)).ok };
17
+ };
18
+ const plain = solvedBy(0, 0), reach = plain.solved ? plain : solvedBy(2, 0);
19
+ const probing = reach.solved ? reach : solvedBy(2, 1);
20
+ const repeats = board.numbers.filter((_, cell) => {
21
+ const x = cell % board.size, y = Math.floor(cell / board.size);
22
+ return board.numbers.some((other, at) => at !== cell && other === board.numbers[cell] && (at % board.size === x || Math.floor(at / board.size) === y));
23
+ }).length;
24
+ return {
25
+ depth: reach.solved ? 0 : probing.solved ? 1 : 2,
26
+ reach: !plain.solved && reach.solved,
27
+ probes: reach.solved ? 0 : probing.probes,
28
+ shaded: shaded.filter(Boolean).length,
29
+ shadedShare: shaded.filter(Boolean).length / shaded.length,
30
+ repeatShare: repeats / shaded.length,
31
+ };
32
+ }
@@ -1,5 +1,8 @@
1
1
  import type { HitoriBoard, HitoriSolve } from "./hitori.types.ts";
2
- /** Counts distinct minimal shade patterns. A non-complete result never asserts uniqueness. */
2
+ /**
3
+ * Counts distinct minimal shade patterns: an answer shades only squares that settle a duplicate, so extra shades
4
+ * are never a second answer. A non-complete result never asserts uniqueness.
5
+ */
3
6
  export declare function solveHitori(board: HitoriBoard, options?: {
4
7
  limit?: number;
5
8
  nodes?: number;
@@ -1,69 +1,18 @@
1
1
  import { HITORI_MAX_NODES } from "./hitori.constants.js";
2
- import { checkHitori, hitoriNeighbors, isHitoriBoard } from "./hitoriBoard.js";
3
- function firstUnshadedDuplicate(board, shaded) {
4
- const size = board.size;
5
- for (let y = 0; y < size; y += 1)
6
- for (let x = 0; x < size; x += 1) {
7
- const cell = y * size + x;
8
- if (shaded[cell])
9
- continue;
10
- for (let otherX = x + 1; otherX < size; otherX += 1) {
11
- const other = y * size + otherX;
12
- if (!shaded[other] && board.numbers[cell] === board.numbers[other])
13
- return [cell, other];
14
- }
15
- for (let otherY = y + 1; otherY < size; otherY += 1) {
16
- const other = otherY * size + x;
17
- if (!shaded[other] && board.numbers[cell] === board.numbers[other])
18
- return [cell, other];
19
- }
20
- }
21
- return null;
22
- }
23
- /** A valid answer cannot contain an unnecessary shade; this avoids treating extra black cells as separate puzzle solutions. */
24
- function isMinimalSolution(board, shaded) {
25
- if (!checkHitori(board, shaded).ok)
26
- return false;
27
- for (let cell = 0; cell < shaded.length; cell += 1)
28
- if (shaded[cell]) {
29
- const smaller = [...shaded];
30
- smaller[cell] = false;
31
- if (checkHitori(board, smaller).ok)
32
- return false;
33
- }
34
- return true;
35
- }
36
- /** Counts distinct minimal shade patterns. A non-complete result never asserts uniqueness. */
2
+ import { isHitoriBoard } from "./hitoriBoard.js";
3
+ import { hitoriModel } from "./hitoriLogic.js";
4
+ import { openSlots, solveCsp } from "./csp.js";
5
+ /**
6
+ * Counts distinct minimal shade patterns: an answer shades only squares that settle a duplicate, so extra shades
7
+ * are never a second answer. A non-complete result never asserts uniqueness.
8
+ */
37
9
  export function solveHitori(board, options = {}) {
38
10
  if (!isHitoriBoard(board))
39
11
  throw new RangeError("Invalid Hitori board");
40
12
  const limit = Math.max(1, Math.floor(options.limit ?? 2));
41
13
  const budget = Math.max(1, Math.floor(options.nodes ?? HITORI_MAX_NODES));
42
- let nodes = 0, count = 0, solution = null, exhausted = false;
43
- const search = (shaded) => {
44
- if (count >= limit || exhausted)
45
- return;
46
- nodes += 1;
47
- if (nodes > budget) {
48
- exhausted = true;
49
- return;
50
- }
51
- const duplicate = firstUnshadedDuplicate(board, shaded);
52
- if (!duplicate) {
53
- if (isMinimalSolution(board, shaded)) {
54
- count += 1;
55
- solution ?? (solution = [...shaded]);
56
- }
57
- return;
58
- }
59
- for (const cell of duplicate) {
60
- if (hitoriNeighbors(board.size, cell).some(neighbor => shaded[neighbor]))
61
- continue;
62
- shaded[cell] = true;
63
- search(shaded);
64
- shaded[cell] = false;
65
- }
66
- };
67
- search(Array(board.size * board.size).fill(false));
68
- return { count, solution, complete: !exhausted && count < limit, nodes: Math.min(nodes, budget) };
14
+ const { csp } = hitoriModel(board);
15
+ const found = solveCsp(csp, openSlots(csp), limit, budget);
16
+ const solution = found.solution ? Array.from({ length: board.size ** 2 }, (_, cell) => found.solution[2 * cell + 1] === 1) : null;
17
+ return { count: found.count, solution, complete: !found.exhausted && !found.stopped, nodes: found.nodes };
69
18
  }
@@ -3,6 +3,7 @@ export * from "./kakuro.constants.ts";
3
3
  export * from "./kakuroBoard.ts";
4
4
  export * from "./kakuroSolve.ts";
5
5
  export * from "./kakuroGenerate.ts";
6
+ export * from "./kakuroRate.ts";
6
7
  export * from "./kakuroGame.ts";
7
8
  export { drawKakuro } from "./kakuroDraw.ts";
8
9
  export type { KakuroDrawOptions } from "./kakuroDraw.ts";
@@ -3,6 +3,7 @@ export * from "./kakuro.constants.js";
3
3
  export * from "./kakuroBoard.js";
4
4
  export * from "./kakuroSolve.js";
5
5
  export * from "./kakuroGenerate.js";
6
+ export * from "./kakuroRate.js";
6
7
  export * from "./kakuroGame.js";
7
8
  export { drawKakuro } from "./kakuroDraw.js";
8
9
  export { mountKakuro } from "./kakuroMount.js";
@@ -1,6 +1,11 @@
1
1
  export declare const KAKURO_MIN_SIDE = 3;
2
- export declare const KAKURO_MAX_SIDE = 10;
2
+ export declare const KAKURO_MAX_SIDE = 12;
3
3
  export declare const KAKURO_MAX_NODES = 250000;
4
4
  export declare const KAKURO_MAX_ATTEMPTS = 120;
5
5
  export declare const KAKURO_GENERATION_ATTEMPTS = 1200;
6
6
  export declare const KAKURO_GENERATION_NODES = 1000000;
7
+ /** How hard a puzzle is made, by what a person must do to solve it; see `rateKakuro`. */
8
+ export declare const KAKURO_LEVELS: readonly ["easy", "medium", "hard", "extra-hard"];
9
+ /** The sides on offer, counting the row and column of totals; any side from 5 to `KAKURO_MAX_SIDE` can be made. */
10
+ export declare const KAKURO_SIZES: readonly [6, 8, 10, 12];
11
+ export declare const KAKURO_LEAST_GENERATED_SIDE = 5;
@@ -1,6 +1,11 @@
1
1
  export const KAKURO_MIN_SIDE = 3;
2
- export const KAKURO_MAX_SIDE = 10;
2
+ export const KAKURO_MAX_SIDE = 12;
3
3
  export const KAKURO_MAX_NODES = 250000;
4
4
  export const KAKURO_MAX_ATTEMPTS = 120;
5
5
  export const KAKURO_GENERATION_ATTEMPTS = 1200;
6
6
  export const KAKURO_GENERATION_NODES = 1000000;
7
+ /** How hard a puzzle is made, by what a person must do to solve it; see `rateKakuro`. */
8
+ export const KAKURO_LEVELS = ["easy", "medium", "hard", "extra-hard"];
9
+ /** The sides on offer, counting the row and column of totals; any side from 5 to `KAKURO_MAX_SIDE` can be made. */
10
+ export const KAKURO_SIZES = [6, 8, 10, 12];
11
+ export const KAKURO_LEAST_GENERATED_SIDE = 5;
@@ -16,10 +16,29 @@ export type KakuroRun = {
16
16
  cells: readonly number[];
17
17
  sum: number;
18
18
  };
19
+ export type KakuroLevel = "easy" | "medium" | "hard" | "extra-hard";
19
20
  export type KakuroPuzzle = KakuroBoard & {
20
21
  seed: number;
22
+ level: KakuroLevel;
21
23
  solution: readonly number[];
22
24
  };
25
+ /**
26
+ * How hard a board is, measured by solving it. `depth` 0 means the rules solve it, 1 that somebody has to suppose a
27
+ * digit and watch it break, 2 that more than that is needed; `plain` says the single-run rules were enough (no
28
+ * digit that must appear had to be traced). `probes` is how many suppositions depth 1 needed. The rest describes the runs.
29
+ */
30
+ export type KakuroRating = {
31
+ depth: 0 | 1 | 2;
32
+ plain: boolean;
33
+ probes: number;
34
+ whites: number;
35
+ runs: number;
36
+ longest: number;
37
+ /** Mean number of squares in a run. */
38
+ meanRun: number;
39
+ /** Runs whose total can be made in only one way, as a share of the runs. */
40
+ fixedShare: number;
41
+ };
23
42
  export type KakuroCheck = {
24
43
  ok: boolean;
25
44
  complete: boolean;
@@ -0,0 +1,30 @@
1
+ import type { KakuroBoard, KakuroLevel } from "./kakuro.types.ts";
2
+ import type { Random } from "./random.ts";
3
+ /** How the black squares are scattered, and the longest run a level allows. */
4
+ export declare const SHAPE: Record<KakuroLevel, {
5
+ black: number;
6
+ longest: number;
7
+ }>;
8
+ /** Which squares are white: no run shorter than two, none longer than the level allows, and all of them one connected piece. */
9
+ export declare function kakuroPattern(side: number, level: KakuroLevel, random: Random): boolean[] | null;
10
+ /** How many different sets of digits can make `sum` out of `length` squares. */
11
+ export declare function kakuroWays(length: number, sum: number): number;
12
+ /** A random digit for every white square with no digit repeated in a run, or null when the search gives up. */
13
+ export declare function kakuroDigits(side: number, white: readonly boolean[], random: Random): number[] | null;
14
+ /**
15
+ * Changes digits one square at a time, keeping every run free of repeats, to make runs whose totals can be made in
16
+ * few ways: a run that says a lot about its digits lets the solver start from the rules alone. A change is kept when
17
+ * it does not raise the count of ways, summed over runs, which is `weight` raised to the number of ways.
18
+ */
19
+ export declare function kakuroTighten(side: number, white: readonly boolean[], digits: number[], random: Random, rounds: number): void;
20
+ /** The totals of a pattern and its digits, as a board. */
21
+ export declare function kakuroBoardOf(side: number, white: readonly boolean[], digits: readonly number[]): KakuroBoard;
22
+ /**
23
+ * A board with exactly one answer, or null. While the board has other answers, each one is broken: a square the
24
+ * two answers disagree on is turned black (when every run stays two long and the squares stay one piece) or given
25
+ * another digit that still repeats nowhere in its runs, which changes two totals.
26
+ */
27
+ export declare function candidateKakuro(side: number, level: KakuroLevel, random: Random): {
28
+ board: KakuroBoard;
29
+ digits: readonly number[];
30
+ } | null;