@johnmorrisdotca/kazu 1.1.0 → 1.2.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 (323) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +379 -7
  3. package/dist/akari-draw-entry.d.ts +2 -0
  4. package/dist/akari-draw-entry.js +1 -0
  5. package/dist/akari-entry.d.ts +12 -0
  6. package/dist/akari-entry.js +10 -0
  7. package/dist/akari-play-entry.d.ts +2 -0
  8. package/dist/akari-play-entry.js +1 -0
  9. package/dist/akari.constants.d.ts +2 -0
  10. package/dist/akari.constants.js +2 -0
  11. package/dist/akari.types.d.ts +33 -0
  12. package/dist/akari.types.js +1 -0
  13. package/dist/akariBoard.d.ts +11 -0
  14. package/dist/akariBoard.js +96 -0
  15. package/dist/akariDraw.d.ts +4 -0
  16. package/dist/akariDraw.js +38 -0
  17. package/dist/akariGame.d.ts +15 -0
  18. package/dist/akariGame.js +71 -0
  19. package/dist/akariGenerate.d.ts +7 -0
  20. package/dist/akariGenerate.js +78 -0
  21. package/dist/akariMount.d.ts +3 -0
  22. package/dist/akariMount.js +214 -0
  23. package/dist/akariPlay.types.d.ts +25 -0
  24. package/dist/akariPlay.types.js +1 -0
  25. package/dist/akariSolve.d.ts +6 -0
  26. package/dist/akariSolve.js +106 -0
  27. package/dist/akariStrings.d.ts +98 -0
  28. package/dist/akariStrings.js +53 -0
  29. package/dist/akariStyle.d.ts +1 -0
  30. package/dist/akariStyle.js +9 -0
  31. package/dist/fillomino-draw-entry.d.ts +2 -0
  32. package/dist/fillomino-draw-entry.js +1 -0
  33. package/dist/fillomino-entry.d.ts +8 -0
  34. package/dist/fillomino-entry.js +7 -0
  35. package/dist/fillomino-play-entry.d.ts +4 -0
  36. package/dist/fillomino-play-entry.js +3 -0
  37. package/dist/fillomino.constants.d.ts +5 -0
  38. package/dist/fillomino.constants.js +5 -0
  39. package/dist/fillomino.types.d.ts +30 -0
  40. package/dist/fillomino.types.js +1 -0
  41. package/dist/fillominoBoard.d.ts +6 -0
  42. package/dist/fillominoBoard.js +84 -0
  43. package/dist/fillominoDraw.d.ts +3 -0
  44. package/dist/fillominoDraw.js +31 -0
  45. package/dist/fillominoGame.d.ts +13 -0
  46. package/dist/fillominoGame.js +65 -0
  47. package/dist/fillominoGenerate.d.ts +3 -0
  48. package/dist/fillominoGenerate.js +125 -0
  49. package/dist/fillominoMount.d.ts +2 -0
  50. package/dist/fillominoMount.js +211 -0
  51. package/dist/fillominoPlay.types.d.ts +25 -0
  52. package/dist/fillominoPlay.types.js +1 -0
  53. package/dist/fillominoSolve.d.ts +6 -0
  54. package/dist/fillominoSolve.js +111 -0
  55. package/dist/fillominoStrings.d.ts +102 -0
  56. package/dist/fillominoStrings.js +13 -0
  57. package/dist/fillominoStyle.d.ts +1 -0
  58. package/dist/fillominoStyle.js +13 -0
  59. package/dist/fillominoWorker.d.ts +1 -0
  60. package/dist/fillominoWorker.js +10 -0
  61. package/dist/heyawake-draw-entry.d.ts +2 -0
  62. package/dist/heyawake-draw-entry.js +1 -0
  63. package/dist/heyawake-entry.d.ts +13 -0
  64. package/dist/heyawake-entry.js +11 -0
  65. package/dist/heyawake-play-entry.d.ts +2 -0
  66. package/dist/heyawake-play-entry.js +1 -0
  67. package/dist/heyawake.constants.d.ts +5 -0
  68. package/dist/heyawake.constants.js +5 -0
  69. package/dist/heyawake.types.d.ts +35 -0
  70. package/dist/heyawake.types.js +1 -0
  71. package/dist/heyawakeBoard.d.ts +7 -0
  72. package/dist/heyawakeBoard.js +116 -0
  73. package/dist/heyawakeDraw.d.ts +3 -0
  74. package/dist/heyawakeDraw.js +27 -0
  75. package/dist/heyawakeGame.d.ts +12 -0
  76. package/dist/heyawakeGame.js +51 -0
  77. package/dist/heyawakeGenerate.d.ts +2 -0
  78. package/dist/heyawakeGenerate.js +114 -0
  79. package/dist/heyawakeMount.d.ts +2 -0
  80. package/dist/heyawakeMount.js +106 -0
  81. package/dist/heyawakePlay.types.d.ts +24 -0
  82. package/dist/heyawakePlay.types.js +1 -0
  83. package/dist/heyawakeSolve.d.ts +5 -0
  84. package/dist/heyawakeSolve.js +52 -0
  85. package/dist/heyawakeStrings.d.ts +19 -0
  86. package/dist/heyawakeStrings.js +7 -0
  87. package/dist/heyawakeStyle.d.ts +1 -0
  88. package/dist/heyawakeStyle.js +2 -0
  89. package/dist/heyawakeWorker.d.ts +1 -0
  90. package/dist/heyawakeWorker.js +10 -0
  91. package/dist/hitori-draw-entry.d.ts +2 -0
  92. package/dist/hitori-draw-entry.js +1 -0
  93. package/dist/hitori-entry.d.ts +6 -0
  94. package/dist/hitori-entry.js +6 -0
  95. package/dist/hitori-play-entry.d.ts +4 -0
  96. package/dist/hitori-play-entry.js +3 -0
  97. package/dist/hitori.constants.d.ts +2 -0
  98. package/dist/hitori.constants.js +2 -0
  99. package/dist/hitori.types.d.ts +20 -0
  100. package/dist/hitori.types.js +1 -0
  101. package/dist/hitoriBoard.d.ts +8 -0
  102. package/dist/hitoriBoard.js +72 -0
  103. package/dist/hitoriDraw.d.ts +4 -0
  104. package/dist/hitoriDraw.js +25 -0
  105. package/dist/hitoriGame.d.ts +10 -0
  106. package/dist/hitoriGame.js +48 -0
  107. package/dist/hitoriGenerate.d.ts +3 -0
  108. package/dist/hitoriGenerate.js +163 -0
  109. package/dist/hitoriMount.d.ts +3 -0
  110. package/dist/hitoriMount.js +111 -0
  111. package/dist/hitoriPlay.types.d.ts +25 -0
  112. package/dist/hitoriPlay.types.js +1 -0
  113. package/dist/hitoriSolve.d.ts +6 -0
  114. package/dist/hitoriSolve.js +69 -0
  115. package/dist/hitoriStrings.d.ts +70 -0
  116. package/dist/hitoriStrings.js +17 -0
  117. package/dist/hitoriStyle.d.ts +1 -0
  118. package/dist/hitoriStyle.js +9 -0
  119. package/dist/index.d.ts +11 -0
  120. package/dist/index.js +11 -0
  121. package/dist/juosan-draw-entry.d.ts +2 -0
  122. package/dist/juosan-draw-entry.js +1 -0
  123. package/dist/juosan-entry.d.ts +11 -0
  124. package/dist/juosan-entry.js +10 -0
  125. package/dist/juosan-play-entry.d.ts +4 -0
  126. package/dist/juosan-play-entry.js +3 -0
  127. package/dist/juosan.constants.d.ts +4 -0
  128. package/dist/juosan.constants.js +4 -0
  129. package/dist/juosan.types.d.ts +33 -0
  130. package/dist/juosan.types.js +1 -0
  131. package/dist/juosanBoard.d.ts +7 -0
  132. package/dist/juosanBoard.js +102 -0
  133. package/dist/juosanDraw.d.ts +4 -0
  134. package/dist/juosanDraw.js +49 -0
  135. package/dist/juosanGame.d.ts +20 -0
  136. package/dist/juosanGame.js +86 -0
  137. package/dist/juosanGenerate.d.ts +3 -0
  138. package/dist/juosanGenerate.js +31 -0
  139. package/dist/juosanMount.d.ts +3 -0
  140. package/dist/juosanMount.js +206 -0
  141. package/dist/juosanPlay.types.d.ts +24 -0
  142. package/dist/juosanPlay.types.js +1 -0
  143. package/dist/juosanSolve.d.ts +3 -0
  144. package/dist/juosanSolve.js +82 -0
  145. package/dist/juosanStrings.d.ts +77 -0
  146. package/dist/juosanStrings.js +43 -0
  147. package/dist/juosanStyle.d.ts +1 -0
  148. package/dist/juosanStyle.js +1 -0
  149. package/dist/kakuro-draw-entry.d.ts +2 -0
  150. package/dist/kakuro-draw-entry.js +1 -0
  151. package/dist/kakuro-entry.d.ts +12 -0
  152. package/dist/kakuro-entry.js +10 -0
  153. package/dist/kakuro-play-entry.d.ts +4 -0
  154. package/dist/kakuro-play-entry.js +3 -0
  155. package/dist/kakuro.constants.d.ts +6 -0
  156. package/dist/kakuro.constants.js +6 -0
  157. package/dist/kakuro.types.d.ts +46 -0
  158. package/dist/kakuro.types.js +1 -0
  159. package/dist/kakuroBoard.d.ts +8 -0
  160. package/dist/kakuroBoard.js +117 -0
  161. package/dist/kakuroDraw.d.ts +10 -0
  162. package/dist/kakuroDraw.js +21 -0
  163. package/dist/kakuroGame.d.ts +12 -0
  164. package/dist/kakuroGame.js +74 -0
  165. package/dist/kakuroGenerate.d.ts +3 -0
  166. package/dist/kakuroGenerate.js +130 -0
  167. package/dist/kakuroMount.d.ts +2 -0
  168. package/dist/kakuroMount.js +109 -0
  169. package/dist/kakuroPlay.types.d.ts +20 -0
  170. package/dist/kakuroPlay.types.js +1 -0
  171. package/dist/kakuroSolve.d.ts +6 -0
  172. package/dist/kakuroSolve.js +85 -0
  173. package/dist/kakuroStrings.d.ts +82 -0
  174. package/dist/kakuroStrings.js +5 -0
  175. package/dist/kakuroStyle.d.ts +1 -0
  176. package/dist/kakuroStyle.js +1 -0
  177. package/dist/masyu-draw-entry.d.ts +2 -0
  178. package/dist/masyu-draw-entry.js +1 -0
  179. package/dist/masyu-entry.d.ts +6 -0
  180. package/dist/masyu-entry.js +6 -0
  181. package/dist/masyu-play-entry.d.ts +4 -0
  182. package/dist/masyu-play-entry.js +3 -0
  183. package/dist/masyu.constants.d.ts +2 -0
  184. package/dist/masyu.constants.js +2 -0
  185. package/dist/masyu.types.d.ts +46 -0
  186. package/dist/masyu.types.js +1 -0
  187. package/dist/masyuBoard.d.ts +10 -0
  188. package/dist/masyuBoard.js +88 -0
  189. package/dist/masyuDraw.d.ts +2 -0
  190. package/dist/masyuDraw.js +33 -0
  191. package/dist/masyuGame.d.ts +9 -0
  192. package/dist/masyuGame.js +46 -0
  193. package/dist/masyuGenerate.d.ts +3 -0
  194. package/dist/masyuGenerate.js +56 -0
  195. package/dist/masyuMount.d.ts +2 -0
  196. package/dist/masyuMount.js +118 -0
  197. package/dist/masyuPlay.types.d.ts +1 -0
  198. package/dist/masyuPlay.types.js +1 -0
  199. package/dist/masyuSolve.d.ts +6 -0
  200. package/dist/masyuSolve.js +88 -0
  201. package/dist/masyuStrings.d.ts +28 -0
  202. package/dist/masyuStrings.js +4 -0
  203. package/dist/masyuStyle.d.ts +1 -0
  204. package/dist/masyuStyle.js +11 -0
  205. package/dist/nurikabe-draw-entry.d.ts +2 -0
  206. package/dist/nurikabe-draw-entry.js +1 -0
  207. package/dist/nurikabe-entry.d.ts +6 -0
  208. package/dist/nurikabe-entry.js +6 -0
  209. package/dist/nurikabe-play-entry.d.ts +4 -0
  210. package/dist/nurikabe-play-entry.js +3 -0
  211. package/dist/nurikabe.constants.d.ts +2 -0
  212. package/dist/nurikabe.constants.js +2 -0
  213. package/dist/nurikabe.types.d.ts +20 -0
  214. package/dist/nurikabe.types.js +1 -0
  215. package/dist/nurikabeBoard.d.ts +8 -0
  216. package/dist/nurikabeBoard.js +72 -0
  217. package/dist/nurikabeDraw.d.ts +4 -0
  218. package/dist/nurikabeDraw.js +15 -0
  219. package/dist/nurikabeGame.d.ts +8 -0
  220. package/dist/nurikabeGame.js +40 -0
  221. package/dist/nurikabeGenerate.d.ts +3 -0
  222. package/dist/nurikabeGenerate.js +31 -0
  223. package/dist/nurikabeMount.d.ts +3 -0
  224. package/dist/nurikabeMount.js +99 -0
  225. package/dist/nurikabePlay.types.d.ts +25 -0
  226. package/dist/nurikabePlay.types.js +1 -0
  227. package/dist/nurikabeSolve.d.ts +6 -0
  228. package/dist/nurikabeSolve.js +75 -0
  229. package/dist/nurikabeStrings.d.ts +70 -0
  230. package/dist/nurikabeStrings.js +5 -0
  231. package/dist/nurikabeStyle.d.ts +1 -0
  232. package/dist/nurikabeStyle.js +9 -0
  233. package/dist/ripple-draw-entry.d.ts +2 -0
  234. package/dist/ripple-draw-entry.js +1 -0
  235. package/dist/ripple-entry.d.ts +12 -0
  236. package/dist/ripple-entry.js +11 -0
  237. package/dist/ripple-play-entry.d.ts +4 -0
  238. package/dist/ripple-play-entry.js +3 -0
  239. package/dist/ripple.constants.d.ts +4 -0
  240. package/dist/ripple.constants.js +4 -0
  241. package/dist/ripple.types.d.ts +34 -0
  242. package/dist/ripple.types.js +1 -0
  243. package/dist/rippleBoard.d.ts +7 -0
  244. package/dist/rippleBoard.js +99 -0
  245. package/dist/rippleDraw.d.ts +3 -0
  246. package/dist/rippleDraw.js +32 -0
  247. package/dist/rippleGame.d.ts +13 -0
  248. package/dist/rippleGame.js +85 -0
  249. package/dist/rippleGenerate.d.ts +3 -0
  250. package/dist/rippleGenerate.js +57 -0
  251. package/dist/rippleMount.d.ts +2 -0
  252. package/dist/rippleMount.js +179 -0
  253. package/dist/ripplePlay.types.d.ts +23 -0
  254. package/dist/ripplePlay.types.js +1 -0
  255. package/dist/rippleSolve.d.ts +6 -0
  256. package/dist/rippleSolve.js +83 -0
  257. package/dist/rippleStrings.d.ts +86 -0
  258. package/dist/rippleStrings.js +5 -0
  259. package/dist/rippleStyle.d.ts +1 -0
  260. package/dist/rippleStyle.js +1 -0
  261. package/dist/shikaku-entry.d.ts +1 -0
  262. package/dist/shikaku-entry.js +1 -0
  263. package/dist/shikakuPacks.d.ts +27 -0
  264. package/dist/shikakuPacks.js +26 -0
  265. package/dist/slitherlink-draw-entry.d.ts +2 -0
  266. package/dist/slitherlink-draw-entry.js +1 -0
  267. package/dist/slitherlink-entry.d.ts +11 -0
  268. package/dist/slitherlink-entry.js +9 -0
  269. package/dist/slitherlink-play-entry.d.ts +4 -0
  270. package/dist/slitherlink-play-entry.js +3 -0
  271. package/dist/slitherlink.constants.d.ts +2 -0
  272. package/dist/slitherlink.constants.js +2 -0
  273. package/dist/slitherlink.types.d.ts +34 -0
  274. package/dist/slitherlink.types.js +1 -0
  275. package/dist/slitherlinkBoard.d.ts +12 -0
  276. package/dist/slitherlinkBoard.js +132 -0
  277. package/dist/slitherlinkDraw.d.ts +4 -0
  278. package/dist/slitherlinkDraw.js +51 -0
  279. package/dist/slitherlinkGame.d.ts +8 -0
  280. package/dist/slitherlinkGame.js +63 -0
  281. package/dist/slitherlinkGenerate.d.ts +3 -0
  282. package/dist/slitherlinkGenerate.js +101 -0
  283. package/dist/slitherlinkMount.d.ts +3 -0
  284. package/dist/slitherlinkMount.js +267 -0
  285. package/dist/slitherlinkPlay.types.d.ts +23 -0
  286. package/dist/slitherlinkPlay.types.js +1 -0
  287. package/dist/slitherlinkSolve.d.ts +6 -0
  288. package/dist/slitherlinkSolve.js +127 -0
  289. package/dist/slitherlinkStrings.d.ts +86 -0
  290. package/dist/slitherlinkStrings.js +47 -0
  291. package/dist/slitherlinkStyle.d.ts +1 -0
  292. package/dist/slitherlinkStyle.js +18 -0
  293. package/dist/version.d.ts +1 -1
  294. package/dist/version.js +1 -1
  295. package/dist/yajilin-draw-entry.d.ts +2 -0
  296. package/dist/yajilin-draw-entry.js +1 -0
  297. package/dist/yajilin-entry.d.ts +6 -0
  298. package/dist/yajilin-entry.js +6 -0
  299. package/dist/yajilin-play-entry.d.ts +4 -0
  300. package/dist/yajilin-play-entry.js +3 -0
  301. package/dist/yajilin.constants.d.ts +2 -0
  302. package/dist/yajilin.constants.js +2 -0
  303. package/dist/yajilin.types.d.ts +63 -0
  304. package/dist/yajilin.types.js +1 -0
  305. package/dist/yajilinBoard.d.ts +9 -0
  306. package/dist/yajilinBoard.js +91 -0
  307. package/dist/yajilinDraw.d.ts +2 -0
  308. package/dist/yajilinDraw.js +33 -0
  309. package/dist/yajilinGame.d.ts +15 -0
  310. package/dist/yajilinGame.js +58 -0
  311. package/dist/yajilinGenerate.d.ts +3 -0
  312. package/dist/yajilinGenerate.js +224 -0
  313. package/dist/yajilinMount.d.ts +2 -0
  314. package/dist/yajilinMount.js +162 -0
  315. package/dist/yajilinPlay.types.d.ts +1 -0
  316. package/dist/yajilinPlay.types.js +1 -0
  317. package/dist/yajilinSolve.d.ts +6 -0
  318. package/dist/yajilinSolve.js +82 -0
  319. package/dist/yajilinStrings.d.ts +32 -0
  320. package/dist/yajilinStrings.js +4 -0
  321. package/dist/yajilinStyle.d.ts +1 -0
  322. package/dist/yajilinStyle.js +12 -0
  323. package/package.json +180 -3
@@ -0,0 +1,125 @@
1
+ import { FILLOMINO_GENERATOR_MOST_CELLS, FILLOMINO_LEVELS, FILLOMINO_MOST_ATTEMPTS } from "./fillomino.constants.js";
2
+ import { checkFillomino, fillominoNeighbours } from "./fillominoBoard.js";
3
+ import { solveFillomino } from "./fillominoSolve.js";
4
+ import { isKazuSeed, seededRandom, shuffled } from "./random.js";
5
+ /** Makes an original connected-region partition, then keeps only independently proved unique clues. */
6
+ export function generateFillomino(width = 5, height = width, level = "medium", seed = 1) {
7
+ if (![width, height].every(value => Number.isInteger(value) && value >= 4 && value <= 8) || width * height > FILLOMINO_GENERATOR_MOST_CELLS
8
+ || !FILLOMINO_LEVELS.includes(level) || !isKazuSeed(seed)) {
9
+ throw new RangeError("Fillomino generation supports 4–8 cells per side and a valid level and seed");
10
+ }
11
+ const random = seededRandom(seed);
12
+ for (let attempt = 0; attempt < FILLOMINO_MOST_ATTEMPTS; attempt += 1) {
13
+ const solution = partitionBoard(width, height, level, random);
14
+ if (!solution)
15
+ continue;
16
+ const regions = regionCells(width, height, solution);
17
+ if (regions.length >= width * height * 0.34)
18
+ continue;
19
+ const givens = Array(width * height).fill(0);
20
+ const extraClues = [];
21
+ for (const region of regions) {
22
+ const choices = shuffled(region, random);
23
+ const count = level === "easy" && region.length > 1 ? 2 : level === "hard" && random() < 0.2 ? 0 : 1;
24
+ for (const cell of choices.slice(0, count))
25
+ givens[cell] = solution[cell];
26
+ extraClues.push(...choices.slice(count));
27
+ }
28
+ const clueOrder = shuffled(extraClues, random);
29
+ const board = { width, height, givens };
30
+ const maximumGivens = Math.floor(width * height * ({ easy: 0.9, medium: 0.72, hard: 0.62 }[level]));
31
+ for (let added = 0;; added += 1) {
32
+ const proof = solveFillomino(board, givens);
33
+ if (proof.complete && proof.count === 1 && proof.solution && checkFillomino(board, proof.solution).ok) {
34
+ return { ...board, seed, level, solution: proof.solution };
35
+ }
36
+ if (added >= clueOrder.length || givens.filter(Boolean).length >= maximumGivens)
37
+ break;
38
+ const cell = clueOrder[added];
39
+ givens[cell] = solution[cell];
40
+ }
41
+ }
42
+ throw new Error("No unique Fillomino found within the generation budget; try another seed or smaller board");
43
+ }
44
+ function partitionBoard(width, height, level, random) {
45
+ const board = { width, height, givens: Array(width * height).fill(0) };
46
+ const entries = Array(width * height).fill(0);
47
+ const maxArea = { easy: 4, medium: 6, hard: 8 }[level];
48
+ let trials = 0;
49
+ const visit = () => {
50
+ if (++trials > 12000)
51
+ return false;
52
+ const anchor = entries.findIndex(value => value === 0);
53
+ if (anchor < 0)
54
+ return true;
55
+ const remaining = entries.filter(value => value === 0).length;
56
+ const preferred = Array.from({ length: Math.min(maxArea, remaining) }, (_, index) => index + 1);
57
+ const sizes = shuffled(preferred, random).sort((left, right) => right - left);
58
+ for (const area of sizes) {
59
+ for (let proposal = 0; proposal < 5; proposal += 1) {
60
+ if (fillominoNeighbours(board, anchor).some(cell => entries[cell] === area))
61
+ continue;
62
+ const shape = growShape(board, anchor, area, entries, random);
63
+ if (!shape)
64
+ continue;
65
+ for (const cell of shape)
66
+ entries[cell] = area;
67
+ if (visit())
68
+ return true;
69
+ for (const cell of shape)
70
+ entries[cell] = 0;
71
+ }
72
+ }
73
+ return false;
74
+ };
75
+ const result = visit() ? [...entries] : null;
76
+ return result && checkFillomino(board, result).ok ? result : null;
77
+ }
78
+ function growShape(board, anchor, area, entries, random) {
79
+ const cells = [anchor];
80
+ const members = new Set(cells);
81
+ while (cells.length < area) {
82
+ const frontier = new Set();
83
+ for (const cell of cells) {
84
+ for (const next of fillominoNeighbours(board, cell)) {
85
+ if (entries[next] !== 0 || members.has(next))
86
+ continue;
87
+ if (fillominoNeighbours(board, next).some(adjacent => entries[adjacent] === area))
88
+ continue;
89
+ frontier.add(next);
90
+ }
91
+ }
92
+ const choices = [...frontier];
93
+ if (!choices.length)
94
+ return null;
95
+ const next = choices[Math.floor(random() * choices.length)];
96
+ cells.push(next);
97
+ members.add(next);
98
+ }
99
+ return cells;
100
+ }
101
+ function regionCells(width, height, entries) {
102
+ const board = { width, height, givens: Array(width * height).fill(0) };
103
+ const visited = new Set();
104
+ const regions = [];
105
+ for (let start = 0; start < entries.length; start += 1) {
106
+ if (visited.has(start))
107
+ continue;
108
+ const value = entries[start];
109
+ const region = [];
110
+ const pending = [start];
111
+ visited.add(start);
112
+ while (pending.length) {
113
+ const cell = pending.pop();
114
+ region.push(cell);
115
+ for (const next of fillominoNeighbours(board, cell)) {
116
+ if (!visited.has(next) && entries[next] === value) {
117
+ visited.add(next);
118
+ pending.push(next);
119
+ }
120
+ }
121
+ }
122
+ regions.push(region);
123
+ }
124
+ return regions;
125
+ }
@@ -0,0 +1,2 @@
1
+ import type { FillominoMount, FillominoMountOptions } from "./fillominoPlay.types.ts";
2
+ export declare function mountFillomino(host: HTMLElement, initial: FillominoMountOptions): FillominoMount;
@@ -0,0 +1,211 @@
1
+ import { checkFillomino } from "./fillominoBoard.js";
2
+ import { drawFillomino } from "./fillominoDraw.js";
3
+ import { decodeFillomino, encodeFillomino, hintFillomino, newFillomino, restartFillomino, setFillominoCell, undoFillomino } from "./fillominoGame.js";
4
+ import { FILLOMINO_PLAY_STYLE } from "./fillominoStyle.js";
5
+ import { fillominoWords } from "./fillominoStrings.js";
6
+ export function mountFillomino(host, initial) {
7
+ const document = host.ownerDocument;
8
+ let options = { ...initial };
9
+ let game = restoreGame(initial);
10
+ let selected = null;
11
+ let errors = [];
12
+ let message = "";
13
+ let destroyed = false;
14
+ let lastFinishState = false;
15
+ const style = document.createElement("style");
16
+ style.textContent = FILLOMINO_PLAY_STYLE;
17
+ const root = document.createElement("div");
18
+ root.className = "fillomino-play";
19
+ const status = document.createElement("p");
20
+ status.className = "fillomino-status";
21
+ status.setAttribute("role", "status");
22
+ const board = document.createElement("div");
23
+ board.className = "fillomino-board";
24
+ const controls = document.createElement("div");
25
+ controls.className = "fillomino-controls";
26
+ root.append(status, board, controls);
27
+ host.append(style, root);
28
+ const language = () => options.language ?? "en";
29
+ const copyGame = (state = game) => ({
30
+ board: { ...state.board, givens: [...state.board.givens] },
31
+ entries: [...state.entries],
32
+ history: state.history.map(entries => [...entries]),
33
+ helped: state.helped,
34
+ });
35
+ const emit = () => {
36
+ const snapshot = copyGame();
37
+ options.onChange?.(snapshot);
38
+ host.dispatchEvent(new document.defaultView.CustomEvent("fillomino-change", { detail: snapshot }));
39
+ };
40
+ const button = (label, action, disabled = false) => {
41
+ const control = document.createElement("button");
42
+ control.type = "button";
43
+ control.textContent = label;
44
+ control.disabled = disabled;
45
+ control.addEventListener("click", action);
46
+ controls.append(control);
47
+ };
48
+ const update = () => {
49
+ if (destroyed)
50
+ return;
51
+ const words = fillominoWords(language());
52
+ const checked = checkFillomino(game.board, game.entries);
53
+ status.textContent = message || (checked.ok ? game.helped ? words.helped : words.solved : checked.errors.length ? words.wrong : words.remaining);
54
+ board.replaceChildren();
55
+ board.style.setProperty("--fillomino-width", String(game.board.width));
56
+ board.style.setProperty("--fillomino-height", String(game.board.height));
57
+ const drawing = document.createElement("div");
58
+ drawing.className = "fillomino-drawing";
59
+ drawing.innerHTML = drawFillomino(game.board, {
60
+ entries: game.entries,
61
+ selected,
62
+ errors,
63
+ material: options.material,
64
+ pieces: options.pieces,
65
+ language: language(),
66
+ });
67
+ const cells = document.createElement("div");
68
+ cells.className = "fillomino-cells";
69
+ cells.setAttribute("role", "grid");
70
+ cells.setAttribute("aria-label", words.title);
71
+ for (let cell = 0; cell < game.entries.length; cell += 1) {
72
+ const x = cell % game.board.width;
73
+ const y = Math.floor(cell / game.board.width);
74
+ const given = game.board.givens[cell];
75
+ const value = game.entries[cell];
76
+ const control = document.createElement("button");
77
+ control.type = "button";
78
+ control.className = "fillomino-cell";
79
+ control.dataset.cell = String(cell);
80
+ control.setAttribute("role", "gridcell");
81
+ control.setAttribute("aria-label", `${words.row} ${y + 1}, ${words.column} ${x + 1}, ${value || words.empty}${value ? `, ${words.filled}` : ""}${given ? `, ${words.clue}` : ""}`);
82
+ control.setAttribute("aria-pressed", String(selected === cell));
83
+ control.tabIndex = selected === cell || (selected === null && cell === 0) ? 0 : -1;
84
+ control.addEventListener("click", () => {
85
+ selected = cell;
86
+ message = "";
87
+ update();
88
+ });
89
+ control.addEventListener("keydown", event => moveByKeyboard(event, cell));
90
+ cells.append(control);
91
+ }
92
+ drawing.append(cells);
93
+ board.append(drawing);
94
+ controls.replaceChildren();
95
+ const label = document.createElement("label");
96
+ label.className = "fillomino-number-label";
97
+ label.textContent = words.selectNumber;
98
+ const number = document.createElement("select");
99
+ number.setAttribute("aria-label", words.selectNumber);
100
+ number.dataset.number = "true";
101
+ for (let value = 1; value <= game.entries.length; value += 1) {
102
+ const option = document.createElement("option");
103
+ option.value = String(value);
104
+ option.textContent = String(value);
105
+ number.append(option);
106
+ }
107
+ label.append(number);
108
+ controls.append(label);
109
+ button(words.fill, () => applySelected(Number(number.value)), selected === null || Boolean(game.board.givens[selected]));
110
+ button(words.clear, () => applySelected(0), selected === null || Boolean(game.board.givens[selected]));
111
+ button(words.undo, () => {
112
+ game = undoFillomino(game);
113
+ errors = [];
114
+ message = "";
115
+ emit();
116
+ update();
117
+ }, game.history.length === 0);
118
+ button(words.check, checkBoard);
119
+ button(words.hint, giveHint);
120
+ button(words.restart, restart);
121
+ const nowFinished = checked.ok;
122
+ if (nowFinished && !lastFinishState)
123
+ options.onFinish?.(copyGame());
124
+ lastFinishState = nowFinished;
125
+ };
126
+ const applySelected = (value) => {
127
+ if (selected === null)
128
+ return;
129
+ const next = setFillominoCell(game, selected, value);
130
+ if (next === game)
131
+ return;
132
+ game = next;
133
+ errors = [];
134
+ message = "";
135
+ emit();
136
+ update();
137
+ };
138
+ const moveByKeyboard = (event, cell) => {
139
+ const x = cell % game.board.width;
140
+ const y = Math.floor(cell / game.board.width);
141
+ const moves = {
142
+ ArrowUp: y > 0 ? cell - game.board.width : cell,
143
+ ArrowDown: y + 1 < game.board.height ? cell + game.board.width : cell,
144
+ ArrowLeft: x > 0 ? cell - 1 : cell,
145
+ ArrowRight: x + 1 < game.board.width ? cell + 1 : cell,
146
+ };
147
+ if (event.key in moves) {
148
+ event.preventDefault();
149
+ selected = moves[event.key];
150
+ update();
151
+ board.querySelector(`button[data-cell="${selected}"]`)?.focus();
152
+ return;
153
+ }
154
+ if (/^[1-9]$/.test(event.key)) {
155
+ event.preventDefault();
156
+ selected = cell;
157
+ applySelected(Number(event.key));
158
+ return;
159
+ }
160
+ if (event.key === "Backspace" || event.key === "Delete") {
161
+ event.preventDefault();
162
+ selected = cell;
163
+ applySelected(0);
164
+ }
165
+ };
166
+ const checkBoard = () => {
167
+ const checked = checkFillomino(game.board, game.entries);
168
+ errors = checked.errors;
169
+ message = checked.ok ? game.helped ? fillominoWords(language()).helped : fillominoWords(language()).solved
170
+ : checked.errors.length ? fillominoWords(language()).wrong : fillominoWords(language()).checked;
171
+ update();
172
+ };
173
+ const giveHint = () => {
174
+ const hint = hintFillomino(game);
175
+ if (!hint) {
176
+ message = fillominoWords(language()).noHint;
177
+ update();
178
+ return;
179
+ }
180
+ game = setFillominoCell(game, hint.cell, hint.value);
181
+ game = { ...game, helped: true };
182
+ selected = hint.cell;
183
+ errors = [];
184
+ message = fillominoWords(language()).hintWhy;
185
+ emit();
186
+ update();
187
+ };
188
+ const restart = () => {
189
+ game = restartFillomino(game);
190
+ selected = null;
191
+ errors = [];
192
+ message = "";
193
+ emit();
194
+ update();
195
+ };
196
+ update();
197
+ return {
198
+ game: () => copyGame(),
199
+ progress: () => encodeFillomino(game),
200
+ set: (next) => { options = { ...options, ...next }; update(); },
201
+ restart,
202
+ destroy: () => { destroyed = true; root.remove(); style.remove(); },
203
+ };
204
+ }
205
+ function restoreGame(source) {
206
+ const saved = source.progress ? decodeFillomino(source.progress) : null;
207
+ return saved && saved.board.width === source.board.width && saved.board.height === source.board.height
208
+ && saved.board.givens.every((given, cell) => given === source.board.givens[cell])
209
+ ? saved
210
+ : newFillomino(source.board);
211
+ }
@@ -0,0 +1,25 @@
1
+ import type { FillominoBoard, FillominoGame } from "./fillomino.types.ts";
2
+ export type FillominoLanguage = "en" | "ja";
3
+ export type FillominoMaterial = "ivory" | "wood" | "slate";
4
+ export type FillominoPieces = "ink" | "tiles";
5
+ export type FillominoDrawOptions = {
6
+ entries?: readonly number[];
7
+ errors?: readonly number[];
8
+ selected?: number | null;
9
+ material?: FillominoMaterial;
10
+ pieces?: FillominoPieces;
11
+ language?: FillominoLanguage;
12
+ };
13
+ export type FillominoMountOptions = FillominoDrawOptions & {
14
+ board: FillominoBoard;
15
+ progress?: string;
16
+ onChange?: (game: FillominoGame) => void;
17
+ onFinish?: (game: FillominoGame) => void;
18
+ };
19
+ export type FillominoMount = {
20
+ game: () => FillominoGame;
21
+ progress: () => string;
22
+ set: (options: FillominoDrawOptions) => void;
23
+ restart: () => void;
24
+ destroy: () => void;
25
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ import type { FillominoBoard, FillominoSolve } from "./fillomino.types.ts";
2
+ /** Counts completed partitions. `complete` is false when a bound, including the answer limit, stops search. */
3
+ export declare function solveFillomino(board: FillominoBoard, entries?: readonly number[], options?: {
4
+ limit?: number;
5
+ nodes?: number;
6
+ }): FillominoSolve;
@@ -0,0 +1,111 @@
1
+ import { FILLOMINO_MOST_NODES } from "./fillomino.constants.js";
2
+ import { fillominoNeighbours, isFillominoBoard } from "./fillominoBoard.js";
3
+ /** Counts completed partitions. `complete` is false when a bound, including the answer limit, stops search. */
4
+ export function solveFillomino(board, entries = board.givens, options = {}) {
5
+ if (!isFillominoBoard(board))
6
+ throw new RangeError("Invalid Fillomino board");
7
+ const limit = options.limit ?? 2;
8
+ const budget = options.nodes ?? FILLOMINO_MOST_NODES;
9
+ if (!Number.isInteger(limit) || limit < 1 || !Number.isInteger(budget) || budget < 1) {
10
+ throw new RangeError("Invalid search bounds");
11
+ }
12
+ if (!Array.isArray(entries) || entries.length !== board.givens.length) {
13
+ throw new RangeError("Invalid Fillomino entries");
14
+ }
15
+ const fixed = entries.map((value, cell) => {
16
+ if (!Number.isInteger(value) || value < 0 || value > entries.length)
17
+ return -1;
18
+ if (board.givens[cell] && value !== board.givens[cell])
19
+ return -1;
20
+ return value || board.givens[cell];
21
+ });
22
+ if (fixed.some(value => value < 0))
23
+ return { count: 0, solution: null, complete: true, nodes: 0 };
24
+ const resolved = Array(entries.length).fill(0);
25
+ const state = { nodes: 0, stopped: false };
26
+ let count = 0;
27
+ let solution = null;
28
+ const visit = () => {
29
+ if (++state.nodes > budget) {
30
+ state.stopped = true;
31
+ return;
32
+ }
33
+ let anchor = -1;
34
+ for (let cell = 0; cell < resolved.length; cell += 1) {
35
+ if (resolved[cell] === 0) {
36
+ anchor = cell;
37
+ break;
38
+ }
39
+ }
40
+ if (anchor < 0) {
41
+ count += 1;
42
+ solution ?? (solution = [...resolved]);
43
+ if (count >= limit)
44
+ state.stopped = true;
45
+ return;
46
+ }
47
+ const choices = fixed[anchor] ? [fixed[anchor]] : Array.from({ length: entries.length }, (_, index) => index + 1);
48
+ for (const value of choices) {
49
+ const shapes = regionShapes(board, anchor, value, resolved, fixed, state, budget);
50
+ if (state.stopped)
51
+ return;
52
+ for (const shape of shapes) {
53
+ for (const cell of shape)
54
+ resolved[cell] = value;
55
+ visit();
56
+ for (const cell of shape)
57
+ resolved[cell] = 0;
58
+ if (state.stopped)
59
+ return;
60
+ }
61
+ }
62
+ };
63
+ visit();
64
+ const complete = !state.stopped;
65
+ return { count, solution, complete, nodes: Math.min(state.nodes, budget) };
66
+ }
67
+ function regionShapes(board, anchor, area, resolved, fixed, state, budget) {
68
+ if (fixed[anchor] !== 0 && fixed[anchor] !== area)
69
+ return [];
70
+ const shapes = [];
71
+ const seen = new Set();
72
+ const grow = (cells) => {
73
+ if (state.stopped)
74
+ return;
75
+ if (++state.nodes > budget) {
76
+ state.stopped = true;
77
+ return;
78
+ }
79
+ const key = [...cells].sort((a, b) => a - b).join(",");
80
+ if (seen.has(key))
81
+ return;
82
+ seen.add(key);
83
+ if (cells.length === area) {
84
+ const members = new Set(cells);
85
+ for (const cell of cells) {
86
+ for (const next of fillominoNeighbours(board, cell)) {
87
+ if (members.has(next))
88
+ continue;
89
+ if (resolved[next] === area || fixed[next] === area)
90
+ return;
91
+ }
92
+ }
93
+ shapes.push([...cells]);
94
+ return;
95
+ }
96
+ const frontier = new Set();
97
+ for (const cell of cells) {
98
+ for (const next of fillominoNeighbours(board, cell)) {
99
+ if (cells.includes(next) || resolved[next] !== 0)
100
+ continue;
101
+ if (fixed[next] !== 0 && fixed[next] !== area)
102
+ continue;
103
+ frontier.add(next);
104
+ }
105
+ }
106
+ for (const next of frontier)
107
+ grow([...cells, next]);
108
+ };
109
+ grow([anchor]);
110
+ return shapes;
111
+ }
@@ -0,0 +1,102 @@
1
+ import type { FillominoLanguage } from "./fillominoPlay.types.ts";
2
+ export declare const FILLOMINO_STRINGS: {
3
+ readonly en: {
4
+ readonly title: "Fillomino";
5
+ readonly help: "Fill every cell with a number. Connected regions of equal numbers must have exactly that many cells. Regions of the same size cannot touch. Arrow keys move; type a number to fill a cell. Clear removes your entry.";
6
+ readonly undo: "Undo";
7
+ readonly hint: "Hint";
8
+ readonly check: "Check";
9
+ readonly restart: "Restart";
10
+ readonly selectNumber: "Number";
11
+ readonly fill: "Enter";
12
+ readonly clear: "Clear";
13
+ readonly selected: "Selected";
14
+ readonly row: "Row";
15
+ readonly column: "column";
16
+ readonly empty: "empty";
17
+ readonly clue: "given";
18
+ readonly filled: "filled";
19
+ readonly checked: "The filled regions follow the rules so far.";
20
+ readonly wrong: "Some regions are too large or a clue was changed.";
21
+ readonly remaining: "Keep filling the empty cells.";
22
+ readonly solved: "Every region has the right size. Solved!";
23
+ readonly helped: "Solved with a hint.";
24
+ readonly hintWhy: "This cell follows from the only complete solution.";
25
+ readonly noHint: "No proved hint is available from the current entries.";
26
+ readonly restored: "Progress restored.";
27
+ };
28
+ readonly ja: {
29
+ readonly title: "フィロミノ";
30
+ readonly help: "すべてのマスに数字を入れます。同じ数字が上下左右につながった領域のマス数は、その数字と同じです。同じ大きさの領域は辺で接しません。矢印キーで移動し、数字を入力します。消去で入力を消します。";
31
+ readonly undo: "戻す";
32
+ readonly hint: "ヒント";
33
+ readonly check: "確かめる";
34
+ readonly restart: "やり直す";
35
+ readonly selectNumber: "数字";
36
+ readonly fill: "入力";
37
+ readonly clear: "消去";
38
+ readonly selected: "選択中";
39
+ readonly row: "行";
40
+ readonly column: "列";
41
+ readonly empty: "空き";
42
+ readonly clue: "ヒント";
43
+ readonly filled: "入力済み";
44
+ readonly checked: "ここまでの領域はルールに合っています。";
45
+ readonly wrong: "大きすぎる領域か、変更されたヒントがあります。";
46
+ readonly remaining: "空きマスを埋めましょう。";
47
+ readonly solved: "すべての領域の大きさが合いました。完成!";
48
+ readonly helped: "ヒントを使って完成!";
49
+ readonly hintWhy: "唯一の完成形から、このマスが決まります。";
50
+ readonly noHint: "現在の入力から確かなヒントを出せません。";
51
+ readonly restored: "続きから再開しました。";
52
+ };
53
+ };
54
+ export declare function fillominoWords(language?: FillominoLanguage): {
55
+ readonly title: "Fillomino";
56
+ readonly help: "Fill every cell with a number. Connected regions of equal numbers must have exactly that many cells. Regions of the same size cannot touch. Arrow keys move; type a number to fill a cell. Clear removes your entry.";
57
+ readonly undo: "Undo";
58
+ readonly hint: "Hint";
59
+ readonly check: "Check";
60
+ readonly restart: "Restart";
61
+ readonly selectNumber: "Number";
62
+ readonly fill: "Enter";
63
+ readonly clear: "Clear";
64
+ readonly selected: "Selected";
65
+ readonly row: "Row";
66
+ readonly column: "column";
67
+ readonly empty: "empty";
68
+ readonly clue: "given";
69
+ readonly filled: "filled";
70
+ readonly checked: "The filled regions follow the rules so far.";
71
+ readonly wrong: "Some regions are too large or a clue was changed.";
72
+ readonly remaining: "Keep filling the empty cells.";
73
+ readonly solved: "Every region has the right size. Solved!";
74
+ readonly helped: "Solved with a hint.";
75
+ readonly hintWhy: "This cell follows from the only complete solution.";
76
+ readonly noHint: "No proved hint is available from the current entries.";
77
+ readonly restored: "Progress restored.";
78
+ } | {
79
+ readonly title: "フィロミノ";
80
+ readonly help: "すべてのマスに数字を入れます。同じ数字が上下左右につながった領域のマス数は、その数字と同じです。同じ大きさの領域は辺で接しません。矢印キーで移動し、数字を入力します。消去で入力を消します。";
81
+ readonly undo: "戻す";
82
+ readonly hint: "ヒント";
83
+ readonly check: "確かめる";
84
+ readonly restart: "やり直す";
85
+ readonly selectNumber: "数字";
86
+ readonly fill: "入力";
87
+ readonly clear: "消去";
88
+ readonly selected: "選択中";
89
+ readonly row: "行";
90
+ readonly column: "列";
91
+ readonly empty: "空き";
92
+ readonly clue: "ヒント";
93
+ readonly filled: "入力済み";
94
+ readonly checked: "ここまでの領域はルールに合っています。";
95
+ readonly wrong: "大きすぎる領域か、変更されたヒントがあります。";
96
+ readonly remaining: "空きマスを埋めましょう。";
97
+ readonly solved: "すべての領域の大きさが合いました。完成!";
98
+ readonly helped: "ヒントを使って完成!";
99
+ readonly hintWhy: "唯一の完成形から、このマスが決まります。";
100
+ readonly noHint: "現在の入力から確かなヒントを出せません。";
101
+ readonly restored: "続きから再開しました。";
102
+ };
@@ -0,0 +1,13 @@
1
+ export const FILLOMINO_STRINGS = {
2
+ en: {
3
+ title: "Fillomino", help: "Fill every cell with a number. Connected regions of equal numbers must have exactly that many cells. Regions of the same size cannot touch. Arrow keys move; type a number to fill a cell. Clear removes your entry.",
4
+ undo: "Undo", hint: "Hint", check: "Check", restart: "Restart", selectNumber: "Number", fill: "Enter", clear: "Clear", selected: "Selected", row: "Row", column: "column", empty: "empty", clue: "given", filled: "filled", checked: "The filled regions follow the rules so far.", wrong: "Some regions are too large or a clue was changed.", remaining: "Keep filling the empty cells.", solved: "Every region has the right size. Solved!", helped: "Solved with a hint.", hintWhy: "This cell follows from the only complete solution.", noHint: "No proved hint is available from the current entries.", restored: "Progress restored.",
5
+ },
6
+ ja: {
7
+ title: "フィロミノ", help: "すべてのマスに数字を入れます。同じ数字が上下左右につながった領域のマス数は、その数字と同じです。同じ大きさの領域は辺で接しません。矢印キーで移動し、数字を入力します。消去で入力を消します。",
8
+ undo: "戻す", hint: "ヒント", check: "確かめる", restart: "やり直す", selectNumber: "数字", fill: "入力", clear: "消去", selected: "選択中", row: "行", column: "列", empty: "空き", clue: "ヒント", filled: "入力済み", checked: "ここまでの領域はルールに合っています。", wrong: "大きすぎる領域か、変更されたヒントがあります。", remaining: "空きマスを埋めましょう。", solved: "すべての領域の大きさが合いました。完成!", helped: "ヒントを使って完成!", hintWhy: "唯一の完成形から、このマスが決まります。", noHint: "現在の入力から確かなヒントを出せません。", restored: "続きから再開しました。",
9
+ },
10
+ };
11
+ export function fillominoWords(language = "en") {
12
+ return FILLOMINO_STRINGS[language];
13
+ }
@@ -0,0 +1 @@
1
+ export declare const FILLOMINO_PLAY_STYLE = "\n.fillomino-play{font:1rem/1.45 system-ui,sans-serif;color:var(--kz-ink,#202521);max-width:58rem;margin:auto}\n.fillomino-status{min-height:1.5em;text-align:center;font-weight:600}\n.fillomino-board{width:min(100%,42rem);margin:1rem auto}\n.fillomino-drawing{position:relative}\n.fillomino-cells{position:absolute;inset:0;display:grid;grid-template-columns:repeat(var(--fillomino-width),1fr);grid-template-rows:repeat(var(--fillomino-height),1fr)}\n.fillomino-cell{min-width:0;min-height:0;border:0;background:transparent;color:transparent;cursor:pointer}\n.fillomino-cell:focus-visible{outline:3px solid #b5963d;outline-offset:-3px}\n.fillomino-controls{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:.5rem;margin:1rem auto}\n.fillomino-controls button,.fillomino-controls select{font:inherit;min-height:2.75rem;padding:.45rem .8rem;border:1px solid var(--kz-grid,#cfc6b2);border-radius:.55rem;background:var(--kz-paper,#fbf8f1);color:inherit}\n.fillomino-number-label{display:flex;align-items:center;gap:.45rem}\n@media(max-width:700px){.fillomino-controls{gap:.35rem}.fillomino-controls button{flex:1 1 28%}}\n";
@@ -0,0 +1,13 @@
1
+ export const FILLOMINO_PLAY_STYLE = `
2
+ .fillomino-play{font:1rem/1.45 system-ui,sans-serif;color:var(--kz-ink,#202521);max-width:58rem;margin:auto}
3
+ .fillomino-status{min-height:1.5em;text-align:center;font-weight:600}
4
+ .fillomino-board{width:min(100%,42rem);margin:1rem auto}
5
+ .fillomino-drawing{position:relative}
6
+ .fillomino-cells{position:absolute;inset:0;display:grid;grid-template-columns:repeat(var(--fillomino-width),1fr);grid-template-rows:repeat(var(--fillomino-height),1fr)}
7
+ .fillomino-cell{min-width:0;min-height:0;border:0;background:transparent;color:transparent;cursor:pointer}
8
+ .fillomino-cell:focus-visible{outline:3px solid #b5963d;outline-offset:-3px}
9
+ .fillomino-controls{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:.5rem;margin:1rem auto}
10
+ .fillomino-controls button,.fillomino-controls select{font:inherit;min-height:2.75rem;padding:.45rem .8rem;border:1px solid var(--kz-grid,#cfc6b2);border-radius:.55rem;background:var(--kz-paper,#fbf8f1);color:inherit}
11
+ .fillomino-number-label{display:flex;align-items:center;gap:.45rem}
12
+ @media(max-width:700px){.fillomino-controls{gap:.35rem}.fillomino-controls button{flex:1 1 28%}}
13
+ `;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import { generateFillomino } from "./fillominoGenerate.js";
2
+ self.onmessage = (event) => {
3
+ try {
4
+ const { width, height, level, seed } = event.data;
5
+ self.postMessage({ puzzle: generateFillomino(width, height, level, seed) });
6
+ }
7
+ catch (error) {
8
+ self.postMessage({ error: error instanceof Error ? error.message : String(error) });
9
+ }
10
+ };
@@ -0,0 +1,2 @@
1
+ export { drawHeyawake } from "./heyawakeDraw.ts";
2
+ export type { HeyawakeDrawOptions } from "./heyawakePlay.types.ts";
@@ -0,0 +1 @@
1
+ export { drawHeyawake } from "./heyawakeDraw.js";
@@ -0,0 +1,13 @@
1
+ /** Heyawake has its own room model alongside Kazu's number puzzles. */
2
+ export * from "./heyawake.types.ts";
3
+ export * from "./heyawake.constants.ts";
4
+ export * from "./heyawakeBoard.ts";
5
+ export * from "./heyawakeSolve.ts";
6
+ export * from "./heyawakeGenerate.ts";
7
+ export * from "./heyawakeGame.ts";
8
+ export { drawHeyawake } from "./heyawakeDraw.ts";
9
+ export type { HeyawakeDrawOptions } from "./heyawakePlay.types.ts";
10
+ export { mountHeyawake } from "./heyawakeMount.ts";
11
+ export { HEYAWAKE_PLAY_STYLE } from "./heyawakeStyle.ts";
12
+ export { heyawakeWords } from "./heyawakeStrings.ts";
13
+ export type * from "./heyawakePlay.types.ts";