@johnmorrisdotca/kazu 1.0.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 (355) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +429 -5
  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/draw-entry.d.ts +1 -0
  32. package/dist/draw-entry.js +1 -0
  33. package/dist/fillomino-draw-entry.d.ts +2 -0
  34. package/dist/fillomino-draw-entry.js +1 -0
  35. package/dist/fillomino-entry.d.ts +8 -0
  36. package/dist/fillomino-entry.js +7 -0
  37. package/dist/fillomino-play-entry.d.ts +4 -0
  38. package/dist/fillomino-play-entry.js +3 -0
  39. package/dist/fillomino.constants.d.ts +5 -0
  40. package/dist/fillomino.constants.js +5 -0
  41. package/dist/fillomino.types.d.ts +30 -0
  42. package/dist/fillomino.types.js +1 -0
  43. package/dist/fillominoBoard.d.ts +6 -0
  44. package/dist/fillominoBoard.js +84 -0
  45. package/dist/fillominoDraw.d.ts +3 -0
  46. package/dist/fillominoDraw.js +31 -0
  47. package/dist/fillominoGame.d.ts +13 -0
  48. package/dist/fillominoGame.js +65 -0
  49. package/dist/fillominoGenerate.d.ts +3 -0
  50. package/dist/fillominoGenerate.js +125 -0
  51. package/dist/fillominoMount.d.ts +2 -0
  52. package/dist/fillominoMount.js +211 -0
  53. package/dist/fillominoPlay.types.d.ts +25 -0
  54. package/dist/fillominoPlay.types.js +1 -0
  55. package/dist/fillominoSolve.d.ts +6 -0
  56. package/dist/fillominoSolve.js +111 -0
  57. package/dist/fillominoStrings.d.ts +102 -0
  58. package/dist/fillominoStrings.js +13 -0
  59. package/dist/fillominoStyle.d.ts +1 -0
  60. package/dist/fillominoStyle.js +13 -0
  61. package/dist/fillominoWorker.d.ts +1 -0
  62. package/dist/fillominoWorker.js +10 -0
  63. package/dist/heyawake-draw-entry.d.ts +2 -0
  64. package/dist/heyawake-draw-entry.js +1 -0
  65. package/dist/heyawake-entry.d.ts +13 -0
  66. package/dist/heyawake-entry.js +11 -0
  67. package/dist/heyawake-play-entry.d.ts +2 -0
  68. package/dist/heyawake-play-entry.js +1 -0
  69. package/dist/heyawake.constants.d.ts +5 -0
  70. package/dist/heyawake.constants.js +5 -0
  71. package/dist/heyawake.types.d.ts +35 -0
  72. package/dist/heyawake.types.js +1 -0
  73. package/dist/heyawakeBoard.d.ts +7 -0
  74. package/dist/heyawakeBoard.js +116 -0
  75. package/dist/heyawakeDraw.d.ts +3 -0
  76. package/dist/heyawakeDraw.js +27 -0
  77. package/dist/heyawakeGame.d.ts +12 -0
  78. package/dist/heyawakeGame.js +51 -0
  79. package/dist/heyawakeGenerate.d.ts +2 -0
  80. package/dist/heyawakeGenerate.js +114 -0
  81. package/dist/heyawakeMount.d.ts +2 -0
  82. package/dist/heyawakeMount.js +106 -0
  83. package/dist/heyawakePlay.types.d.ts +24 -0
  84. package/dist/heyawakePlay.types.js +1 -0
  85. package/dist/heyawakeSolve.d.ts +5 -0
  86. package/dist/heyawakeSolve.js +52 -0
  87. package/dist/heyawakeStrings.d.ts +19 -0
  88. package/dist/heyawakeStrings.js +7 -0
  89. package/dist/heyawakeStyle.d.ts +1 -0
  90. package/dist/heyawakeStyle.js +2 -0
  91. package/dist/heyawakeWorker.d.ts +1 -0
  92. package/dist/heyawakeWorker.js +10 -0
  93. package/dist/hitori-draw-entry.d.ts +2 -0
  94. package/dist/hitori-draw-entry.js +1 -0
  95. package/dist/hitori-entry.d.ts +6 -0
  96. package/dist/hitori-entry.js +6 -0
  97. package/dist/hitori-play-entry.d.ts +4 -0
  98. package/dist/hitori-play-entry.js +3 -0
  99. package/dist/hitori.constants.d.ts +2 -0
  100. package/dist/hitori.constants.js +2 -0
  101. package/dist/hitori.types.d.ts +20 -0
  102. package/dist/hitori.types.js +1 -0
  103. package/dist/hitoriBoard.d.ts +8 -0
  104. package/dist/hitoriBoard.js +72 -0
  105. package/dist/hitoriDraw.d.ts +4 -0
  106. package/dist/hitoriDraw.js +25 -0
  107. package/dist/hitoriGame.d.ts +10 -0
  108. package/dist/hitoriGame.js +48 -0
  109. package/dist/hitoriGenerate.d.ts +3 -0
  110. package/dist/hitoriGenerate.js +163 -0
  111. package/dist/hitoriMount.d.ts +3 -0
  112. package/dist/hitoriMount.js +111 -0
  113. package/dist/hitoriPlay.types.d.ts +25 -0
  114. package/dist/hitoriPlay.types.js +1 -0
  115. package/dist/hitoriSolve.d.ts +6 -0
  116. package/dist/hitoriSolve.js +69 -0
  117. package/dist/hitoriStrings.d.ts +70 -0
  118. package/dist/hitoriStrings.js +17 -0
  119. package/dist/hitoriStyle.d.ts +1 -0
  120. package/dist/hitoriStyle.js +9 -0
  121. package/dist/index.d.ts +12 -0
  122. package/dist/index.js +12 -0
  123. package/dist/juosan-draw-entry.d.ts +2 -0
  124. package/dist/juosan-draw-entry.js +1 -0
  125. package/dist/juosan-entry.d.ts +11 -0
  126. package/dist/juosan-entry.js +10 -0
  127. package/dist/juosan-play-entry.d.ts +4 -0
  128. package/dist/juosan-play-entry.js +3 -0
  129. package/dist/juosan.constants.d.ts +4 -0
  130. package/dist/juosan.constants.js +4 -0
  131. package/dist/juosan.types.d.ts +33 -0
  132. package/dist/juosan.types.js +1 -0
  133. package/dist/juosanBoard.d.ts +7 -0
  134. package/dist/juosanBoard.js +102 -0
  135. package/dist/juosanDraw.d.ts +4 -0
  136. package/dist/juosanDraw.js +49 -0
  137. package/dist/juosanGame.d.ts +20 -0
  138. package/dist/juosanGame.js +86 -0
  139. package/dist/juosanGenerate.d.ts +3 -0
  140. package/dist/juosanGenerate.js +31 -0
  141. package/dist/juosanMount.d.ts +3 -0
  142. package/dist/juosanMount.js +206 -0
  143. package/dist/juosanPlay.types.d.ts +24 -0
  144. package/dist/juosanPlay.types.js +1 -0
  145. package/dist/juosanSolve.d.ts +3 -0
  146. package/dist/juosanSolve.js +82 -0
  147. package/dist/juosanStrings.d.ts +77 -0
  148. package/dist/juosanStrings.js +43 -0
  149. package/dist/juosanStyle.d.ts +1 -0
  150. package/dist/juosanStyle.js +1 -0
  151. package/dist/kakuro-draw-entry.d.ts +2 -0
  152. package/dist/kakuro-draw-entry.js +1 -0
  153. package/dist/kakuro-entry.d.ts +12 -0
  154. package/dist/kakuro-entry.js +10 -0
  155. package/dist/kakuro-play-entry.d.ts +4 -0
  156. package/dist/kakuro-play-entry.js +3 -0
  157. package/dist/kakuro.constants.d.ts +6 -0
  158. package/dist/kakuro.constants.js +6 -0
  159. package/dist/kakuro.types.d.ts +46 -0
  160. package/dist/kakuro.types.js +1 -0
  161. package/dist/kakuroBoard.d.ts +8 -0
  162. package/dist/kakuroBoard.js +117 -0
  163. package/dist/kakuroDraw.d.ts +10 -0
  164. package/dist/kakuroDraw.js +21 -0
  165. package/dist/kakuroGame.d.ts +12 -0
  166. package/dist/kakuroGame.js +74 -0
  167. package/dist/kakuroGenerate.d.ts +3 -0
  168. package/dist/kakuroGenerate.js +130 -0
  169. package/dist/kakuroMount.d.ts +2 -0
  170. package/dist/kakuroMount.js +109 -0
  171. package/dist/kakuroPlay.types.d.ts +20 -0
  172. package/dist/kakuroPlay.types.js +1 -0
  173. package/dist/kakuroSolve.d.ts +6 -0
  174. package/dist/kakuroSolve.js +85 -0
  175. package/dist/kakuroStrings.d.ts +82 -0
  176. package/dist/kakuroStrings.js +5 -0
  177. package/dist/kakuroStyle.d.ts +1 -0
  178. package/dist/kakuroStyle.js +1 -0
  179. package/dist/masyu-draw-entry.d.ts +2 -0
  180. package/dist/masyu-draw-entry.js +1 -0
  181. package/dist/masyu-entry.d.ts +6 -0
  182. package/dist/masyu-entry.js +6 -0
  183. package/dist/masyu-play-entry.d.ts +4 -0
  184. package/dist/masyu-play-entry.js +3 -0
  185. package/dist/masyu.constants.d.ts +2 -0
  186. package/dist/masyu.constants.js +2 -0
  187. package/dist/masyu.types.d.ts +46 -0
  188. package/dist/masyu.types.js +1 -0
  189. package/dist/masyuBoard.d.ts +10 -0
  190. package/dist/masyuBoard.js +88 -0
  191. package/dist/masyuDraw.d.ts +2 -0
  192. package/dist/masyuDraw.js +33 -0
  193. package/dist/masyuGame.d.ts +9 -0
  194. package/dist/masyuGame.js +46 -0
  195. package/dist/masyuGenerate.d.ts +3 -0
  196. package/dist/masyuGenerate.js +56 -0
  197. package/dist/masyuMount.d.ts +2 -0
  198. package/dist/masyuMount.js +118 -0
  199. package/dist/masyuPlay.types.d.ts +1 -0
  200. package/dist/masyuPlay.types.js +1 -0
  201. package/dist/masyuSolve.d.ts +6 -0
  202. package/dist/masyuSolve.js +88 -0
  203. package/dist/masyuStrings.d.ts +28 -0
  204. package/dist/masyuStrings.js +4 -0
  205. package/dist/masyuStyle.d.ts +1 -0
  206. package/dist/masyuStyle.js +11 -0
  207. package/dist/nurikabe-draw-entry.d.ts +2 -0
  208. package/dist/nurikabe-draw-entry.js +1 -0
  209. package/dist/nurikabe-entry.d.ts +6 -0
  210. package/dist/nurikabe-entry.js +6 -0
  211. package/dist/nurikabe-play-entry.d.ts +4 -0
  212. package/dist/nurikabe-play-entry.js +3 -0
  213. package/dist/nurikabe.constants.d.ts +2 -0
  214. package/dist/nurikabe.constants.js +2 -0
  215. package/dist/nurikabe.types.d.ts +20 -0
  216. package/dist/nurikabe.types.js +1 -0
  217. package/dist/nurikabeBoard.d.ts +8 -0
  218. package/dist/nurikabeBoard.js +72 -0
  219. package/dist/nurikabeDraw.d.ts +4 -0
  220. package/dist/nurikabeDraw.js +15 -0
  221. package/dist/nurikabeGame.d.ts +8 -0
  222. package/dist/nurikabeGame.js +40 -0
  223. package/dist/nurikabeGenerate.d.ts +3 -0
  224. package/dist/nurikabeGenerate.js +31 -0
  225. package/dist/nurikabeMount.d.ts +3 -0
  226. package/dist/nurikabeMount.js +99 -0
  227. package/dist/nurikabePlay.types.d.ts +25 -0
  228. package/dist/nurikabePlay.types.js +1 -0
  229. package/dist/nurikabeSolve.d.ts +6 -0
  230. package/dist/nurikabeSolve.js +75 -0
  231. package/dist/nurikabeStrings.d.ts +70 -0
  232. package/dist/nurikabeStrings.js +5 -0
  233. package/dist/nurikabeStyle.d.ts +1 -0
  234. package/dist/nurikabeStyle.js +9 -0
  235. package/dist/play-entry.d.ts +1 -0
  236. package/dist/play-entry.js +1 -0
  237. package/dist/ripple-draw-entry.d.ts +2 -0
  238. package/dist/ripple-draw-entry.js +1 -0
  239. package/dist/ripple-entry.d.ts +12 -0
  240. package/dist/ripple-entry.js +11 -0
  241. package/dist/ripple-play-entry.d.ts +4 -0
  242. package/dist/ripple-play-entry.js +3 -0
  243. package/dist/ripple.constants.d.ts +4 -0
  244. package/dist/ripple.constants.js +4 -0
  245. package/dist/ripple.types.d.ts +34 -0
  246. package/dist/ripple.types.js +1 -0
  247. package/dist/rippleBoard.d.ts +7 -0
  248. package/dist/rippleBoard.js +99 -0
  249. package/dist/rippleDraw.d.ts +3 -0
  250. package/dist/rippleDraw.js +32 -0
  251. package/dist/rippleGame.d.ts +13 -0
  252. package/dist/rippleGame.js +85 -0
  253. package/dist/rippleGenerate.d.ts +3 -0
  254. package/dist/rippleGenerate.js +57 -0
  255. package/dist/rippleMount.d.ts +2 -0
  256. package/dist/rippleMount.js +179 -0
  257. package/dist/ripplePlay.types.d.ts +23 -0
  258. package/dist/ripplePlay.types.js +1 -0
  259. package/dist/rippleSolve.d.ts +6 -0
  260. package/dist/rippleSolve.js +83 -0
  261. package/dist/rippleStrings.d.ts +86 -0
  262. package/dist/rippleStrings.js +5 -0
  263. package/dist/rippleStyle.d.ts +1 -0
  264. package/dist/rippleStyle.js +1 -0
  265. package/dist/shikaku-draw-entry.d.ts +2 -0
  266. package/dist/shikaku-draw-entry.js +1 -0
  267. package/dist/shikaku-entry.d.ts +8 -0
  268. package/dist/shikaku-entry.js +8 -0
  269. package/dist/shikaku-play-entry.d.ts +4 -0
  270. package/dist/shikaku-play-entry.js +3 -0
  271. package/dist/shikaku.constants.d.ts +5 -0
  272. package/dist/shikaku.constants.js +5 -0
  273. package/dist/shikaku.types.d.ts +36 -0
  274. package/dist/shikaku.types.js +1 -0
  275. package/dist/shikakuBoard.d.ts +9 -0
  276. package/dist/shikakuBoard.js +59 -0
  277. package/dist/shikakuDraw.d.ts +4 -0
  278. package/dist/shikakuDraw.js +23 -0
  279. package/dist/shikakuGame.d.ts +14 -0
  280. package/dist/shikakuGame.js +61 -0
  281. package/dist/shikakuGenerate.d.ts +3 -0
  282. package/dist/shikakuGenerate.js +43 -0
  283. package/dist/shikakuMount.d.ts +3 -0
  284. package/dist/shikakuMount.js +143 -0
  285. package/dist/shikakuPacks.d.ts +27 -0
  286. package/dist/shikakuPacks.js +26 -0
  287. package/dist/shikakuPlay.types.d.ts +26 -0
  288. package/dist/shikakuPlay.types.js +1 -0
  289. package/dist/shikakuSolve.d.ts +6 -0
  290. package/dist/shikakuSolve.js +54 -0
  291. package/dist/shikakuStrings.d.ts +102 -0
  292. package/dist/shikakuStrings.js +5 -0
  293. package/dist/shikakuStyle.d.ts +1 -0
  294. package/dist/shikakuStyle.js +12 -0
  295. package/dist/shikakuWorker.d.ts +1 -0
  296. package/dist/shikakuWorker.js +10 -0
  297. package/dist/slitherlink-draw-entry.d.ts +2 -0
  298. package/dist/slitherlink-draw-entry.js +1 -0
  299. package/dist/slitherlink-entry.d.ts +11 -0
  300. package/dist/slitherlink-entry.js +9 -0
  301. package/dist/slitherlink-play-entry.d.ts +4 -0
  302. package/dist/slitherlink-play-entry.js +3 -0
  303. package/dist/slitherlink.constants.d.ts +2 -0
  304. package/dist/slitherlink.constants.js +2 -0
  305. package/dist/slitherlink.types.d.ts +34 -0
  306. package/dist/slitherlink.types.js +1 -0
  307. package/dist/slitherlinkBoard.d.ts +12 -0
  308. package/dist/slitherlinkBoard.js +132 -0
  309. package/dist/slitherlinkDraw.d.ts +4 -0
  310. package/dist/slitherlinkDraw.js +51 -0
  311. package/dist/slitherlinkGame.d.ts +8 -0
  312. package/dist/slitherlinkGame.js +63 -0
  313. package/dist/slitherlinkGenerate.d.ts +3 -0
  314. package/dist/slitherlinkGenerate.js +101 -0
  315. package/dist/slitherlinkMount.d.ts +3 -0
  316. package/dist/slitherlinkMount.js +267 -0
  317. package/dist/slitherlinkPlay.types.d.ts +23 -0
  318. package/dist/slitherlinkPlay.types.js +1 -0
  319. package/dist/slitherlinkSolve.d.ts +6 -0
  320. package/dist/slitherlinkSolve.js +127 -0
  321. package/dist/slitherlinkStrings.d.ts +86 -0
  322. package/dist/slitherlinkStrings.js +47 -0
  323. package/dist/slitherlinkStyle.d.ts +1 -0
  324. package/dist/slitherlinkStyle.js +18 -0
  325. package/dist/version.d.ts +1 -1
  326. package/dist/version.js +1 -1
  327. package/dist/yajilin-draw-entry.d.ts +2 -0
  328. package/dist/yajilin-draw-entry.js +1 -0
  329. package/dist/yajilin-entry.d.ts +6 -0
  330. package/dist/yajilin-entry.js +6 -0
  331. package/dist/yajilin-play-entry.d.ts +4 -0
  332. package/dist/yajilin-play-entry.js +3 -0
  333. package/dist/yajilin.constants.d.ts +2 -0
  334. package/dist/yajilin.constants.js +2 -0
  335. package/dist/yajilin.types.d.ts +63 -0
  336. package/dist/yajilin.types.js +1 -0
  337. package/dist/yajilinBoard.d.ts +9 -0
  338. package/dist/yajilinBoard.js +91 -0
  339. package/dist/yajilinDraw.d.ts +2 -0
  340. package/dist/yajilinDraw.js +33 -0
  341. package/dist/yajilinGame.d.ts +15 -0
  342. package/dist/yajilinGame.js +58 -0
  343. package/dist/yajilinGenerate.d.ts +3 -0
  344. package/dist/yajilinGenerate.js +224 -0
  345. package/dist/yajilinMount.d.ts +2 -0
  346. package/dist/yajilinMount.js +162 -0
  347. package/dist/yajilinPlay.types.d.ts +1 -0
  348. package/dist/yajilinPlay.types.js +1 -0
  349. package/dist/yajilinSolve.d.ts +6 -0
  350. package/dist/yajilinSolve.js +82 -0
  351. package/dist/yajilinStrings.d.ts +32 -0
  352. package/dist/yajilinStrings.js +4 -0
  353. package/dist/yajilinStyle.d.ts +1 -0
  354. package/dist/yajilinStyle.js +12 -0
  355. package/package.json +196 -3
@@ -0,0 +1,2 @@
1
+ export const HEYAWAKE_PLAY_STYLE = `
2
+ .kh-root{max-width:40rem;margin:auto;font:inherit;color:inherit}.kh-board{position:relative;max-width:min(92vw,36rem);margin:0 auto}.kh-board svg{display:block}.kh-grid{position:absolute;inset:0;display:grid;touch-action:manipulation}.kh-cell{position:relative;aspect-ratio:1;border:0;background:transparent;color:inherit;font:inherit;padding:0;cursor:pointer}.kh-cell:focus-visible{outline:3px solid var(--kz-select,#ffd45b);outline-offset:-3px;z-index:1}.kh-cell[aria-pressed=true]{font-weight:700}.kh-tools{display:flex;flex-wrap:wrap;justify-content:center;gap:.5rem;margin:.9rem 0}.kh-tools button{min-height:2.75rem;padding:.45rem .8rem;border:1px solid currentColor;border-radius:.5rem;background:transparent;color:inherit;font:inherit}.kh-status,.kh-help{text-align:center;min-height:1.5em}.kh-errors{color:var(--kz-bad,#b5452c)}`;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import { generateHeyawake } from "./heyawakeGenerate.js";
2
+ self.onmessage = (event) => {
3
+ try {
4
+ const { width, height, level, seed } = event.data;
5
+ self.postMessage({ puzzle: generateHeyawake(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 { drawHitori } from "./hitoriDraw.ts";
2
+ export type { HitoriDrawOptions } from "./hitoriPlay.types.ts";
@@ -0,0 +1 @@
1
+ export { drawHitori } from "./hitoriDraw.js";
@@ -0,0 +1,6 @@
1
+ export * from "./hitori.types.ts";
2
+ export * from "./hitori.constants.ts";
3
+ export * from "./hitoriBoard.ts";
4
+ export * from "./hitoriSolve.ts";
5
+ export * from "./hitoriGenerate.ts";
6
+ export * from "./hitoriGame.ts";
@@ -0,0 +1,6 @@
1
+ export * from "./hitori.types.js";
2
+ export * from "./hitori.constants.js";
3
+ export * from "./hitoriBoard.js";
4
+ export * from "./hitoriSolve.js";
5
+ export * from "./hitoriGenerate.js";
6
+ export * from "./hitoriGame.js";
@@ -0,0 +1,4 @@
1
+ export { mountHitori } from "./hitoriMount.ts";
2
+ export { HITORI_PLAY_STYLE } from "./hitoriStyle.ts";
3
+ export { HITORI_STRINGS } from "./hitoriStrings.ts";
4
+ export type * from "./hitoriPlay.types.ts";
@@ -0,0 +1,3 @@
1
+ export { mountHitori } from "./hitoriMount.js";
2
+ export { HITORI_PLAY_STYLE } from "./hitoriStyle.js";
3
+ export { HITORI_STRINGS } from "./hitoriStrings.js";
@@ -0,0 +1,2 @@
1
+ export declare const HITORI_MAX_NODES = 300000;
2
+ export declare const HITORI_SIZES: readonly [5, 7];
@@ -0,0 +1,2 @@
1
+ export const HITORI_MAX_NODES = 300000;
2
+ export const HITORI_SIZES = [5, 7];
@@ -0,0 +1,20 @@
1
+ export type HitoriBoard = {
2
+ size: 5 | 7;
3
+ numbers: readonly number[];
4
+ };
5
+ export type HitoriPuzzle = HitoriBoard & {
6
+ seed: number;
7
+ solution: readonly boolean[];
8
+ };
9
+ export type HitoriSolve = {
10
+ count: number;
11
+ solution: readonly boolean[] | null;
12
+ complete: boolean;
13
+ nodes: number;
14
+ };
15
+ export type HitoriGame = {
16
+ board: HitoriBoard;
17
+ shaded: readonly boolean[];
18
+ history: readonly (readonly boolean[])[];
19
+ helped: boolean;
20
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ import type { HitoriBoard } from "./hitori.types.ts";
2
+ export declare function isHitoriBoard(board: HitoriBoard): boolean;
3
+ export declare function hitoriNeighbors(size: number, cell: number): number[];
4
+ /** Checks unshaded row and column duplicates, adjacent shades, and white connectivity. */
5
+ export declare function checkHitori(board: HitoriBoard, shaded: readonly boolean[]): {
6
+ ok: boolean;
7
+ errors: readonly number[];
8
+ };
@@ -0,0 +1,72 @@
1
+ export function isHitoriBoard(board) {
2
+ return !!board && (board.size === 5 || board.size === 7)
3
+ && Array.isArray(board.numbers)
4
+ && board.numbers.length === board.size * board.size
5
+ && board.numbers.every(value => Number.isInteger(value) && value >= 1 && value <= board.size);
6
+ }
7
+ export function hitoriNeighbors(size, cell) {
8
+ const x = cell % size, y = Math.floor(cell / size), neighbors = [];
9
+ if (x > 0)
10
+ neighbors.push(cell - 1);
11
+ if (x < size - 1)
12
+ neighbors.push(cell + 1);
13
+ if (y > 0)
14
+ neighbors.push(cell - size);
15
+ if (y < size - 1)
16
+ neighbors.push(cell + size);
17
+ return neighbors;
18
+ }
19
+ /** Checks unshaded row and column duplicates, adjacent shades, and white connectivity. */
20
+ export function checkHitori(board, shaded) {
21
+ if (!isHitoriBoard(board))
22
+ return { ok: false, errors: [] };
23
+ const size = board.size, total = size * size;
24
+ if (shaded.length !== total || !shaded.every(value => typeof value === "boolean"))
25
+ return { ok: false, errors: [] };
26
+ const errors = new Set();
27
+ for (let y = 0; y < size; y += 1)
28
+ for (let x = 0; x < size; x += 1) {
29
+ const cell = y * size + x;
30
+ if (shaded[cell])
31
+ continue;
32
+ for (let otherX = x + 1; otherX < size; otherX += 1) {
33
+ const other = y * size + otherX;
34
+ if (!shaded[other] && board.numbers[cell] === board.numbers[other]) {
35
+ errors.add(cell);
36
+ errors.add(other);
37
+ }
38
+ }
39
+ for (let otherY = y + 1; otherY < size; otherY += 1) {
40
+ const other = otherY * size + x;
41
+ if (!shaded[other] && board.numbers[cell] === board.numbers[other]) {
42
+ errors.add(cell);
43
+ errors.add(other);
44
+ }
45
+ }
46
+ }
47
+ for (let cell = 0; cell < total; cell += 1)
48
+ if (shaded[cell]) {
49
+ for (const neighbor of hitoriNeighbors(size, cell))
50
+ if (shaded[neighbor]) {
51
+ errors.add(cell);
52
+ errors.add(neighbor);
53
+ }
54
+ }
55
+ const white = shaded.map((isShaded, cell) => isShaded ? -1 : cell).filter(cell => cell >= 0), reached = new Set();
56
+ if (white.length) {
57
+ const stack = [white[0]];
58
+ reached.add(white[0]);
59
+ while (stack.length)
60
+ for (const neighbor of hitoriNeighbors(size, stack.pop())) {
61
+ if (!shaded[neighbor] && !reached.has(neighbor)) {
62
+ reached.add(neighbor);
63
+ stack.push(neighbor);
64
+ }
65
+ }
66
+ }
67
+ if (reached.size !== white.length)
68
+ for (const cell of white)
69
+ if (!reached.has(cell))
70
+ errors.add(cell);
71
+ return { ok: errors.size === 0, errors: [...errors].sort((a, b) => a - b) };
72
+ }
@@ -0,0 +1,4 @@
1
+ import type { HitoriBoard } from "./hitori.types.ts";
2
+ import type { HitoriDrawOptions } from "./hitoriPlay.types.ts";
3
+ /** SVG text contains only the public numbers and the player's marks. */
4
+ export declare function drawHitori(board: HitoriBoard, options?: HitoriDrawOptions): string;
@@ -0,0 +1,25 @@
1
+ import { isHitoriBoard } from "./hitoriBoard.js";
2
+ /** SVG text contains only the public numbers and the player's marks. */
3
+ export function drawHitori(board, options = {}) {
4
+ if (!isHitoriBoard(board))
5
+ throw new RangeError("Invalid Hitori board");
6
+ const size = board.size, cellSize = 52;
7
+ const palettes = {
8
+ ivory: ["var(--kz-paper,#fbf8f1)", "var(--kz-ink,#1f2320)", "var(--kz-grid,#cfc6b2)"],
9
+ wood: ["#e5cda6", "#493e2f", "#bca582"],
10
+ slate: ["#333a42", "#f1eee7", "#707984"],
11
+ };
12
+ const [paper, ink, grid] = palettes[options.material ?? "ivory"], shaded = options.shaded ?? [];
13
+ const cells = board.numbers.map((number, cell) => {
14
+ const x = cell % size * cellSize, y = Math.floor(cell / size) * cellSize;
15
+ const black = shaded[cell], selected = options.selected === cell, error = options.errors?.includes(cell);
16
+ const tile = options.pieces === "tiles" && !black
17
+ ? `<rect x="${x + 10}" y="${y + 10}" width="32" height="32" rx="6" fill="${paper}" stroke="${grid}"/>`
18
+ : "";
19
+ const background = black ? ink : selected ? `${ink}22` : paper;
20
+ const foreground = black ? paper : error ? "#b5452c" : ink;
21
+ return `<g><rect x="${x}" y="${y}" width="${cellSize}" height="${cellSize}" fill="${background}" stroke="${grid}" stroke-width="1"/>${tile}<text x="${x + cellSize / 2}" y="${y + cellSize / 2}" text-anchor="middle" dominant-baseline="middle" fill="${foreground}" font-size="22" font-weight="${selected ? 700 : 500}" font-family="system-ui,sans-serif">${number}</text></g>`;
22
+ }).join("");
23
+ const width = size * cellSize;
24
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 ${width + 4} ${width + 4}" role="img" aria-label="Hitori" style="display:block;width:100%;height:auto;background:${paper}">${cells}<rect width="${width}" height="${width}" fill="none" stroke="var(--kz-frame,#a98954)" stroke-width="2"/></svg>`;
25
+ }
@@ -0,0 +1,10 @@
1
+ import type { HitoriBoard, HitoriGame } from "./hitori.types.ts";
2
+ export declare function newHitori(board: HitoriBoard): HitoriGame;
3
+ export declare function shadeHitori(game: HitoriGame, cell: number): HitoriGame;
4
+ export declare function undoHitori(game: HitoriGame): HitoriGame;
5
+ export declare function restartHitori(game: HitoriGame): HitoriGame;
6
+ /** Gives one next shade in the unique minimal solution, if uniqueness was proved. */
7
+ export declare function hintHitori(game: HitoriGame): number | null;
8
+ /** Versioned public clue and mark data; the generated answer is not saved. */
9
+ export declare function encodeHitori(game: HitoriGame): string;
10
+ export declare function decodeHitori(code: string): HitoriGame | null;
@@ -0,0 +1,48 @@
1
+ import { isHitoriBoard } from "./hitoriBoard.js";
2
+ import { solveHitori } from "./hitoriSolve.js";
3
+ export function newHitori(board) {
4
+ if (!isHitoriBoard(board))
5
+ throw new RangeError("Invalid Hitori board");
6
+ return { board: { size: board.size, numbers: [...board.numbers] }, shaded: Array(board.size ** 2).fill(false), history: [], helped: false };
7
+ }
8
+ export function shadeHitori(game, cell) {
9
+ if (!Number.isInteger(cell) || cell < 0 || cell >= game.shaded.length)
10
+ return game;
11
+ const shaded = [...game.shaded];
12
+ shaded[cell] = !shaded[cell];
13
+ return { ...game, shaded, history: [...game.history, [...game.shaded]] };
14
+ }
15
+ export function undoHitori(game) {
16
+ const prior = game.history.at(-1);
17
+ return prior ? { ...game, shaded: [...prior], history: game.history.slice(0, -1) } : game;
18
+ }
19
+ export function restartHitori(game) {
20
+ return { ...game, shaded: Array(game.shaded.length).fill(false), history: [], helped: false };
21
+ }
22
+ /** Gives one next shade in the unique minimal solution, if uniqueness was proved. */
23
+ export function hintHitori(game) {
24
+ const result = solveHitori(game.board);
25
+ if (!result.complete || result.count !== 1 || !result.solution)
26
+ return null;
27
+ const cell = result.solution.findIndex((isShaded, index) => isShaded !== game.shaded[index]);
28
+ return cell < 0 ? null : cell;
29
+ }
30
+ /** Versioned public clue and mark data; the generated answer is not saved. */
31
+ export function encodeHitori(game) {
32
+ return JSON.stringify({ version: 1, board: game.board, shaded: game.shaded, helped: game.helped });
33
+ }
34
+ export function decodeHitori(code) {
35
+ try {
36
+ if (code.length > 10000)
37
+ return null;
38
+ const value = JSON.parse(code);
39
+ if (value.version !== 1 || !isHitoriBoard(value.board) || !Array.isArray(value.shaded)
40
+ || value.shaded.length !== value.board.size ** 2 || !value.shaded.every((cell) => typeof cell === "boolean")
41
+ || typeof value.helped !== "boolean")
42
+ return null;
43
+ return { ...newHitori(value.board), shaded: [...value.shaded], helped: value.helped };
44
+ }
45
+ catch {
46
+ return null;
47
+ }
48
+ }
@@ -0,0 +1,3 @@
1
+ import type { HitoriPuzzle } from "./hitori.types.ts";
2
+ /** Seeded number relabelings and board symmetries of original layouts, accepted only after a uniqueness proof. */
3
+ export declare function generateHitori(size?: 5 | 7, seed?: number): HitoriPuzzle;
@@ -0,0 +1,163 @@
1
+ import { HITORI_MAX_NODES, HITORI_SIZES } from "./hitori.constants.js";
2
+ import { checkHitori } from "./hitoriBoard.js";
3
+ import { solveHitori } from "./hitoriSolve.js";
4
+ import { isKazuSeed, seededRandom, shuffled } from "./random.js";
5
+ const layouts = {
6
+ 5: [
7
+ {
8
+ numbers: [
9
+ 1, 3, 3, 1, 5, 2, 3,
10
+ 4, 5, 1, 3, 4, 5, 1,
11
+ 2, 4, 5, 1, 2, 3, 5,
12
+ 1, 2, 3, 4,
13
+ ],
14
+ shade: [
15
+ false, true, false, true, false, ...Array(20).fill(false),
16
+ ],
17
+ },
18
+ {
19
+ numbers: [
20
+ 1, 2, 3, 4, 5, 2, 3,
21
+ 4, 1, 1, 3, 4, 5, 1,
22
+ 2, 3, 5, 1, 4, 3, 5,
23
+ 1, 2, 3, 4,
24
+ ],
25
+ shade: [
26
+ false, false, false, false, false, false, false,
27
+ false, true, false, false, false, false, false,
28
+ false, true, false, false, true, false, false,
29
+ false, false, false, false,
30
+ ],
31
+ },
32
+ {
33
+ numbers: [
34
+ 1, 4, 3, 4, 1, 2, 3,
35
+ 4, 5, 1, 3, 4, 5, 1,
36
+ 2, 4, 5, 1, 2, 3, 5,
37
+ 1, 2, 3, 4,
38
+ ],
39
+ shade: [
40
+ false, true, false, false, true, false, false,
41
+ false, false, false, false, false, false, false,
42
+ false, false, false, false, false, false, false,
43
+ false, false, false, false,
44
+ ],
45
+ },
46
+ {
47
+ numbers: [
48
+ 1, 4, 3, 4, 5, 2, 3,
49
+ 2, 5, 1, 2, 4, 5, 1,
50
+ 2, 4, 5, 1, 2, 3, 5,
51
+ 5, 2, 3, 4,
52
+ ],
53
+ shade: [
54
+ false, true, false, false, false, false, false,
55
+ true, false, false, true, false, false, false,
56
+ false, false, false, false, false, false, false,
57
+ true, false, false, false,
58
+ ],
59
+ },
60
+ ],
61
+ 7: [
62
+ {
63
+ numbers: [
64
+ 1, 2, 3, 4, 2, 6, 7,
65
+ 2, 6, 4, 5, 6, 7, 1,
66
+ 3, 4, 5, 6, 1, 1, 1,
67
+ 4, 5, 6, 7, 1, 2, 3,
68
+ 5, 6, 7, 1, 2, 3, 4,
69
+ 6, 7, 1, 2, 3, 4, 5,
70
+ 7, 1, 2, 3, 4, 5, 6,
71
+ ],
72
+ shade: [
73
+ false, false, false, false, true, false, false,
74
+ false, true, false, false, false, false, false,
75
+ false, false, false, false, true, false, true,
76
+ ...Array(28).fill(false),
77
+ ],
78
+ },
79
+ {
80
+ numbers: [
81
+ 1, 2, 4, 4, 5, 7, 7,
82
+ 2, 3, 4, 5, 5, 7, 1,
83
+ 3, 4, 5, 6, 7, 1, 2,
84
+ 4, 5, 6, 7, 1, 2, 7,
85
+ 5, 6, 7, 1, 2, 3, 4,
86
+ 6, 2, 1, 2, 3, 4, 5,
87
+ 7, 1, 2, 3, 7, 5, 6,
88
+ ],
89
+ shade: [
90
+ false, false, true, false, false, true, false,
91
+ false, false, false, false, true, false, false,
92
+ false, false, false, false, false, false, false,
93
+ false, false, false, false, false, false, true,
94
+ false, false, false, false, false, false, false,
95
+ false, true, false, false, false, false, false,
96
+ false, false, false, false, true, false, false,
97
+ ],
98
+ },
99
+ {
100
+ numbers: [
101
+ 1, 2, 3, 4, 5, 6, 7,
102
+ 2, 3, 4, 5, 6, 7, 1,
103
+ 3, 4, 5, 6, 1, 1, 6,
104
+ 4, 6, 6, 6, 1, 2, 3,
105
+ 5, 6, 7, 1, 2, 3, 4,
106
+ 6, 7, 1, 2, 3, 4, 5,
107
+ 7, 1, 2, 3, 4, 5, 6,
108
+ ],
109
+ shade: [
110
+ false, false, false, false, false, false, false,
111
+ false, false, false, false, false, false, false,
112
+ false, false, false, false, true, false, true,
113
+ false, true, false, true, false, false, false,
114
+ false, false, false, false, false, false, false,
115
+ false, ...Array(13).fill(false),
116
+ ],
117
+ },
118
+ {
119
+ numbers: [
120
+ 1, 2, 3, 4, 6, 6, 7,
121
+ 2, 2, 4, 5, 6, 2, 1,
122
+ 3, 4, 5, 6, 7, 1, 2,
123
+ 4, 5, 6, 7, 1, 2, 3,
124
+ 5, 6, 7, 1, 2, 3, 4,
125
+ 6, 7, 1, 2, 3, 4, 5,
126
+ 7, 1, 2, 3, 6, 5, 6,
127
+ ],
128
+ shade: [
129
+ false, false, false, false, true, false, false,
130
+ false, true, false, false, false, true, false,
131
+ false, false, false, false, false, false, false,
132
+ false, false, false, false, false, false, false,
133
+ false, false, false, false, false, false, false,
134
+ false, false, false, false, false, false, false,
135
+ false, false, false, false, true, false, false,
136
+ ],
137
+ },
138
+ ],
139
+ };
140
+ /** Seeded number relabelings and board symmetries of original layouts, accepted only after a uniqueness proof. */
141
+ export function generateHitori(size = 5, seed = 1) {
142
+ if (!HITORI_SIZES.includes(size) || !isKazuSeed(seed))
143
+ throw new RangeError("Invalid Hitori settings");
144
+ const random = seededRandom(seed), templates = layouts[size];
145
+ const template = templates[Math.floor(random() * templates.length)];
146
+ const labels = shuffled(Array.from({ length: size }, (_, index) => index + 1), random);
147
+ const turns = Math.floor(random() * 4), reflected = random() < 0.5;
148
+ const numbers = Array(size * size), solution = Array(size * size);
149
+ for (let y = 0; y < size; y += 1)
150
+ for (let x = 0; x < size; x += 1) {
151
+ let tx = reflected ? size - 1 - x : x, ty = y;
152
+ for (let turn = 0; turn < turns; turn += 1)
153
+ [tx, ty] = [size - 1 - ty, tx];
154
+ const source = y * size + x, destination = ty * size + tx;
155
+ numbers[destination] = labels[template.numbers[source] - 1];
156
+ solution[destination] = template.shade[source];
157
+ }
158
+ const board = { size, numbers }, proof = solveHitori(board, { nodes: HITORI_MAX_NODES });
159
+ if (!checkHitori(board, solution).ok || !proof.complete || proof.count !== 1 || !proof.solution) {
160
+ throw new Error("No uniquely solvable Hitori found within the generation budget; try another seed");
161
+ }
162
+ return { ...board, seed, solution: proof.solution };
163
+ }
@@ -0,0 +1,3 @@
1
+ import type { HitoriMount, HitoriMountOptions } from "./hitoriPlay.types.ts";
2
+ /** A keyboard- and touch-friendly Hitori player, mounted in any host element. */
3
+ export declare function mountHitori(host: HTMLElement, initial: HitoriMountOptions): HitoriMount;
@@ -0,0 +1,111 @@
1
+ import { checkHitori } from "./hitoriBoard.js";
2
+ import { decodeHitori, encodeHitori, hintHitori, newHitori, restartHitori, shadeHitori, undoHitori } from "./hitoriGame.js";
3
+ import { drawHitori } from "./hitoriDraw.js";
4
+ import { KAZU_STYLE } from "./style.js";
5
+ import { HITORI_PLAY_STYLE } from "./hitoriStyle.js";
6
+ import { hitoriWords } from "./hitoriStrings.js";
7
+ /** A keyboard- and touch-friendly Hitori player, mounted in any host element. */
8
+ export function mountHitori(host, initial) {
9
+ const doc = host.ownerDocument;
10
+ let options = { ...initial }, game = initial.progress ? decodeHitori(initial.progress) : null;
11
+ game ?? (game = newHitori(initial.board));
12
+ let selected = 0, errors = [], message = "", finished = checkHitori(game.board, game.shaded).ok, dead = false;
13
+ const root = doc.createElement("div"), style = doc.createElement("style"), board = doc.createElement("div");
14
+ const status = doc.createElement("p"), tools = doc.createElement("div"), help = doc.createElement("p");
15
+ root.className = "kh-root";
16
+ style.textContent = KAZU_STYLE.replace(/\.kazu/g, ".ks-palette") + HITORI_PLAY_STYLE;
17
+ board.className = "kh-board";
18
+ status.className = "kh-status";
19
+ status.setAttribute("role", "status");
20
+ tools.className = "kh-tools";
21
+ help.className = "kh-help";
22
+ root.append(style, board, status, tools, help);
23
+ host.append(root);
24
+ const copy = () => ({ ...game, board: { ...game.board, numbers: [...game.board.numbers] }, shaded: [...game.shaded], history: game.history.map(turn => [...turn]) });
25
+ const button = (label, action, disabled = false) => {
26
+ const control = doc.createElement("button");
27
+ control.type = "button";
28
+ control.textContent = label;
29
+ control.disabled = disabled;
30
+ control.onclick = action;
31
+ tools.append(control);
32
+ };
33
+ const changed = () => {
34
+ errors = [];
35
+ message = "";
36
+ const wasFinished = finished;
37
+ finished = checkHitori(game.board, game.shaded).ok;
38
+ render();
39
+ options.onChange?.(copy());
40
+ if (finished && !wasFinished)
41
+ options.onFinish?.(copy());
42
+ };
43
+ const shade = (cell) => {
44
+ if (finished)
45
+ return;
46
+ selected = cell;
47
+ game = shadeHitori(game, cell);
48
+ changed();
49
+ };
50
+ const render = () => {
51
+ if (dead)
52
+ return;
53
+ const words = hitoriWords(options.language), focused = board.contains(doc.activeElement), size = game.board.size;
54
+ board.innerHTML = drawHitori(game.board, { ...options, shaded: game.shaded, selected, errors });
55
+ const cells = doc.createElement("div");
56
+ cells.className = "kh-cells";
57
+ cells.style.gridTemplateColumns = `repeat(${size},1fr)`;
58
+ for (let cell = 0; cell < game.shaded.length; cell += 1) {
59
+ const control = doc.createElement("button");
60
+ control.type = "button";
61
+ control.className = "kh-cell";
62
+ control.dataset.cell = String(cell);
63
+ control.tabIndex = cell === selected ? 0 : -1;
64
+ const state = game.shaded[cell] ? words.shaded : words.white;
65
+ control.setAttribute("aria-label", `${words.cell} ${Math.floor(cell / size) + 1}, ${words.column} ${cell % size + 1}: ${game.board.numbers[cell]}, ${state}`);
66
+ control.setAttribute("aria-pressed", String(game.shaded[cell]));
67
+ control.onclick = () => shade(cell);
68
+ control.onkeydown = event => {
69
+ const moves = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -size, ArrowDown: size };
70
+ if (event.key in moves) {
71
+ event.preventDefault();
72
+ selected = Math.max(0, Math.min(game.shaded.length - 1, cell + moves[event.key]));
73
+ render();
74
+ }
75
+ else if (event.key === " " || event.key === "Enter") {
76
+ event.preventDefault();
77
+ shade(cell);
78
+ }
79
+ };
80
+ cells.append(control);
81
+ }
82
+ board.append(cells);
83
+ if (focused)
84
+ board.querySelector(`[data-cell="${selected}"]`)?.focus();
85
+ status.textContent = finished ? (game.helped ? words.helped : words.won) : message;
86
+ help.textContent = words.help;
87
+ tools.replaceChildren();
88
+ button(words.undo, () => { game = undoHitori(game); changed(); }, !game.history.length);
89
+ button(words.check, () => { errors = checkHitori(game.board, game.shaded).errors; message = errors.length ? words.bad : words.good; render(); });
90
+ button(words.hint, () => {
91
+ const cell = hintHitori(game);
92
+ if (cell === null) {
93
+ message = words.noHint;
94
+ render();
95
+ return;
96
+ }
97
+ selected = cell;
98
+ game = { ...shadeHitori(game, cell), helped: true };
99
+ changed();
100
+ }, finished);
101
+ button(words.restart, () => { game = restartHitori(game); finished = false; changed(); });
102
+ };
103
+ render();
104
+ return {
105
+ game: copy,
106
+ progress: () => encodeHitori(game),
107
+ restart: () => { game = restartHitori(game); finished = false; changed(); },
108
+ set: (next) => { options = { ...options, ...next }; render(); },
109
+ destroy: () => { dead = true; root.remove(); },
110
+ };
111
+ }
@@ -0,0 +1,25 @@
1
+ import type { HitoriBoard, HitoriGame } from "./hitori.types.ts";
2
+ export type HitoriLanguage = "en" | "ja";
3
+ export type HitoriMaterial = "ivory" | "wood" | "slate";
4
+ export type HitoriPieces = "ink" | "tiles";
5
+ export type HitoriDrawOptions = {
6
+ shaded?: readonly boolean[];
7
+ selected?: number | null;
8
+ errors?: readonly number[];
9
+ material?: HitoriMaterial;
10
+ pieces?: HitoriPieces;
11
+ language?: HitoriLanguage;
12
+ };
13
+ export type HitoriMountOptions = HitoriDrawOptions & {
14
+ board: HitoriBoard;
15
+ progress?: string;
16
+ onChange?: (game: HitoriGame) => void;
17
+ onFinish?: (game: HitoriGame) => void;
18
+ };
19
+ export type HitoriMount = {
20
+ game: () => HitoriGame;
21
+ progress: () => string;
22
+ restart: () => void;
23
+ set: (options: HitoriDrawOptions) => void;
24
+ destroy: () => void;
25
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ import type { HitoriBoard, HitoriSolve } from "./hitori.types.ts";
2
+ /** Counts distinct minimal shade patterns. A non-complete result never asserts uniqueness. */
3
+ export declare function solveHitori(board: HitoriBoard, options?: {
4
+ limit?: number;
5
+ nodes?: number;
6
+ }): HitoriSolve;
@@ -0,0 +1,69 @@
1
+ import { HITORI_MAX_NODES } from "./hitori.constants.js";
2
+ import { checkHitori, hitoriNeighbors, isHitoriBoard } from "./hitoriBoard.js";
3
+ function firstUnshadedDuplicate(board, shaded) {
4
+ const size = board.size;
5
+ for (let y = 0; y < size; y += 1)
6
+ for (let x = 0; x < size; x += 1) {
7
+ const cell = y * size + x;
8
+ if (shaded[cell])
9
+ continue;
10
+ for (let otherX = x + 1; otherX < size; otherX += 1) {
11
+ const other = y * size + otherX;
12
+ if (!shaded[other] && board.numbers[cell] === board.numbers[other])
13
+ return [cell, other];
14
+ }
15
+ for (let otherY = y + 1; otherY < size; otherY += 1) {
16
+ const other = otherY * size + x;
17
+ if (!shaded[other] && board.numbers[cell] === board.numbers[other])
18
+ return [cell, other];
19
+ }
20
+ }
21
+ return null;
22
+ }
23
+ /** A valid answer cannot contain an unnecessary shade; this avoids treating extra black cells as separate puzzle solutions. */
24
+ function isMinimalSolution(board, shaded) {
25
+ if (!checkHitori(board, shaded).ok)
26
+ return false;
27
+ for (let cell = 0; cell < shaded.length; cell += 1)
28
+ if (shaded[cell]) {
29
+ const smaller = [...shaded];
30
+ smaller[cell] = false;
31
+ if (checkHitori(board, smaller).ok)
32
+ return false;
33
+ }
34
+ return true;
35
+ }
36
+ /** Counts distinct minimal shade patterns. A non-complete result never asserts uniqueness. */
37
+ export function solveHitori(board, options = {}) {
38
+ if (!isHitoriBoard(board))
39
+ throw new RangeError("Invalid Hitori board");
40
+ const limit = Math.max(1, Math.floor(options.limit ?? 2));
41
+ const budget = Math.max(1, Math.floor(options.nodes ?? HITORI_MAX_NODES));
42
+ let nodes = 0, count = 0, solution = null, exhausted = false;
43
+ const search = (shaded) => {
44
+ if (count >= limit || exhausted)
45
+ return;
46
+ nodes += 1;
47
+ if (nodes > budget) {
48
+ exhausted = true;
49
+ return;
50
+ }
51
+ const duplicate = firstUnshadedDuplicate(board, shaded);
52
+ if (!duplicate) {
53
+ if (isMinimalSolution(board, shaded)) {
54
+ count += 1;
55
+ solution ?? (solution = [...shaded]);
56
+ }
57
+ return;
58
+ }
59
+ for (const cell of duplicate) {
60
+ if (hitoriNeighbors(board.size, cell).some(neighbor => shaded[neighbor]))
61
+ continue;
62
+ shaded[cell] = true;
63
+ search(shaded);
64
+ shaded[cell] = false;
65
+ }
66
+ };
67
+ search(Array(board.size * board.size).fill(false));
68
+ return { count, solution, complete: !exhausted && count < limit, nodes: Math.min(nodes, budget) };
69
+ }