@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,79 @@
1
+ import { decodeCells, EMPTY_CELL, encodeCells } from "./cells.js";
2
+ /**
3
+ * Towers (Skyscrapers) as a string: the cells, then the clues around the edge.
4
+ *
5
+ * The cells come first, row-major, as every number grid is written (`.` for
6
+ * empty; most puzzles print none). Then one character for every place a clue
7
+ * can stand outside the square, four sides of `size`: along the top left to
8
+ * right, along the bottom left to right, down the left top to bottom, down the
9
+ * right top to bottom. A digit is how many towers can be seen from there; `.`
10
+ * is no clue. A 7×7 is 49 + 28 characters.
11
+ */
12
+ export const TOWER_SIDES = ["top", "bottom", "left", "right"];
13
+ export function noClues(size) {
14
+ return { top: new Array(size).fill(0), bottom: new Array(size).fill(0), left: new Array(size).fill(0), right: new Array(size).fill(0) };
15
+ }
16
+ /**
17
+ * The cells a clue looks along, nearest first: the clue at `at` on the top
18
+ * looks down column `at`, on the bottom up it, on the left along row `at`
19
+ * to the right, on the right along it to the left.
20
+ */
21
+ export function lineFrom(side, at, size) {
22
+ return Array.from({ length: size }, (_, step) => {
23
+ const far = size - 1 - step;
24
+ if (side === "top")
25
+ return step * size + at;
26
+ if (side === "bottom")
27
+ return far * size + at;
28
+ if (side === "left")
29
+ return at * size + step;
30
+ return at * size + far;
31
+ });
32
+ }
33
+ /** How many towers show looking along `heights` from its first end: each one taller than every one before it. */
34
+ export function towersSeen(heights) {
35
+ let tallest = 0;
36
+ let seen = 0;
37
+ for (const height of heights) {
38
+ if (height > tallest) {
39
+ tallest = height;
40
+ seen += 1;
41
+ }
42
+ }
43
+ return seen;
44
+ }
45
+ /** Every clue a finished square makes true: what each side of it sees. */
46
+ export function cluesOf(solution, size) {
47
+ const clues = noClues(size);
48
+ for (const side of TOWER_SIDES) {
49
+ for (let at = 0; at < size; at += 1)
50
+ clues[side][at] = towersSeen(lineFrom(side, at, size).map((index) => solution[index]));
51
+ }
52
+ return clues;
53
+ }
54
+ export function encodeTowers(cells, clues) {
55
+ const ring = TOWER_SIDES.flatMap((side) => clues[side].map((clue) => (clue === 0 ? EMPTY_CELL : String(clue))));
56
+ return `${encodeCells(cells)}${ring.join("")}`;
57
+ }
58
+ /** The cells and clues a code says, or null for a string that is not a Towers puzzle of this size. */
59
+ export function decodeTowers(code, size) {
60
+ if (typeof code !== "string" || code.length !== size * size + 4 * size)
61
+ return null;
62
+ const cells = decodeCells(code.slice(0, size * size), size);
63
+ if (cells === null)
64
+ return null;
65
+ const clues = noClues(size);
66
+ const ring = code.slice(size * size);
67
+ for (const [s, side] of TOWER_SIDES.entries()) {
68
+ for (let at = 0; at < size; at += 1) {
69
+ const character = ring[s * size + at];
70
+ if (character === EMPTY_CELL)
71
+ continue;
72
+ const clue = Number(character);
73
+ if (!Number.isInteger(clue) || clue < 1 || clue > size)
74
+ return null;
75
+ clues[side][at] = clue;
76
+ }
77
+ }
78
+ return { cells, clues };
79
+ }
@@ -0,0 +1,65 @@
1
+ import { type TowerClues } from "./towersCode.ts";
2
+ /**
3
+ * The Skyscrapers solver (Towers): counting, the reasoning a person does, and
4
+ * how deep a guess goes.
5
+ *
6
+ * A Latin square whose rows and columns are also seen from outside. The
7
+ * candidates are bitmasks, bit `v` for a tower of height `v`, as in More or
8
+ * Less. It reasons about a clued line at one of two strengths:
9
+ *
10
+ * - `edges`, what a person sees at a glance, and what the levels are measured
11
+ * by: a 1 means the tallest tower is next to it, a clue as big as the square
12
+ * means they climb one step at a time, and a clue of `c` caps the `k`th cell
13
+ * from it at `size − c + k`, since `c − 1` taller towers must still show
14
+ * behind it. And once a clued line is down to `FEW_LEFT` open cells, every
15
+ * order of what is left — six at most — is tried in the head, as a person
16
+ * does; at seven open cells nobody tries 5,040.
17
+ * - `lines`, the general form of all of that, used to COUNT answers and never
18
+ * to grade them: a height no ordering of 1..size that fits both the cells
19
+ * and both clues puts in a cell cannot go there. It is far stronger than
20
+ * anybody's eye — graded by it, nearly every puzzle came out "easy" — and
21
+ * it is what keeps proving a 7×7 unique down to milliseconds. At seven a
22
+ * side there are 5,040 orderings, filed once by what they show from each
23
+ * end, so a clue reads only its own.
24
+ */
25
+ export type Grid = number[];
26
+ /** How hard the solver looks at a clued line: see the note at the top. */
27
+ export type Reasoning = "edges" | "lines";
28
+ /** A row or column with a clue at one end or both: its cells from the first end, and the clue at each (0 for none). */
29
+ export type Line = {
30
+ cells: number[];
31
+ from: number;
32
+ to: number;
33
+ };
34
+ export declare function cluedLines(size: number, clues: TowerClues): Line[];
35
+ /**
36
+ * What a person reads off a clued line at a glance (see the note at the top).
37
+ * Returns whether anything changed, or null for a contradiction: a cell left
38
+ * with no height, or a nearly filled line no order of its open cells can make
39
+ * show what its clues say.
40
+ */
41
+ export declare function narrowEdges(line: Line, candidates: number[], size: number): boolean | null;
42
+ export type SinglesResult = {
43
+ grid: Grid;
44
+ solved: boolean;
45
+ contradiction: boolean;
46
+ candidates: number[];
47
+ };
48
+ /**
49
+ * What can be seen without trying anything: every clued line narrowed at the
50
+ * strength asked for, a cell with one height left, a height with one place
51
+ * left in its row or column. Runs to a fixpoint. Returns a new grid; the
52
+ * input is left alone.
53
+ */
54
+ export declare function applySingles(grid: Grid, size: number, clues: TowerClues, reasoning?: Reasoning): SinglesResult;
55
+ /** How many answers the grid has, up to `limit`: the strongest reasoning at every node, then a branch on the tightest cell. */
56
+ export declare function countSolutions(grid: Grid, size: number, clues: TowerClues, limit?: number, first?: (answer: Grid) => void): number;
57
+ /** The one answer the clues allow, or null when they allow none or more than one: see `groupSolve.ts`'s `solutionOf`. */
58
+ export declare function solutionOf(grid: Grid, size: number, clues: TowerClues): Grid | null;
59
+ /**
60
+ * How many guesses, each followed by everything a glance then finds (`edges`),
61
+ * a person needs: 0 when looking finishes it, `Infinity` when there is no answer or
62
+ * when it needs more than `limit` — all a caller asking "is this within the
63
+ * level" needs to know.
64
+ */
65
+ export declare function guessDepth(grid: Grid, size: number, clues: TowerClues, limit?: number): number;
@@ -0,0 +1,276 @@
1
+ import { lineFrom, towersSeen } from "./towersCode.js";
2
+ /** How few open cells a clued line must have before a person tries every order of them. */
3
+ const FEW_LEFT = 3;
4
+ const ALL = (size) => (1 << (size + 1)) - 2;
5
+ function bitCount(mask) {
6
+ let count = 0;
7
+ for (let m = mask; m !== 0; m &= m - 1)
8
+ count += 1;
9
+ return count;
10
+ }
11
+ function lowestBit(mask) {
12
+ return 31 - Math.clz32(mask & -mask);
13
+ }
14
+ /**
15
+ * Every ordering of 1..size, filed by the towers it shows from its first end
16
+ * and from its last: `[from][to]`, with 0 meaning "either". Flat, `size`
17
+ * heights an ordering, because a line reads thousands of them per step.
18
+ */
19
+ const ORDERINGS = new Map();
20
+ function orderingsFor(size) {
21
+ const known = ORDERINGS.get(size);
22
+ if (known !== undefined)
23
+ return known;
24
+ const lists = Array.from({ length: size + 1 }, () => Array.from({ length: size + 1 }, () => []));
25
+ const heights = Array.from({ length: size }, (_, i) => i + 1);
26
+ const place = (at) => {
27
+ if (at === size) {
28
+ const from = towersSeen(heights);
29
+ const to = towersSeen([...heights].reverse());
30
+ for (const [a, b] of [[from, to], [from, 0], [0, to], [0, 0]])
31
+ lists[a][b].push(...heights);
32
+ return;
33
+ }
34
+ for (let i = at; i < size; i += 1) {
35
+ [heights[at], heights[i]] = [heights[i], heights[at]];
36
+ place(at + 1);
37
+ [heights[at], heights[i]] = [heights[i], heights[at]];
38
+ }
39
+ };
40
+ place(0);
41
+ const filed = lists.map((row) => row.map((flat) => Uint8Array.from(flat)));
42
+ ORDERINGS.set(size, filed);
43
+ return filed;
44
+ }
45
+ export function cluedLines(size, clues) {
46
+ const lines = [];
47
+ for (let at = 0; at < size; at += 1) {
48
+ if (clues.left[at] !== 0 || clues.right[at] !== 0)
49
+ lines.push({ cells: lineFrom("left", at, size), from: clues.left[at], to: clues.right[at] });
50
+ if (clues.top[at] !== 0 || clues.bottom[at] !== 0)
51
+ lines.push({ cells: lineFrom("top", at, size), from: clues.top[at], to: clues.bottom[at] });
52
+ }
53
+ return lines;
54
+ }
55
+ /**
56
+ * Narrow one clued line to what its fitting orderings allow. Returns whether
57
+ * anything changed, or null when no ordering fits — a contradiction.
58
+ */
59
+ function narrowLine(line, candidates, size) {
60
+ const flat = orderingsFor(size)[line.from][line.to];
61
+ const masks = line.cells.map((index) => candidates[index]);
62
+ const allowed = new Array(size).fill(0);
63
+ let any = false;
64
+ for (let start = 0; start < flat.length; start += size) {
65
+ let fits = true;
66
+ for (let k = 0; k < size; k += 1) {
67
+ if ((masks[k] & (1 << flat[start + k])) === 0) {
68
+ fits = false;
69
+ break;
70
+ }
71
+ }
72
+ if (!fits)
73
+ continue;
74
+ any = true;
75
+ for (let k = 0; k < size; k += 1)
76
+ allowed[k] |= 1 << flat[start + k];
77
+ }
78
+ if (!any)
79
+ return null;
80
+ let changed = false;
81
+ line.cells.forEach((index, k) => {
82
+ const narrowed = candidates[index] & allowed[k];
83
+ if (narrowed !== candidates[index]) {
84
+ candidates[index] = narrowed;
85
+ changed = true;
86
+ }
87
+ });
88
+ return changed;
89
+ }
90
+ /**
91
+ * What a person reads off a clued line at a glance (see the note at the top).
92
+ * Returns whether anything changed, or null for a contradiction: a cell left
93
+ * with no height, or a nearly filled line no order of its open cells can make
94
+ * show what its clues say.
95
+ */
96
+ export function narrowEdges(line, candidates, size) {
97
+ let changed = false;
98
+ const narrow = (index, keep) => {
99
+ const narrowed = candidates[index] & keep;
100
+ if (narrowed !== candidates[index]) {
101
+ candidates[index] = narrowed;
102
+ changed = true;
103
+ }
104
+ };
105
+ for (const [clue, cells] of [[line.from, line.cells], [line.to, [...line.cells].reverse()]]) {
106
+ if (clue === 0)
107
+ continue;
108
+ cells.forEach((index, k) => {
109
+ if (clue === 1 && k === 0)
110
+ narrow(index, 1 << size);
111
+ else if (clue === size)
112
+ narrow(index, 1 << (k + 1));
113
+ else
114
+ narrow(index, (1 << (Math.min(size, size - clue + k + 1) + 1)) - 2);
115
+ });
116
+ }
117
+ if (line.cells.some((index) => candidates[index] === 0))
118
+ return null;
119
+ // Few enough left to try every order: the same narrowing as `lines`, which also refuses a filled line its clues do not see.
120
+ if (line.cells.filter((index) => bitCount(candidates[index]) > 1).length <= FEW_LEFT) {
121
+ const tried = narrowLine(line, candidates, size);
122
+ if (tried === null)
123
+ return null;
124
+ if (tried)
125
+ changed = true;
126
+ }
127
+ return changed;
128
+ }
129
+ /**
130
+ * What can be seen without trying anything: every clued line narrowed at the
131
+ * strength asked for, a cell with one height left, a height with one place
132
+ * left in its row or column. Runs to a fixpoint. Returns a new grid; the
133
+ * input is left alone.
134
+ */
135
+ export function applySingles(grid, size, clues, reasoning = "edges") {
136
+ const narrowOne = reasoning === "lines" ? narrowLine : narrowEdges;
137
+ const work = [...grid];
138
+ const lines = cluedLines(size, clues);
139
+ const candidates = work.map((value, index) => {
140
+ if (value !== 0)
141
+ return 1 << value;
142
+ let mask = ALL(size);
143
+ const row = Math.floor(index / size);
144
+ const col = index % size;
145
+ work.forEach((other, at) => {
146
+ if (other !== 0 && (Math.floor(at / size) === row || at % size === col))
147
+ mask &= ~(1 << other);
148
+ });
149
+ return mask;
150
+ });
151
+ const set = (index, value) => {
152
+ work[index] = value;
153
+ candidates[index] = 1 << value;
154
+ const row = Math.floor(index / size);
155
+ const col = index % size;
156
+ for (let at = 0; at < work.length; at += 1) {
157
+ if (at !== index && (Math.floor(at / size) === row || at % size === col))
158
+ candidates[at] &= ~(1 << value);
159
+ }
160
+ };
161
+ const stuck = () => ({ grid: work, solved: false, contradiction: true, candidates });
162
+ let changed = true;
163
+ while (changed) {
164
+ changed = false;
165
+ for (const line of lines) {
166
+ const narrowed = narrowOne(line, candidates, size);
167
+ if (narrowed === null)
168
+ return stuck();
169
+ if (narrowed)
170
+ changed = true;
171
+ }
172
+ for (let index = 0; index < work.length; index += 1) {
173
+ if (candidates[index] === 0)
174
+ return stuck();
175
+ if (work[index] === 0 && bitCount(candidates[index]) === 1) {
176
+ set(index, lowestBit(candidates[index]));
177
+ changed = true;
178
+ }
179
+ }
180
+ for (let unit = 0; unit < size; unit += 1) {
181
+ for (let value = 1; value <= size; value += 1) {
182
+ const bit = 1 << value;
183
+ for (const kind of ["row", "col"]) {
184
+ let place = -1;
185
+ let places = 0;
186
+ for (let k = 0; k < size; k += 1) {
187
+ const index = kind === "row" ? unit * size + k : k * size + unit;
188
+ if ((candidates[index] & bit) !== 0) {
189
+ place = index;
190
+ places += 1;
191
+ }
192
+ }
193
+ if (places === 0)
194
+ return stuck();
195
+ if (places === 1 && work[place] === 0) {
196
+ set(place, value);
197
+ changed = true;
198
+ }
199
+ }
200
+ }
201
+ }
202
+ }
203
+ return { grid: work, solved: work.every((value) => value !== 0), contradiction: false, candidates };
204
+ }
205
+ /** The empty cell with the fewest heights left, which is where a guess costs least. */
206
+ function branchCell(singles, size) {
207
+ let best = -1;
208
+ let bestCount = size + 1;
209
+ singles.candidates.forEach((mask, index) => {
210
+ if (singles.grid[index] !== 0)
211
+ return;
212
+ const count = bitCount(mask);
213
+ if (count < bestCount) {
214
+ best = index;
215
+ bestCount = count;
216
+ }
217
+ });
218
+ return best;
219
+ }
220
+ /** How many answers the grid has, up to `limit`: the strongest reasoning at every node, then a branch on the tightest cell. */
221
+ export function countSolutions(grid, size, clues, limit = 2, first) {
222
+ let found = 0;
223
+ const step = (at) => {
224
+ if (found >= limit)
225
+ return;
226
+ const singles = applySingles(at, size, clues, "lines");
227
+ if (singles.contradiction)
228
+ return;
229
+ if (singles.solved) {
230
+ if (found === 0)
231
+ first?.(singles.grid);
232
+ found += 1;
233
+ return;
234
+ }
235
+ const best = branchCell(singles, size);
236
+ for (let mask = singles.candidates[best]; mask !== 0; mask &= mask - 1) {
237
+ const next = [...singles.grid];
238
+ next[best] = lowestBit(mask);
239
+ step(next);
240
+ if (found >= limit)
241
+ return;
242
+ }
243
+ };
244
+ step(grid);
245
+ return found;
246
+ }
247
+ /** The one answer the clues allow, or null when they allow none or more than one: see `groupSolve.ts`'s `solutionOf`. */
248
+ export function solutionOf(grid, size, clues) {
249
+ let answer = null;
250
+ return countSolutions(grid, size, clues, 2, (first) => (answer = [...first])) === 1 ? answer : null;
251
+ }
252
+ /**
253
+ * How many guesses, each followed by everything a glance then finds (`edges`),
254
+ * a person needs: 0 when looking finishes it, `Infinity` when there is no answer or
255
+ * when it needs more than `limit` — all a caller asking "is this within the
256
+ * level" needs to know.
257
+ */
258
+ export function guessDepth(grid, size, clues, limit = Infinity) {
259
+ const singles = applySingles(grid, size, clues);
260
+ if (singles.contradiction)
261
+ return Infinity;
262
+ if (singles.solved)
263
+ return 0;
264
+ if (limit <= 0)
265
+ return Infinity;
266
+ const best = branchCell(singles, size);
267
+ let deepest = Infinity;
268
+ for (let mask = singles.candidates[best]; mask !== 0; mask &= mask - 1) {
269
+ const next = [...singles.grid];
270
+ next[best] = lowestBit(mask);
271
+ const depth = guessDepth(next, size, clues, limit - 1);
272
+ if (depth < deepest)
273
+ deepest = depth;
274
+ }
275
+ return deepest === Infinity ? Infinity : deepest + 1;
276
+ }
@@ -0,0 +1,2 @@
1
+ /** The package's version. */
2
+ export declare const VERSION = "1.0.0";
@@ -0,0 +1,2 @@
1
+ /** The package's version. */
2
+ export const VERSION = "1.0.0";
package/package.json ADDED
@@ -0,0 +1,104 @@
1
+ {
2
+ "name": "@johnmorrisdotca/kazu",
3
+ "version": "1.0.0",
4
+ "description": "Kazu 数, grid number puzzles for JavaScript and TypeScript: Sudoku (4×4 to a 16×16 Giant), Jigsaw, Diagonal and Killer Sudoku, Futoshiki and Skyscrapers. A seeded generator whose every puzzle has exactly one answer, at three levels; a solver that counts answers; a check that reads a finished grid in O(cells); a hint that says which cell to fill next and why; puzzles as short codes; the grid drawn as SVG; and played by touch, mouse and keyboard in any page, with pencil marks, undo and a clock, as a function call or a tag. Zero dependencies.",
5
+ "keywords": [
6
+ "puzzle",
7
+ "sudoku",
8
+ "number-place",
9
+ "killer-sudoku",
10
+ "jigsaw-sudoku",
11
+ "sudoku-x",
12
+ "futoshiki",
13
+ "skyscrapers",
14
+ "logic-puzzle",
15
+ "puzzle-generator",
16
+ "puzzle-solver",
17
+ "seeded-random",
18
+ "svg",
19
+ "custom-element",
20
+ "web-component",
21
+ "typescript",
22
+ "javascript",
23
+ "zero-dependencies",
24
+ "browser",
25
+ "node",
26
+ "itsutsu"
27
+ ],
28
+ "homepage": "https://github.com/johnmorrisdotca/kazu#readme",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/johnmorrisdotca/kazu.git"
32
+ },
33
+ "bugs": {
34
+ "url": "https://github.com/johnmorrisdotca/kazu/issues"
35
+ },
36
+ "author": "John Morris",
37
+ "license": "MIT",
38
+ "type": "module",
39
+ "sideEffects": [
40
+ "./dist/element-define.js"
41
+ ],
42
+ "main": "./dist/index.js",
43
+ "module": "./dist/index.js",
44
+ "types": "./dist/index.d.ts",
45
+ "exports": {
46
+ ".": {
47
+ "types": "./dist/index.d.ts",
48
+ "import": "./dist/index.js",
49
+ "default": "./dist/index.js"
50
+ },
51
+ "./draw": {
52
+ "types": "./dist/draw-entry.d.ts",
53
+ "import": "./dist/draw-entry.js",
54
+ "default": "./dist/draw-entry.js"
55
+ },
56
+ "./play": {
57
+ "types": "./dist/play-entry.d.ts",
58
+ "import": "./dist/play-entry.js",
59
+ "default": "./dist/play-entry.js"
60
+ },
61
+ "./element": {
62
+ "types": "./dist/element.d.ts",
63
+ "import": "./dist/element.js",
64
+ "default": "./dist/element.js"
65
+ },
66
+ "./element/define": {
67
+ "types": "./dist/element-define.d.ts",
68
+ "import": "./dist/element-define.js",
69
+ "default": "./dist/element-define.js"
70
+ }
71
+ },
72
+ "files": [
73
+ "dist",
74
+ "README.md",
75
+ "LICENSE",
76
+ "CHANGELOG.md"
77
+ ],
78
+ "engines": {
79
+ "node": ">=22"
80
+ },
81
+ "scripts": {
82
+ "build": "tsc -p tsconfig.json",
83
+ "lint": "eslint .",
84
+ "typecheck": "tsc -p tsconfig.check.json",
85
+ "test": "vitest run",
86
+ "test:package": "pnpm build && node scripts/check-package.mjs",
87
+ "family:readme": "node scripts/family-readme.mjs",
88
+ "check": "pnpm lint && pnpm typecheck && pnpm test",
89
+ "prepublishOnly": "pnpm check && pnpm build",
90
+ "site": "pnpm build && node scripts/site.mjs",
91
+ "test:demo": "pnpm site && playwright test",
92
+ "pictures": "pnpm site && node scripts/readme-pictures.mjs",
93
+ "docs:make": "node scripts/docs.mjs"
94
+ },
95
+ "devDependencies": {
96
+ "@eslint/js": "^9.0.0",
97
+ "@playwright/test": "^1.63.0",
98
+ "eslint": "^9.0.0",
99
+ "typescript": "^5.9.0",
100
+ "typescript-eslint": "^8.0.0",
101
+ "vitest": "^5.0.0"
102
+ },
103
+ "packageManager": "pnpm@10.11.0"
104
+ }