@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.
- package/CHANGELOG.md +29 -0
- package/LICENSE +21 -0
- package/README.md +568 -0
- package/dist/cells.d.ts +28 -0
- package/dist/cells.js +65 -0
- package/dist/check.d.ts +15 -0
- package/dist/check.js +189 -0
- package/dist/clock.d.ts +2 -0
- package/dist/clock.js +9 -0
- package/dist/conflicts.d.ts +14 -0
- package/dist/conflicts.js +79 -0
- package/dist/draw-entry.d.ts +14 -0
- package/dist/draw-entry.js +10 -0
- package/dist/draw.d.ts +47 -0
- package/dist/draw.js +219 -0
- package/dist/element-define.d.ts +1 -0
- package/dist/element-define.js +13 -0
- package/dist/element.d.ts +48 -0
- package/dist/element.js +150 -0
- package/dist/game.d.ts +65 -0
- package/dist/game.js +120 -0
- package/dist/generate.d.ts +11 -0
- package/dist/generate.js +35 -0
- package/dist/geometry.d.ts +33 -0
- package/dist/geometry.js +27 -0
- package/dist/givens.d.ts +30 -0
- package/dist/givens.js +43 -0
- package/dist/groupSolve.d.ts +68 -0
- package/dist/groupSolve.js +284 -0
- package/dist/hint.d.ts +39 -0
- package/dist/hint.js +158 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +31 -0
- package/dist/jigsaw.d.ts +21 -0
- package/dist/jigsaw.js +144 -0
- package/dist/kinds.d.ts +51 -0
- package/dist/kinds.js +40 -0
- package/dist/layout.d.ts +63 -0
- package/dist/layout.js +128 -0
- package/dist/moreOrLess.d.ts +7 -0
- package/dist/moreOrLess.js +90 -0
- package/dist/moreOrLessCode.d.ts +22 -0
- package/dist/moreOrLessCode.js +52 -0
- package/dist/moreOrLessSolve.d.ts +45 -0
- package/dist/moreOrLessSolve.js +180 -0
- package/dist/mount.d.ts +117 -0
- package/dist/mount.js +558 -0
- package/dist/names.d.ts +41 -0
- package/dist/names.js +163 -0
- package/dist/numberPlace.d.ts +14 -0
- package/dist/numberPlace.js +123 -0
- package/dist/play-entry.d.ts +9 -0
- package/dist/play-entry.js +8 -0
- package/dist/playStyle.d.ts +11 -0
- package/dist/playStyle.js +47 -0
- package/dist/progress.d.ts +29 -0
- package/dist/progress.js +96 -0
- package/dist/random.d.ts +25 -0
- package/dist/random.js +42 -0
- package/dist/solve.d.ts +22 -0
- package/dist/solve.js +60 -0
- package/dist/strings.d.ts +17 -0
- package/dist/strings.js +143 -0
- package/dist/style.d.ts +13 -0
- package/dist/style.js +61 -0
- package/dist/sumCages.d.ts +60 -0
- package/dist/sumCages.js +190 -0
- package/dist/towers.d.ts +3 -0
- package/dist/towers.js +48 -0
- package/dist/towersCode.d.ts +31 -0
- package/dist/towersCode.js +79 -0
- package/dist/towersSolve.d.ts +65 -0
- package/dist/towersSolve.js +276 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +2 -0
- package/package.json +104 -0
package/dist/draw.js
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { symbolOf } from "./cells.js";
|
|
2
|
+
import { kazuGeometry, KAZU_CELL } from "./geometry.js";
|
|
3
|
+
import { readGivens } from "./givens.js";
|
|
4
|
+
import { notesOf } from "./progress.js";
|
|
5
|
+
import { kazuNameOf, kazuSay } from "./strings.js";
|
|
6
|
+
import { KAZU_STYLE } from "./style.js";
|
|
7
|
+
import { cageOutline } from "./sumCages.js";
|
|
8
|
+
import { lineFrom, TOWER_SIDES } from "./towersCode.js";
|
|
9
|
+
const escape = (text) => text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
10
|
+
const num = (value) => String(Math.round(value * 100) / 100);
|
|
11
|
+
const WHERE_KEY = {
|
|
12
|
+
"number-place": "whereNumberPlace",
|
|
13
|
+
diagonal: "whereDiagonal",
|
|
14
|
+
jigsaw: "whereJigsaw",
|
|
15
|
+
"sum-cages": "whereSumCages",
|
|
16
|
+
"more-or-less": "whereMoreOrLess",
|
|
17
|
+
towers: "whereTowers",
|
|
18
|
+
};
|
|
19
|
+
/** The words for the groups a number is ruled out by, for a hint's line: "its row, column and box". */
|
|
20
|
+
export function kazuWhere(kind, language) {
|
|
21
|
+
return kazuSay(language, WHERE_KEY[kind]);
|
|
22
|
+
}
|
|
23
|
+
/** A cell as a screen reader names it: "row 3, column 5". */
|
|
24
|
+
export function kazuCellName(size, index, language) {
|
|
25
|
+
return kazuSay(language, "cell", { row: Math.floor(index / size) + 1, col: (index % size) + 1 });
|
|
26
|
+
}
|
|
27
|
+
/** The thick lines: where two neighbouring cells are in different regions, and the outer edge. */
|
|
28
|
+
function boxPath(givens, x0, y0) {
|
|
29
|
+
const { size, regions } = givens;
|
|
30
|
+
const U = KAZU_CELL;
|
|
31
|
+
const parts = [`M${x0} ${y0}h${size * U}v${size * U}h${-size * U}z`];
|
|
32
|
+
if (regions !== null) {
|
|
33
|
+
for (let row = 0; row < size; row += 1) {
|
|
34
|
+
for (let col = 0; col < size; col += 1) {
|
|
35
|
+
const here = regions[row * size + col];
|
|
36
|
+
if (col < size - 1 && regions[row * size + col + 1] !== here)
|
|
37
|
+
parts.push(`M${x0 + (col + 1) * U} ${y0 + row * U}v${U}`);
|
|
38
|
+
if (row < size - 1 && regions[(row + 1) * size + col] !== here)
|
|
39
|
+
parts.push(`M${x0 + col * U} ${y0 + (row + 1) * U}h${U}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return parts.join("");
|
|
44
|
+
}
|
|
45
|
+
/** A mark between two cells: a chevron on the shared edge whose point faces the smaller number. */
|
|
46
|
+
function markShape(size, less, more, x0, y0) {
|
|
47
|
+
const U = KAZU_CELL;
|
|
48
|
+
const low = Math.min(less, more);
|
|
49
|
+
const horizontal = Math.max(less, more) === low + 1;
|
|
50
|
+
const d = 0.1 * U;
|
|
51
|
+
const lowRow = Math.floor(low / size);
|
|
52
|
+
const lowCol = low % size;
|
|
53
|
+
if (horizontal) {
|
|
54
|
+
const x = x0 + (lowCol + 1) * U;
|
|
55
|
+
const y = y0 + lowRow * U + U / 2;
|
|
56
|
+
// The point faces the smaller cell: left when the left cell is the smaller.
|
|
57
|
+
const toward = less === low ? -1 : 1;
|
|
58
|
+
return `<circle class="kz-mark-bg" cx="${num(x)}" cy="${num(y)}" r="${num(0.19 * U)}"/><path class="kz-mark" d="M${num(x - toward * d)} ${num(y - d * 1.2)}L${num(x + toward * d)} ${num(y)}L${num(x - toward * d)} ${num(y + d * 1.2)}"/>`;
|
|
59
|
+
}
|
|
60
|
+
const x = x0 + lowCol * U + U / 2;
|
|
61
|
+
const y = y0 + (lowRow + 1) * U;
|
|
62
|
+
const toward = less === low ? -1 : 1;
|
|
63
|
+
return `<circle class="kz-mark-bg" cx="${num(x)}" cy="${num(y)}" r="${num(0.19 * U)}"/><path class="kz-mark" d="M${num(x - d * 1.2)} ${num(y - toward * d)}L${num(x)} ${num(y + toward * d)}L${num(x + d * 1.2)} ${num(y - toward * d)}"/>`;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* A PUZZLE AS SVG TEXT: the grid with its printed numbers, whatever has been written in it, pencil
|
|
67
|
+
* marks, and everything the kind of puzzle prints: the heavier rules round the boxes or a Jigsaw's
|
|
68
|
+
* regions, the shaded diagonals, a cage's dashed outline with its sum, the more-than marks between
|
|
69
|
+
* cells, and the tower clues round the edge. The chosen cell, the cells that break a rule, the cells
|
|
70
|
+
* Check flagged and the cell a hint pointed at are washed in colour.
|
|
71
|
+
*
|
|
72
|
+
* Returns the `<svg>` as a string: put it in a page, a file or an image, with nothing to load. It is
|
|
73
|
+
* one steady square whatever is drawn, so nothing moves as numbers are written. Nothing in it can be
|
|
74
|
+
* selected or dragged. Null for givens that are not a puzzle of that kind and side.
|
|
75
|
+
*/
|
|
76
|
+
export function drawKazu(kind, size, givensCode, options = {}) {
|
|
77
|
+
const givens = readGivens(kind, size, givensCode);
|
|
78
|
+
if (givens === null)
|
|
79
|
+
return null;
|
|
80
|
+
const language = options.language ?? "en";
|
|
81
|
+
const U = KAZU_CELL;
|
|
82
|
+
const geo = kazuGeometry(kind, size);
|
|
83
|
+
const { origin: x0, side } = geo;
|
|
84
|
+
const y0 = x0;
|
|
85
|
+
const entries = options.entries ?? [];
|
|
86
|
+
const notes = options.notes ?? [];
|
|
87
|
+
const value = (index) => (givens.cells[index] !== 0 ? givens.cells[index] : (entries[index] ?? 0));
|
|
88
|
+
const selected = options.selected ?? null;
|
|
89
|
+
const conflicts = new Set(options.conflicts ?? []);
|
|
90
|
+
const wrong = new Set(options.wrong ?? []);
|
|
91
|
+
const area = size * size;
|
|
92
|
+
const at = (index) => geo.corner(index);
|
|
93
|
+
// Washes: the chosen cell's lines and its number, then the colours that say something is wrong.
|
|
94
|
+
const peers = new Set();
|
|
95
|
+
const same = new Set();
|
|
96
|
+
if (options.peers === true && selected !== null && selected >= 0 && selected < area) {
|
|
97
|
+
const row = Math.floor(selected / size);
|
|
98
|
+
const col = selected % size;
|
|
99
|
+
for (let index = 0; index < area; index += 1) {
|
|
100
|
+
const sharesRegion = givens.regions !== null && givens.regions[index] === givens.regions[selected];
|
|
101
|
+
if (Math.floor(index / size) === row || index % size === col || sharesRegion)
|
|
102
|
+
peers.add(index);
|
|
103
|
+
}
|
|
104
|
+
const chosen = value(selected);
|
|
105
|
+
if (chosen !== 0)
|
|
106
|
+
for (let index = 0; index < area; index += 1)
|
|
107
|
+
if (value(index) === chosen)
|
|
108
|
+
same.add(index);
|
|
109
|
+
}
|
|
110
|
+
const wash = [];
|
|
111
|
+
const square = (index, cls) => {
|
|
112
|
+
const { x, y } = at(index);
|
|
113
|
+
return `<rect class="${cls}" x="${x}" y="${y}" width="${U}" height="${U}"/>`;
|
|
114
|
+
};
|
|
115
|
+
for (let index = 0; index < area; index += 1) {
|
|
116
|
+
const row = Math.floor(index / size);
|
|
117
|
+
const col = index % size;
|
|
118
|
+
if (givens.diagonals && (row === col || row + col === size - 1))
|
|
119
|
+
wash.push(square(index, "kz-diagonal"));
|
|
120
|
+
if (peers.has(index))
|
|
121
|
+
wash.push(square(index, "kz-peer"));
|
|
122
|
+
if (same.has(index))
|
|
123
|
+
wash.push(square(index, "kz-same"));
|
|
124
|
+
if (index === selected)
|
|
125
|
+
wash.push(square(index, "kz-select"));
|
|
126
|
+
if (options.hint === index)
|
|
127
|
+
wash.push(square(index, "kz-hint"));
|
|
128
|
+
if (wrong.has(index))
|
|
129
|
+
wash.push(square(index, "kz-wrong"));
|
|
130
|
+
if (conflicts.has(index))
|
|
131
|
+
wash.push(square(index, "kz-conflict"));
|
|
132
|
+
}
|
|
133
|
+
const grid = [];
|
|
134
|
+
for (let k = 1; k < size; k += 1) {
|
|
135
|
+
grid.push(`M${x0 + k * U} ${y0}v${size * U}`, `M${x0} ${y0 + k * U}h${size * U}`);
|
|
136
|
+
}
|
|
137
|
+
// Cages: a dashed outline inside each, and the sum in the first cell's corner.
|
|
138
|
+
const cageOf = new Map();
|
|
139
|
+
const sumAt = new Map();
|
|
140
|
+
(givens.cages ?? []).forEach((cage, c) => {
|
|
141
|
+
cage.cells.forEach((index) => cageOf.set(index, c));
|
|
142
|
+
sumAt.set(Math.min(...cage.cells), cage.sum);
|
|
143
|
+
});
|
|
144
|
+
const cageLines = givens.cages === null ? [] : cageOutline(size, (index) => cageOf.get(index)).map((line) => `<line x1="${num(x0 + line.x1 * U)}" y1="${num(y0 + line.y1 * U)}" x2="${num(x0 + line.x2 * U)}" y2="${num(y0 + line.y2 * U)}"/>`);
|
|
145
|
+
// Numbers and pencil marks.
|
|
146
|
+
const digitSize = size > 9 ? 0.5 * U : 0.6 * U;
|
|
147
|
+
const columns = Math.ceil(Math.sqrt(size));
|
|
148
|
+
const noteTop = givens.cages === null ? 0.07 * U : 0.27 * U;
|
|
149
|
+
const noteSide = 0.07 * U;
|
|
150
|
+
const noteWidth = U - 2 * noteSide;
|
|
151
|
+
const noteHeight = U - noteTop - 0.05 * U;
|
|
152
|
+
const noteRows = Math.ceil(size / columns);
|
|
153
|
+
const noteFont = Math.min(noteWidth / columns, noteHeight / noteRows) * 0.72;
|
|
154
|
+
const digits = [];
|
|
155
|
+
const pencil = [];
|
|
156
|
+
for (let index = 0; index < area; index += 1) {
|
|
157
|
+
const shown = value(index);
|
|
158
|
+
const { x, y } = at(index);
|
|
159
|
+
if (shown !== 0) {
|
|
160
|
+
const printed = givens.cells[index] !== 0;
|
|
161
|
+
const bad = conflicts.has(index) || wrong.has(index);
|
|
162
|
+
digits.push(`<text class="kz-digit ${printed ? "kz-given" : "kz-entry"}${bad ? " kz-bad" : ""}" x="${num(x + U / 2)}" y="${num(y + U / 2 + (givens.cages !== null ? 0.06 * U : 0))}" font-size="${num(digitSize)}" data-cell="${index}">${symbolOf(shown)}</text>`);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
for (const noted of notesOf(notes[index] ?? 0)) {
|
|
166
|
+
const col = (noted - 1) % columns;
|
|
167
|
+
const row = Math.floor((noted - 1) / columns);
|
|
168
|
+
pencil.push(`<text class="kz-note" x="${num(x + noteSide + ((col + 0.5) * noteWidth) / columns)}" y="${num(y + noteTop + ((row + 0.5) * noteHeight) / noteRows)}" font-size="${num(noteFont)}">${symbolOf(noted)}</text>`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const sums = [...sumAt].map(([index, sum]) => {
|
|
173
|
+
const { x, y } = at(index);
|
|
174
|
+
return `<text class="kz-cage-sum" x="${num(x + 0.17 * U)}" y="${num(y + 0.17 * U)}" font-size="${num(0.24 * U)}" data-cell="${index}">${sum}</text>`;
|
|
175
|
+
});
|
|
176
|
+
const marks = givens.marks.map((mark) => markShape(size, mark.less, mark.more, x0, y0));
|
|
177
|
+
// Towers' clues, round the edge.
|
|
178
|
+
const clues = [];
|
|
179
|
+
if (givens.clues !== null) {
|
|
180
|
+
for (const side of TOWER_SIDES) {
|
|
181
|
+
for (let place = 0; place < size; place += 1) {
|
|
182
|
+
const clue = givens.clues[side][place];
|
|
183
|
+
if (clue === 0)
|
|
184
|
+
continue;
|
|
185
|
+
const first = lineFrom(side, place, size)[0];
|
|
186
|
+
const { x, y } = at(first);
|
|
187
|
+
const cx = side === "left" ? x - U / 2 : side === "right" ? x + U + U / 2 : x + U / 2;
|
|
188
|
+
const cy = side === "top" ? y - U / 2 : side === "bottom" ? y + U + U / 2 : y + U / 2;
|
|
189
|
+
clues.push(`<text class="kz-clue" x="${num(cx)}" y="${num(cy)}" font-size="${num(0.5 * U)}" data-side="${side}" data-at="${place}">${clue}</text>`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const hits = [];
|
|
194
|
+
if (options.interactive === true) {
|
|
195
|
+
for (let index = 0; index < area; index += 1) {
|
|
196
|
+
const { x, y } = at(index);
|
|
197
|
+
hits.push(`<rect class="kz-hit" x="${x}" y="${y}" width="${U}" height="${U}" data-cell="${index}"/>`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const label = options.label ?? kazuSay(language, "board", { name: kazuNameOf(kind, language), size });
|
|
201
|
+
return [
|
|
202
|
+
`<svg class="kazu" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${side} ${side}" role="img" aria-label="${escape(label)}" data-kind="${kind}" data-size="${size}"${options.done === true ? ` data-solved="true"` : ""}>`,
|
|
203
|
+
options.style === true ? `<style>${KAZU_STYLE}</style>` : "",
|
|
204
|
+
options.frame === true ? `<rect class="kz-frame" x="0" y="0" width="${side}" height="${side}" rx="12"/>` : "",
|
|
205
|
+
`<rect class="kz-paper" x="${options.frame === true ? 6 : 0}" y="${options.frame === true ? 6 : 0}" width="${options.frame === true ? side - 12 : side}" height="${options.frame === true ? side - 12 : side}" rx="${options.frame === true ? 8 : 6}"/>`,
|
|
206
|
+
wash.join(""),
|
|
207
|
+
`<path class="kz-grid" d="${grid.join("")}"/>`,
|
|
208
|
+
cageLines.length > 0 ? `<g class="kz-cage">${cageLines.join("")}</g>` : "",
|
|
209
|
+
`<path class="kz-box" d="${boxPath(givens, x0, y0)}"/>`,
|
|
210
|
+
pencil.join(""),
|
|
211
|
+
digits.join(""),
|
|
212
|
+
sums.join(""),
|
|
213
|
+
marks.join(""),
|
|
214
|
+
clues.join(""),
|
|
215
|
+
options.done === true ? `<rect class="kz-solved" x="${x0}" y="${y0}" width="${size * U}" height="${size * U}"/>` : "",
|
|
216
|
+
hits.join(""),
|
|
217
|
+
`</svg>`,
|
|
218
|
+
].join("");
|
|
219
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Defines the `<kazu-board>` element on the page. Import it for its effect:
|
|
3
|
+
*
|
|
4
|
+
* ```html
|
|
5
|
+
* <script type="module" src="https://cdn.jsdelivr.net/npm/@johnmorrisdotca/kazu@1/dist/element-define.js"></script>
|
|
6
|
+
* <kazu-board kind="number-place" size="9" level="medium"></kazu-board>
|
|
7
|
+
* ```
|
|
8
|
+
*
|
|
9
|
+
* A tag already defined is left as it is, and on a server, where there is no page, nothing happens.
|
|
10
|
+
*/
|
|
11
|
+
import { KazuBoard } from "./element.js";
|
|
12
|
+
if (typeof customElements !== "undefined" && customElements.get("kazu-board") === undefined)
|
|
13
|
+
customElements.define("kazu-board", KazuBoard);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { KazuGame } from "./game.ts";
|
|
2
|
+
import { type KazuEventDetail, type KazuMount } from "./mount.ts";
|
|
3
|
+
/**
|
|
4
|
+
* THE `<kazu-board>` ELEMENT: a playable Kazu puzzle in a tag, with no framework.
|
|
5
|
+
* `@johnmorrisdotca/kazu/element/define` defines it; this entry holds the class alone, to extend or to
|
|
6
|
+
* define under another name. Safe to import on a server, where there is no page: the class then extends
|
|
7
|
+
* nothing.
|
|
8
|
+
*
|
|
9
|
+
* ```html
|
|
10
|
+
* <kazu-board kind="sum-cages" size="9" level="medium" seed="42"></kazu-board>
|
|
11
|
+
* <kazu-board kind="towers" size="5" givens="…" solution="…" hints="show"></kazu-board>
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* Attributes (each is read again when it changes):
|
|
15
|
+
* - `kind`: `number-place` (default), `jigsaw`, `diagonal`, `sum-cages`, `more-or-less` or `towers`.
|
|
16
|
+
* - `size` (the kind's default side if left out) with `level` (`easy`, `medium` (default) or `hard`) and
|
|
17
|
+
* `seed` (a new one if left out): the puzzle is made in the page. Or `size` with `givens` (and
|
|
18
|
+
* `solution`, if you have it): a puzzle of your own.
|
|
19
|
+
* - `run` and `notes`: what has been written and the pencil marks, from an event's `run` and `notes`, to
|
|
20
|
+
* carry on a puzzle half done. `elapsed`: milliseconds already on the clock.
|
|
21
|
+
* - `hints`: `place` (default), `show` or `off`. `check`: `count` (default), `show` or `off`.
|
|
22
|
+
* - `conflicts`, `peers`, `tidy`, `clock`, `controls`, `tap-to-step`: each on by default; `off` turns it off.
|
|
23
|
+
* - `lang`: `en` or `ja`, or the page's.
|
|
24
|
+
*
|
|
25
|
+
* It fires `kazu-change`, `kazu-hint`, `kazu-check` and `kazu-solve` (see `mountKazu`), and has the methods
|
|
26
|
+
* `undo()`, `hint()`, `check()`, `restart()`, `pencil()` and `select()`. Nothing in it can be selected, and
|
|
27
|
+
* its box stays one steady square whatever is drawn.
|
|
28
|
+
*/
|
|
29
|
+
declare const ElementBase: typeof HTMLElement;
|
|
30
|
+
export declare class KazuBoard extends ElementBase {
|
|
31
|
+
#private;
|
|
32
|
+
static observedAttributes: string[];
|
|
33
|
+
connectedCallback(): void;
|
|
34
|
+
disconnectedCallback(): void;
|
|
35
|
+
attributeChangedCallback(): void;
|
|
36
|
+
/** The mounted board's handle (`mountKazu`), or null until a puzzle has been made. */
|
|
37
|
+
get mount(): KazuMount | null;
|
|
38
|
+
get game(): KazuGame | null;
|
|
39
|
+
/** What the events would tell now. */
|
|
40
|
+
get detail(): KazuEventDetail | null;
|
|
41
|
+
undo(): void;
|
|
42
|
+
hint(): void;
|
|
43
|
+
check(): void;
|
|
44
|
+
restart(): void;
|
|
45
|
+
pencil(on?: boolean): void;
|
|
46
|
+
select(cell: number | null): void;
|
|
47
|
+
}
|
|
48
|
+
export {};
|
package/dist/element.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
2
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
3
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
4
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
5
|
+
};
|
|
6
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
7
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
9
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
10
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
11
|
+
};
|
|
12
|
+
var _KazuBoard_instances, _KazuBoard_mount, _KazuBoard_key, _KazuBoard_queued, _KazuBoard_seed, _KazuBoard_refresh;
|
|
13
|
+
import { generateKazu } from "./generate.js";
|
|
14
|
+
import { isKazuKind, isKazuLevel, isKazuSize, KAZU_SPECS } from "./kinds.js";
|
|
15
|
+
import { mountKazu } from "./mount.js";
|
|
16
|
+
import { freshKazuSeed, isKazuSeed } from "./random.js";
|
|
17
|
+
/**
|
|
18
|
+
* THE `<kazu-board>` ELEMENT: a playable Kazu puzzle in a tag, with no framework.
|
|
19
|
+
* `@johnmorrisdotca/kazu/element/define` defines it; this entry holds the class alone, to extend or to
|
|
20
|
+
* define under another name. Safe to import on a server, where there is no page: the class then extends
|
|
21
|
+
* nothing.
|
|
22
|
+
*
|
|
23
|
+
* ```html
|
|
24
|
+
* <kazu-board kind="sum-cages" size="9" level="medium" seed="42"></kazu-board>
|
|
25
|
+
* <kazu-board kind="towers" size="5" givens="…" solution="…" hints="show"></kazu-board>
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* Attributes (each is read again when it changes):
|
|
29
|
+
* - `kind`: `number-place` (default), `jigsaw`, `diagonal`, `sum-cages`, `more-or-less` or `towers`.
|
|
30
|
+
* - `size` (the kind's default side if left out) with `level` (`easy`, `medium` (default) or `hard`) and
|
|
31
|
+
* `seed` (a new one if left out): the puzzle is made in the page. Or `size` with `givens` (and
|
|
32
|
+
* `solution`, if you have it): a puzzle of your own.
|
|
33
|
+
* - `run` and `notes`: what has been written and the pencil marks, from an event's `run` and `notes`, to
|
|
34
|
+
* carry on a puzzle half done. `elapsed`: milliseconds already on the clock.
|
|
35
|
+
* - `hints`: `place` (default), `show` or `off`. `check`: `count` (default), `show` or `off`.
|
|
36
|
+
* - `conflicts`, `peers`, `tidy`, `clock`, `controls`, `tap-to-step`: each on by default; `off` turns it off.
|
|
37
|
+
* - `lang`: `en` or `ja`, or the page's.
|
|
38
|
+
*
|
|
39
|
+
* It fires `kazu-change`, `kazu-hint`, `kazu-check` and `kazu-solve` (see `mountKazu`), and has the methods
|
|
40
|
+
* `undo()`, `hint()`, `check()`, `restart()`, `pencil()` and `select()`. Nothing in it can be selected, and
|
|
41
|
+
* its box stays one steady square whatever is drawn.
|
|
42
|
+
*/
|
|
43
|
+
const ElementBase = typeof HTMLElement === "undefined" ? class {
|
|
44
|
+
} : HTMLElement;
|
|
45
|
+
const isOff = (value) => value !== null && ["false", "off", "0", "no"].includes(value.toLowerCase());
|
|
46
|
+
const oneOf = (value, allowed) => (allowed.includes(value) ? value : undefined);
|
|
47
|
+
export class KazuBoard extends ElementBase {
|
|
48
|
+
constructor() {
|
|
49
|
+
super(...arguments);
|
|
50
|
+
_KazuBoard_instances.add(this);
|
|
51
|
+
_KazuBoard_mount.set(this, null);
|
|
52
|
+
_KazuBoard_key.set(this, "");
|
|
53
|
+
_KazuBoard_queued.set(this, false);
|
|
54
|
+
_KazuBoard_seed.set(this, null);
|
|
55
|
+
}
|
|
56
|
+
connectedCallback() {
|
|
57
|
+
__classPrivateFieldGet(this, _KazuBoard_instances, "m", _KazuBoard_refresh).call(this);
|
|
58
|
+
}
|
|
59
|
+
disconnectedCallback() {
|
|
60
|
+
__classPrivateFieldGet(this, _KazuBoard_mount, "f")?.destroy();
|
|
61
|
+
__classPrivateFieldSet(this, _KazuBoard_mount, null, "f");
|
|
62
|
+
__classPrivateFieldSet(this, _KazuBoard_key, "", "f");
|
|
63
|
+
}
|
|
64
|
+
attributeChangedCallback() {
|
|
65
|
+
if (!this.isConnected || __classPrivateFieldGet(this, _KazuBoard_queued, "f"))
|
|
66
|
+
return;
|
|
67
|
+
__classPrivateFieldSet(this, _KazuBoard_queued, true, "f");
|
|
68
|
+
queueMicrotask(() => {
|
|
69
|
+
__classPrivateFieldSet(this, _KazuBoard_queued, false, "f");
|
|
70
|
+
__classPrivateFieldGet(this, _KazuBoard_instances, "m", _KazuBoard_refresh).call(this);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/** The mounted board's handle (`mountKazu`), or null until a puzzle has been made. */
|
|
74
|
+
get mount() {
|
|
75
|
+
return __classPrivateFieldGet(this, _KazuBoard_mount, "f");
|
|
76
|
+
}
|
|
77
|
+
get game() {
|
|
78
|
+
return __classPrivateFieldGet(this, _KazuBoard_mount, "f")?.game() ?? null;
|
|
79
|
+
}
|
|
80
|
+
/** What the events would tell now. */
|
|
81
|
+
get detail() {
|
|
82
|
+
return __classPrivateFieldGet(this, _KazuBoard_mount, "f")?.detail() ?? null;
|
|
83
|
+
}
|
|
84
|
+
undo() {
|
|
85
|
+
__classPrivateFieldGet(this, _KazuBoard_mount, "f")?.undo();
|
|
86
|
+
}
|
|
87
|
+
hint() {
|
|
88
|
+
__classPrivateFieldGet(this, _KazuBoard_mount, "f")?.hint();
|
|
89
|
+
}
|
|
90
|
+
check() {
|
|
91
|
+
__classPrivateFieldGet(this, _KazuBoard_mount, "f")?.check();
|
|
92
|
+
}
|
|
93
|
+
restart() {
|
|
94
|
+
__classPrivateFieldGet(this, _KazuBoard_mount, "f")?.restart();
|
|
95
|
+
}
|
|
96
|
+
pencil(on) {
|
|
97
|
+
__classPrivateFieldGet(this, _KazuBoard_mount, "f")?.pencil(on);
|
|
98
|
+
}
|
|
99
|
+
select(cell) {
|
|
100
|
+
__classPrivateFieldGet(this, _KazuBoard_mount, "f")?.select(cell);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
_KazuBoard_mount = new WeakMap(), _KazuBoard_key = new WeakMap(), _KazuBoard_queued = new WeakMap(), _KazuBoard_seed = new WeakMap(), _KazuBoard_instances = new WeakSet(), _KazuBoard_refresh = function _KazuBoard_refresh() {
|
|
104
|
+
const kind = isKazuKind(this.getAttribute("kind")) ? this.getAttribute("kind") : "number-place";
|
|
105
|
+
const asked = Number(this.getAttribute("size"));
|
|
106
|
+
const size = Number.isInteger(asked) && asked > 0 ? asked : KAZU_SPECS[kind].defaultSize;
|
|
107
|
+
const level = isKazuLevel(this.getAttribute("level")) ? this.getAttribute("level") : "medium";
|
|
108
|
+
const givens = this.getAttribute("givens") ?? undefined;
|
|
109
|
+
const solution = this.getAttribute("solution") ?? undefined;
|
|
110
|
+
const seedAttribute = this.getAttribute("seed");
|
|
111
|
+
const seed = isKazuSeed(Number(seedAttribute)) && seedAttribute !== null ? Number(seedAttribute) : (__classPrivateFieldSet(this, _KazuBoard_seed, __classPrivateFieldGet(this, _KazuBoard_seed, "f") ?? freshKazuSeed(), "f"));
|
|
112
|
+
const run = this.getAttribute("run") ?? undefined;
|
|
113
|
+
const notes = this.getAttribute("notes") ?? undefined;
|
|
114
|
+
const elapsed = this.getAttribute("elapsed") === null ? undefined : Number(this.getAttribute("elapsed"));
|
|
115
|
+
const settings = {
|
|
116
|
+
hints: oneOf(this.getAttribute("hints"), ["place", "show", "off"]),
|
|
117
|
+
check: oneOf(this.getAttribute("check"), ["count", "show", "off"]),
|
|
118
|
+
conflicts: !isOff(this.getAttribute("conflicts")),
|
|
119
|
+
peers: !isOff(this.getAttribute("peers")),
|
|
120
|
+
tidy: !isOff(this.getAttribute("tidy")),
|
|
121
|
+
tapToStep: !isOff(this.getAttribute("tap-to-step")),
|
|
122
|
+
clock: !isOff(this.getAttribute("clock")),
|
|
123
|
+
controls: !isOff(this.getAttribute("controls")),
|
|
124
|
+
language: oneOf(this.getAttribute("lang"), ["en", "ja"]),
|
|
125
|
+
};
|
|
126
|
+
const key = JSON.stringify([kind, size, givens === undefined ? [level, seed] : givens, solution, run, notes, elapsed]);
|
|
127
|
+
if (key === __classPrivateFieldGet(this, _KazuBoard_key, "f") && __classPrivateFieldGet(this, _KazuBoard_mount, "f") !== null) {
|
|
128
|
+
__classPrivateFieldGet(this, _KazuBoard_mount, "f").set(settings);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (givens === undefined && !isKazuSize(kind, size))
|
|
132
|
+
return;
|
|
133
|
+
const made = givens === undefined ? generateKazu(kind, size, level, seed) : null;
|
|
134
|
+
__classPrivateFieldGet(this, _KazuBoard_mount, "f")?.destroy();
|
|
135
|
+
__classPrivateFieldSet(this, _KazuBoard_mount, null, "f");
|
|
136
|
+
__classPrivateFieldSet(this, _KazuBoard_key, key, "f");
|
|
137
|
+
__classPrivateFieldSet(this, _KazuBoard_mount, mountKazu(this, {
|
|
138
|
+
kind,
|
|
139
|
+
size,
|
|
140
|
+
givens: made?.givens ?? givens,
|
|
141
|
+
solution: made?.solution ?? solution,
|
|
142
|
+
level: made?.level,
|
|
143
|
+
seed: made?.seed,
|
|
144
|
+
run,
|
|
145
|
+
notes,
|
|
146
|
+
elapsed: elapsed !== undefined && Number.isFinite(elapsed) ? elapsed : undefined,
|
|
147
|
+
...settings,
|
|
148
|
+
}), "f");
|
|
149
|
+
};
|
|
150
|
+
KazuBoard.observedAttributes = ["kind", "size", "level", "seed", "givens", "solution", "run", "notes", "elapsed", "hints", "check", "conflicts", "peers", "tidy", "tap-to-step", "clock", "controls", "lang"];
|
package/dist/game.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type KazuGivens } from "./givens.ts";
|
|
2
|
+
import type { KazuKind } from "./kinds.ts";
|
|
3
|
+
/**
|
|
4
|
+
* A PUZZLE IN PLAY, as pure functions: what has been written, the pencil marks, and how to take a
|
|
5
|
+
* step back. Every function returns a new game and leaves the one it was given as it was, so a page
|
|
6
|
+
* can keep them as its history, and a server can replay a solve with no page at all. `mountKazu`
|
|
7
|
+
* is these functions with a drawing and a finger on them.
|
|
8
|
+
*/
|
|
9
|
+
export type KazuGame = {
|
|
10
|
+
kind: KazuKind;
|
|
11
|
+
size: number;
|
|
12
|
+
/** The puzzle's givens code, as made. */
|
|
13
|
+
givens: string;
|
|
14
|
+
/** What the puzzle was printed with. */
|
|
15
|
+
printed: KazuGivens;
|
|
16
|
+
/** What the player has written, row-major, 0 where nothing; always 0 on a printed cell. */
|
|
17
|
+
entries: readonly number[];
|
|
18
|
+
/** Pencil marks, a bit mask a cell: bit `v` is the note `v`. Always 0 on a printed cell or a cell with a number in it. */
|
|
19
|
+
notes: readonly number[];
|
|
20
|
+
/** The grids before this one, newest last, for Undo. */
|
|
21
|
+
past: readonly {
|
|
22
|
+
entries: readonly number[];
|
|
23
|
+
notes: readonly number[];
|
|
24
|
+
}[];
|
|
25
|
+
};
|
|
26
|
+
export type KazuGameOptions = {
|
|
27
|
+
/** Entries to start from: a run kept half done (`decodeRun`). Printed cells are ignored. */
|
|
28
|
+
entries?: readonly number[];
|
|
29
|
+
/** Pencil marks to start from (`decodeNotes`). */
|
|
30
|
+
notes?: readonly number[];
|
|
31
|
+
};
|
|
32
|
+
/** A game of a puzzle, or null for givens that are not a puzzle of that kind and side. */
|
|
33
|
+
export declare function newKazuGame(kind: KazuKind, size: number, givens: string, options?: KazuGameOptions): KazuGame | null;
|
|
34
|
+
/** The whole grid as it stands: the printed numbers, and the player's where nothing is printed. */
|
|
35
|
+
export declare function valuesOf(game: KazuGame): number[];
|
|
36
|
+
/** Whether a cell was printed with its number: it never changes. */
|
|
37
|
+
export declare function isPrinted(game: KazuGame, cell: number): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Write a number into a cell (0 empties it). Its own pencil marks go, and with `tidy` (the default)
|
|
40
|
+
* the number comes out of the pencil marks of every cell that shares a group with it, as a person
|
|
41
|
+
* with a pencil would rub it out. Nothing happens to a printed cell, or when the cell already holds
|
|
42
|
+
* that number.
|
|
43
|
+
*/
|
|
44
|
+
export declare function enterNumber(game: KazuGame, cell: number, value: number, tidy?: boolean): KazuGame;
|
|
45
|
+
/** Turn the pencil mark `value` on or off in a cell. Nothing happens to a printed cell or a cell that holds a number. */
|
|
46
|
+
export declare function toggleNote(game: KazuGame, cell: number, value: number): KazuGame;
|
|
47
|
+
/** Empty a cell of its number and its pencil marks. */
|
|
48
|
+
export declare function clearCell(game: KazuGame, cell: number): KazuGame;
|
|
49
|
+
/** Take back the last change. The same game when there is nothing to take back. */
|
|
50
|
+
export declare function undoKazu(game: KazuGame): KazuGame;
|
|
51
|
+
/** Start again: every entry and pencil mark gone, and nothing to undo. */
|
|
52
|
+
export declare function restartKazu(game: KazuGame): KazuGame;
|
|
53
|
+
/** How far along it is: cells with a number (printed or written) out of all of them. */
|
|
54
|
+
export declare function kazuProgress(game: KazuGame): {
|
|
55
|
+
filled: number;
|
|
56
|
+
total: number;
|
|
57
|
+
};
|
|
58
|
+
/** The whole grid as a cells code: what `checkKazu` takes as an answer. */
|
|
59
|
+
export declare function answerOf(game: KazuGame): string;
|
|
60
|
+
/** Whether the grid is full and right, by the rules alone (`checkKazu`): every puzzle has exactly one answer, so a grid that keeps every rule IS that answer. */
|
|
61
|
+
export declare function isSolved(game: KazuGame): boolean;
|
|
62
|
+
/** The cells that break a rule now (`conflictsOf`). */
|
|
63
|
+
export declare function gameConflicts(game: KazuGame): number[];
|
|
64
|
+
/** The numbers 1 up to the side, each with how many of it are on the grid: a pad greys a number that is all placed. */
|
|
65
|
+
export declare function numberCounts(game: KazuGame): number[];
|
package/dist/game.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { encodeCells } from "./cells.js";
|
|
2
|
+
import { checkKazu } from "./check.js";
|
|
3
|
+
import { conflictsOf } from "./conflicts.js";
|
|
4
|
+
import { layoutOfGivens, readGivens } from "./givens.js";
|
|
5
|
+
/** The most steps Undo goes back. */
|
|
6
|
+
const UNDO_MOST = 1000;
|
|
7
|
+
/** A game of a puzzle, or null for givens that are not a puzzle of that kind and side. */
|
|
8
|
+
export function newKazuGame(kind, size, givens, options = {}) {
|
|
9
|
+
const printed = readGivens(kind, size, givens);
|
|
10
|
+
if (printed === null)
|
|
11
|
+
return null;
|
|
12
|
+
const area = size * size;
|
|
13
|
+
const entries = Array.from({ length: area }, (_, i) => (printed.cells[i] !== 0 ? 0 : (options.entries?.[i] ?? 0)));
|
|
14
|
+
const notes = Array.from({ length: area }, (_, i) => (printed.cells[i] !== 0 || entries[i] !== 0 ? 0 : (options.notes?.[i] ?? 0)));
|
|
15
|
+
return { kind, size, givens, printed, entries, notes, past: [] };
|
|
16
|
+
}
|
|
17
|
+
/** The whole grid as it stands: the printed numbers, and the player's where nothing is printed. */
|
|
18
|
+
export function valuesOf(game) {
|
|
19
|
+
return game.printed.cells.map((printed, index) => (printed !== 0 ? printed : game.entries[index]));
|
|
20
|
+
}
|
|
21
|
+
/** Whether a cell was printed with its number: it never changes. */
|
|
22
|
+
export function isPrinted(game, cell) {
|
|
23
|
+
return game.printed.cells[cell] !== 0;
|
|
24
|
+
}
|
|
25
|
+
function remembered(game, entries, notes) {
|
|
26
|
+
const past = [...game.past, { entries: game.entries, notes: game.notes }].slice(-UNDO_MOST);
|
|
27
|
+
return { ...game, entries, notes, past };
|
|
28
|
+
}
|
|
29
|
+
/** The cells a cell shares a row, column, box, region, diagonal or cage with: where a number written in it takes that number out of the notes. */
|
|
30
|
+
function peersOf(game, cell) {
|
|
31
|
+
const { size } = game;
|
|
32
|
+
const layout = layoutOfGivens(game.printed);
|
|
33
|
+
const peers = new Set();
|
|
34
|
+
if (layout !== null)
|
|
35
|
+
for (const group of layout.groupsOf[cell])
|
|
36
|
+
for (const index of layout.groups[group])
|
|
37
|
+
peers.add(index);
|
|
38
|
+
else {
|
|
39
|
+
const row = Math.floor(cell / size);
|
|
40
|
+
const col = cell % size;
|
|
41
|
+
for (let k = 0; k < size; k += 1) {
|
|
42
|
+
peers.add(row * size + k);
|
|
43
|
+
peers.add(k * size + col);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
peers.delete(cell);
|
|
47
|
+
return peers;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Write a number into a cell (0 empties it). Its own pencil marks go, and with `tidy` (the default)
|
|
51
|
+
* the number comes out of the pencil marks of every cell that shares a group with it, as a person
|
|
52
|
+
* with a pencil would rub it out. Nothing happens to a printed cell, or when the cell already holds
|
|
53
|
+
* that number.
|
|
54
|
+
*/
|
|
55
|
+
export function enterNumber(game, cell, value, tidy = true) {
|
|
56
|
+
if (cell < 0 || cell >= game.size * game.size || isPrinted(game, cell) || !Number.isInteger(value) || value < 0 || value > game.size)
|
|
57
|
+
return game;
|
|
58
|
+
if (game.entries[cell] === value && (value !== 0 || game.notes[cell] === 0))
|
|
59
|
+
return game;
|
|
60
|
+
const entries = [...game.entries];
|
|
61
|
+
entries[cell] = value;
|
|
62
|
+
const notes = [...game.notes];
|
|
63
|
+
notes[cell] = 0;
|
|
64
|
+
if (tidy && value !== 0)
|
|
65
|
+
for (const peer of peersOf(game, cell))
|
|
66
|
+
notes[peer] &= ~(1 << value);
|
|
67
|
+
return remembered(game, entries, notes);
|
|
68
|
+
}
|
|
69
|
+
/** Turn the pencil mark `value` on or off in a cell. Nothing happens to a printed cell or a cell that holds a number. */
|
|
70
|
+
export function toggleNote(game, cell, value) {
|
|
71
|
+
if (cell < 0 || cell >= game.size * game.size || isPrinted(game, cell) || game.entries[cell] !== 0 || !Number.isInteger(value) || value < 1 || value > game.size)
|
|
72
|
+
return game;
|
|
73
|
+
const notes = [...game.notes];
|
|
74
|
+
notes[cell] ^= 1 << value;
|
|
75
|
+
return remembered(game, game.entries, notes);
|
|
76
|
+
}
|
|
77
|
+
/** Empty a cell of its number and its pencil marks. */
|
|
78
|
+
export function clearCell(game, cell) {
|
|
79
|
+
if (cell < 0 || cell >= game.size * game.size || isPrinted(game, cell) || (game.entries[cell] === 0 && game.notes[cell] === 0))
|
|
80
|
+
return game;
|
|
81
|
+
const entries = [...game.entries];
|
|
82
|
+
const notes = [...game.notes];
|
|
83
|
+
entries[cell] = 0;
|
|
84
|
+
notes[cell] = 0;
|
|
85
|
+
return remembered(game, entries, notes);
|
|
86
|
+
}
|
|
87
|
+
/** Take back the last change. The same game when there is nothing to take back. */
|
|
88
|
+
export function undoKazu(game) {
|
|
89
|
+
const last = game.past.at(-1);
|
|
90
|
+
return last === undefined ? game : { ...game, entries: last.entries, notes: last.notes, past: game.past.slice(0, -1) };
|
|
91
|
+
}
|
|
92
|
+
/** Start again: every entry and pencil mark gone, and nothing to undo. */
|
|
93
|
+
export function restartKazu(game) {
|
|
94
|
+
return { ...game, entries: game.entries.map(() => 0), notes: game.notes.map(() => 0), past: [] };
|
|
95
|
+
}
|
|
96
|
+
/** How far along it is: cells with a number (printed or written) out of all of them. */
|
|
97
|
+
export function kazuProgress(game) {
|
|
98
|
+
const values = valuesOf(game);
|
|
99
|
+
return { filled: values.filter((value) => value !== 0).length, total: values.length };
|
|
100
|
+
}
|
|
101
|
+
/** The whole grid as a cells code: what `checkKazu` takes as an answer. */
|
|
102
|
+
export function answerOf(game) {
|
|
103
|
+
return encodeCells(valuesOf(game));
|
|
104
|
+
}
|
|
105
|
+
/** Whether the grid is full and right, by the rules alone (`checkKazu`): every puzzle has exactly one answer, so a grid that keeps every rule IS that answer. */
|
|
106
|
+
export function isSolved(game) {
|
|
107
|
+
return kazuProgress(game).filled === game.size * game.size && checkKazu(game.kind, game.size, game.givens, answerOf(game)).ok;
|
|
108
|
+
}
|
|
109
|
+
/** The cells that break a rule now (`conflictsOf`). */
|
|
110
|
+
export function gameConflicts(game) {
|
|
111
|
+
return conflictsOf(game.printed, valuesOf(game));
|
|
112
|
+
}
|
|
113
|
+
/** The numbers 1 up to the side, each with how many of it are on the grid: a pad greys a number that is all placed. */
|
|
114
|
+
export function numberCounts(game) {
|
|
115
|
+
const counts = new Array(game.size + 1).fill(0);
|
|
116
|
+
for (const value of valuesOf(game))
|
|
117
|
+
if (value !== 0)
|
|
118
|
+
counts[value] += 1;
|
|
119
|
+
return counts;
|
|
120
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type KazuKind, type KazuLevel, type KazuPuzzle } from "./kinds.ts";
|
|
2
|
+
/**
|
|
3
|
+
* A puzzle of any of the six, from a seed: the one door. The same kind, size, level and seed make the
|
|
4
|
+
* same puzzle in every browser and every Node, for ever, which is what lets a solve be kept as those
|
|
5
|
+
* four and a race be handed one number. Every puzzle has exactly one answer, and an easy one yields
|
|
6
|
+
* to singles alone.
|
|
7
|
+
*
|
|
8
|
+
* Throws a RangeError for a kind, size, level or seed it does not make, rather than a puzzle made
|
|
9
|
+
* from something else.
|
|
10
|
+
*/
|
|
11
|
+
export declare function generateKazu(kind: KazuKind, size: number, level: KazuLevel, seed: number): KazuPuzzle;
|