@johnmorrisdotca/kazu 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (372) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +424 -10
  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 +13 -0
  6. package/dist/akari-entry.js +11 -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 +7 -0
  10. package/dist/akari.constants.js +7 -0
  11. package/dist/akari.types.d.ts +52 -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 +10 -0
  20. package/dist/akariGenerate.js +101 -0
  21. package/dist/akariLogic.d.ts +10 -0
  22. package/dist/akariLogic.js +84 -0
  23. package/dist/akariMount.d.ts +3 -0
  24. package/dist/akariMount.js +214 -0
  25. package/dist/akariPlay.types.d.ts +25 -0
  26. package/dist/akariPlay.types.js +1 -0
  27. package/dist/akariRate.d.ts +6 -0
  28. package/dist/akariRate.js +34 -0
  29. package/dist/akariSolve.d.ts +6 -0
  30. package/dist/akariSolve.js +18 -0
  31. package/dist/akariStrings.d.ts +98 -0
  32. package/dist/akariStrings.js +53 -0
  33. package/dist/akariStyle.d.ts +1 -0
  34. package/dist/akariStyle.js +9 -0
  35. package/dist/akariTemplate.d.ts +7 -0
  36. package/dist/akariTemplate.js +78 -0
  37. package/dist/csp.d.ts +63 -0
  38. package/dist/csp.js +162 -0
  39. package/dist/fillomino-draw-entry.d.ts +2 -0
  40. package/dist/fillomino-draw-entry.js +1 -0
  41. package/dist/fillomino-entry.d.ts +9 -0
  42. package/dist/fillomino-entry.js +8 -0
  43. package/dist/fillomino-play-entry.d.ts +4 -0
  44. package/dist/fillomino-play-entry.js +3 -0
  45. package/dist/fillomino.constants.d.ts +9 -0
  46. package/dist/fillomino.constants.js +9 -0
  47. package/dist/fillomino.types.d.ts +47 -0
  48. package/dist/fillomino.types.js +1 -0
  49. package/dist/fillominoBoard.d.ts +6 -0
  50. package/dist/fillominoBoard.js +84 -0
  51. package/dist/fillominoBuild.d.ts +10 -0
  52. package/dist/fillominoBuild.js +78 -0
  53. package/dist/fillominoDraw.d.ts +3 -0
  54. package/dist/fillominoDraw.js +31 -0
  55. package/dist/fillominoGame.d.ts +13 -0
  56. package/dist/fillominoGame.js +65 -0
  57. package/dist/fillominoGenerate.d.ts +9 -0
  58. package/dist/fillominoGenerate.js +93 -0
  59. package/dist/fillominoLogic.d.ts +14 -0
  60. package/dist/fillominoLogic.js +177 -0
  61. package/dist/fillominoMount.d.ts +2 -0
  62. package/dist/fillominoMount.js +214 -0
  63. package/dist/fillominoPlay.types.d.ts +25 -0
  64. package/dist/fillominoPlay.types.js +1 -0
  65. package/dist/fillominoRate.d.ts +3 -0
  66. package/dist/fillominoRate.js +57 -0
  67. package/dist/fillominoSolve.d.ts +6 -0
  68. package/dist/fillominoSolve.js +38 -0
  69. package/dist/fillominoStrings.d.ts +102 -0
  70. package/dist/fillominoStrings.js +13 -0
  71. package/dist/fillominoStyle.d.ts +1 -0
  72. package/dist/fillominoStyle.js +13 -0
  73. package/dist/fillominoWorker.d.ts +1 -0
  74. package/dist/fillominoWorker.js +10 -0
  75. package/dist/heyawake-draw-entry.d.ts +2 -0
  76. package/dist/heyawake-draw-entry.js +1 -0
  77. package/dist/heyawake-entry.d.ts +13 -0
  78. package/dist/heyawake-entry.js +11 -0
  79. package/dist/heyawake-play-entry.d.ts +2 -0
  80. package/dist/heyawake-play-entry.js +1 -0
  81. package/dist/heyawake.constants.d.ts +5 -0
  82. package/dist/heyawake.constants.js +5 -0
  83. package/dist/heyawake.types.d.ts +35 -0
  84. package/dist/heyawake.types.js +1 -0
  85. package/dist/heyawakeBoard.d.ts +7 -0
  86. package/dist/heyawakeBoard.js +116 -0
  87. package/dist/heyawakeDraw.d.ts +3 -0
  88. package/dist/heyawakeDraw.js +27 -0
  89. package/dist/heyawakeGame.d.ts +12 -0
  90. package/dist/heyawakeGame.js +51 -0
  91. package/dist/heyawakeGenerate.d.ts +2 -0
  92. package/dist/heyawakeGenerate.js +114 -0
  93. package/dist/heyawakeMount.d.ts +2 -0
  94. package/dist/heyawakeMount.js +106 -0
  95. package/dist/heyawakePlay.types.d.ts +24 -0
  96. package/dist/heyawakePlay.types.js +1 -0
  97. package/dist/heyawakeSolve.d.ts +5 -0
  98. package/dist/heyawakeSolve.js +52 -0
  99. package/dist/heyawakeStrings.d.ts +19 -0
  100. package/dist/heyawakeStrings.js +7 -0
  101. package/dist/heyawakeStyle.d.ts +1 -0
  102. package/dist/heyawakeStyle.js +2 -0
  103. package/dist/heyawakeWorker.d.ts +1 -0
  104. package/dist/heyawakeWorker.js +10 -0
  105. package/dist/hitori-draw-entry.d.ts +2 -0
  106. package/dist/hitori-draw-entry.js +1 -0
  107. package/dist/hitori-entry.d.ts +7 -0
  108. package/dist/hitori-entry.js +7 -0
  109. package/dist/hitori-play-entry.d.ts +4 -0
  110. package/dist/hitori-play-entry.js +3 -0
  111. package/dist/hitori.constants.d.ts +8 -0
  112. package/dist/hitori.constants.js +8 -0
  113. package/dist/hitori.types.d.ts +37 -0
  114. package/dist/hitori.types.js +1 -0
  115. package/dist/hitoriBoard.d.ts +8 -0
  116. package/dist/hitoriBoard.js +73 -0
  117. package/dist/hitoriBuild.d.ts +12 -0
  118. package/dist/hitoriBuild.js +117 -0
  119. package/dist/hitoriDraw.d.ts +4 -0
  120. package/dist/hitoriDraw.js +25 -0
  121. package/dist/hitoriGame.d.ts +10 -0
  122. package/dist/hitoriGame.js +48 -0
  123. package/dist/hitoriGenerate.d.ts +10 -0
  124. package/dist/hitoriGenerate.js +65 -0
  125. package/dist/hitoriLogic.d.ts +12 -0
  126. package/dist/hitoriLogic.js +189 -0
  127. package/dist/hitoriMount.d.ts +3 -0
  128. package/dist/hitoriMount.js +111 -0
  129. package/dist/hitoriPlay.types.d.ts +25 -0
  130. package/dist/hitoriPlay.types.js +1 -0
  131. package/dist/hitoriRate.d.ts +3 -0
  132. package/dist/hitoriRate.js +32 -0
  133. package/dist/hitoriSolve.d.ts +9 -0
  134. package/dist/hitoriSolve.js +18 -0
  135. package/dist/hitoriStrings.d.ts +70 -0
  136. package/dist/hitoriStrings.js +17 -0
  137. package/dist/hitoriStyle.d.ts +1 -0
  138. package/dist/hitoriStyle.js +9 -0
  139. package/dist/index.d.ts +11 -0
  140. package/dist/index.js +11 -0
  141. package/dist/juosan-draw-entry.d.ts +2 -0
  142. package/dist/juosan-draw-entry.js +1 -0
  143. package/dist/juosan-entry.d.ts +11 -0
  144. package/dist/juosan-entry.js +10 -0
  145. package/dist/juosan-play-entry.d.ts +4 -0
  146. package/dist/juosan-play-entry.js +3 -0
  147. package/dist/juosan.constants.d.ts +4 -0
  148. package/dist/juosan.constants.js +4 -0
  149. package/dist/juosan.types.d.ts +33 -0
  150. package/dist/juosan.types.js +1 -0
  151. package/dist/juosanBoard.d.ts +7 -0
  152. package/dist/juosanBoard.js +102 -0
  153. package/dist/juosanDraw.d.ts +4 -0
  154. package/dist/juosanDraw.js +49 -0
  155. package/dist/juosanGame.d.ts +20 -0
  156. package/dist/juosanGame.js +86 -0
  157. package/dist/juosanGenerate.d.ts +3 -0
  158. package/dist/juosanGenerate.js +31 -0
  159. package/dist/juosanMount.d.ts +3 -0
  160. package/dist/juosanMount.js +206 -0
  161. package/dist/juosanPlay.types.d.ts +24 -0
  162. package/dist/juosanPlay.types.js +1 -0
  163. package/dist/juosanSolve.d.ts +3 -0
  164. package/dist/juosanSolve.js +82 -0
  165. package/dist/juosanStrings.d.ts +77 -0
  166. package/dist/juosanStrings.js +43 -0
  167. package/dist/juosanStyle.d.ts +1 -0
  168. package/dist/juosanStyle.js +1 -0
  169. package/dist/kakuro-draw-entry.d.ts +2 -0
  170. package/dist/kakuro-draw-entry.js +1 -0
  171. package/dist/kakuro-entry.d.ts +13 -0
  172. package/dist/kakuro-entry.js +11 -0
  173. package/dist/kakuro-play-entry.d.ts +4 -0
  174. package/dist/kakuro-play-entry.js +3 -0
  175. package/dist/kakuro.constants.d.ts +11 -0
  176. package/dist/kakuro.constants.js +11 -0
  177. package/dist/kakuro.types.d.ts +65 -0
  178. package/dist/kakuro.types.js +1 -0
  179. package/dist/kakuroBoard.d.ts +8 -0
  180. package/dist/kakuroBoard.js +117 -0
  181. package/dist/kakuroBuild.d.ts +30 -0
  182. package/dist/kakuroBuild.js +337 -0
  183. package/dist/kakuroDraw.d.ts +10 -0
  184. package/dist/kakuroDraw.js +21 -0
  185. package/dist/kakuroGame.d.ts +12 -0
  186. package/dist/kakuroGame.js +74 -0
  187. package/dist/kakuroGenerate.d.ts +10 -0
  188. package/dist/kakuroGenerate.js +62 -0
  189. package/dist/kakuroLogic.d.ts +13 -0
  190. package/dist/kakuroLogic.js +153 -0
  191. package/dist/kakuroMount.d.ts +2 -0
  192. package/dist/kakuroMount.js +109 -0
  193. package/dist/kakuroPlay.types.d.ts +20 -0
  194. package/dist/kakuroPlay.types.js +1 -0
  195. package/dist/kakuroRate.d.ts +3 -0
  196. package/dist/kakuroRate.js +40 -0
  197. package/dist/kakuroSolve.d.ts +6 -0
  198. package/dist/kakuroSolve.js +33 -0
  199. package/dist/kakuroStrings.d.ts +82 -0
  200. package/dist/kakuroStrings.js +5 -0
  201. package/dist/kakuroStyle.d.ts +1 -0
  202. package/dist/kakuroStyle.js +1 -0
  203. package/dist/kakuroTemplate.d.ts +3 -0
  204. package/dist/kakuroTemplate.js +130 -0
  205. package/dist/masyu-draw-entry.d.ts +2 -0
  206. package/dist/masyu-draw-entry.js +1 -0
  207. package/dist/masyu-entry.d.ts +6 -0
  208. package/dist/masyu-entry.js +6 -0
  209. package/dist/masyu-play-entry.d.ts +4 -0
  210. package/dist/masyu-play-entry.js +3 -0
  211. package/dist/masyu.constants.d.ts +2 -0
  212. package/dist/masyu.constants.js +2 -0
  213. package/dist/masyu.types.d.ts +46 -0
  214. package/dist/masyu.types.js +1 -0
  215. package/dist/masyuBoard.d.ts +10 -0
  216. package/dist/masyuBoard.js +88 -0
  217. package/dist/masyuDraw.d.ts +2 -0
  218. package/dist/masyuDraw.js +33 -0
  219. package/dist/masyuGame.d.ts +9 -0
  220. package/dist/masyuGame.js +46 -0
  221. package/dist/masyuGenerate.d.ts +3 -0
  222. package/dist/masyuGenerate.js +56 -0
  223. package/dist/masyuMount.d.ts +2 -0
  224. package/dist/masyuMount.js +118 -0
  225. package/dist/masyuPlay.types.d.ts +1 -0
  226. package/dist/masyuPlay.types.js +1 -0
  227. package/dist/masyuSolve.d.ts +6 -0
  228. package/dist/masyuSolve.js +88 -0
  229. package/dist/masyuStrings.d.ts +28 -0
  230. package/dist/masyuStrings.js +4 -0
  231. package/dist/masyuStyle.d.ts +1 -0
  232. package/dist/masyuStyle.js +11 -0
  233. package/dist/nurikabe-draw-entry.d.ts +2 -0
  234. package/dist/nurikabe-draw-entry.js +1 -0
  235. package/dist/nurikabe-entry.d.ts +6 -0
  236. package/dist/nurikabe-entry.js +6 -0
  237. package/dist/nurikabe-play-entry.d.ts +4 -0
  238. package/dist/nurikabe-play-entry.js +3 -0
  239. package/dist/nurikabe.constants.d.ts +2 -0
  240. package/dist/nurikabe.constants.js +2 -0
  241. package/dist/nurikabe.types.d.ts +20 -0
  242. package/dist/nurikabe.types.js +1 -0
  243. package/dist/nurikabeBoard.d.ts +8 -0
  244. package/dist/nurikabeBoard.js +72 -0
  245. package/dist/nurikabeDraw.d.ts +4 -0
  246. package/dist/nurikabeDraw.js +15 -0
  247. package/dist/nurikabeGame.d.ts +8 -0
  248. package/dist/nurikabeGame.js +40 -0
  249. package/dist/nurikabeGenerate.d.ts +3 -0
  250. package/dist/nurikabeGenerate.js +31 -0
  251. package/dist/nurikabeMount.d.ts +3 -0
  252. package/dist/nurikabeMount.js +99 -0
  253. package/dist/nurikabePlay.types.d.ts +25 -0
  254. package/dist/nurikabePlay.types.js +1 -0
  255. package/dist/nurikabeSolve.d.ts +6 -0
  256. package/dist/nurikabeSolve.js +75 -0
  257. package/dist/nurikabeStrings.d.ts +70 -0
  258. package/dist/nurikabeStrings.js +5 -0
  259. package/dist/nurikabeStyle.d.ts +1 -0
  260. package/dist/nurikabeStyle.js +9 -0
  261. package/dist/ripple-draw-entry.d.ts +2 -0
  262. package/dist/ripple-draw-entry.js +1 -0
  263. package/dist/ripple-entry.d.ts +12 -0
  264. package/dist/ripple-entry.js +11 -0
  265. package/dist/ripple-play-entry.d.ts +4 -0
  266. package/dist/ripple-play-entry.js +3 -0
  267. package/dist/ripple.constants.d.ts +4 -0
  268. package/dist/ripple.constants.js +4 -0
  269. package/dist/ripple.types.d.ts +34 -0
  270. package/dist/ripple.types.js +1 -0
  271. package/dist/rippleBoard.d.ts +7 -0
  272. package/dist/rippleBoard.js +99 -0
  273. package/dist/rippleDraw.d.ts +3 -0
  274. package/dist/rippleDraw.js +32 -0
  275. package/dist/rippleGame.d.ts +13 -0
  276. package/dist/rippleGame.js +85 -0
  277. package/dist/rippleGenerate.d.ts +3 -0
  278. package/dist/rippleGenerate.js +57 -0
  279. package/dist/rippleMount.d.ts +2 -0
  280. package/dist/rippleMount.js +179 -0
  281. package/dist/ripplePlay.types.d.ts +23 -0
  282. package/dist/ripplePlay.types.js +1 -0
  283. package/dist/rippleSolve.d.ts +6 -0
  284. package/dist/rippleSolve.js +83 -0
  285. package/dist/rippleStrings.d.ts +86 -0
  286. package/dist/rippleStrings.js +5 -0
  287. package/dist/rippleStyle.d.ts +1 -0
  288. package/dist/rippleStyle.js +1 -0
  289. package/dist/shikaku-entry.d.ts +2 -0
  290. package/dist/shikaku-entry.js +2 -0
  291. package/dist/shikaku.constants.d.ts +5 -2
  292. package/dist/shikaku.constants.js +5 -2
  293. package/dist/shikaku.types.d.ts +18 -1
  294. package/dist/shikakuBuild.d.ts +21 -0
  295. package/dist/shikakuBuild.js +135 -0
  296. package/dist/shikakuGenerate.d.ts +6 -1
  297. package/dist/shikakuGenerate.js +50 -35
  298. package/dist/shikakuLogic.d.ts +13 -0
  299. package/dist/shikakuLogic.js +84 -0
  300. package/dist/shikakuPacks.d.ts +27 -0
  301. package/dist/shikakuPacks.js +26 -0
  302. package/dist/shikakuRate.d.ts +3 -0
  303. package/dist/shikakuRate.js +27 -0
  304. package/dist/shikakuSolve.d.ts +1 -1
  305. package/dist/shikakuSolve.js +23 -44
  306. package/dist/shikakuTemplate.d.ts +3 -0
  307. package/dist/shikakuTemplate.js +43 -0
  308. package/dist/slitherlink-draw-entry.d.ts +2 -0
  309. package/dist/slitherlink-draw-entry.js +1 -0
  310. package/dist/slitherlink-entry.d.ts +12 -0
  311. package/dist/slitherlink-entry.js +10 -0
  312. package/dist/slitherlink-play-entry.d.ts +4 -0
  313. package/dist/slitherlink-play-entry.js +3 -0
  314. package/dist/slitherlink.constants.d.ts +7 -0
  315. package/dist/slitherlink.constants.js +7 -0
  316. package/dist/slitherlink.types.d.ts +52 -0
  317. package/dist/slitherlink.types.js +1 -0
  318. package/dist/slitherlinkBoard.d.ts +12 -0
  319. package/dist/slitherlinkBoard.js +132 -0
  320. package/dist/slitherlinkDraw.d.ts +4 -0
  321. package/dist/slitherlinkDraw.js +51 -0
  322. package/dist/slitherlinkGame.d.ts +8 -0
  323. package/dist/slitherlinkGame.js +63 -0
  324. package/dist/slitherlinkGenerate.d.ts +10 -0
  325. package/dist/slitherlinkGenerate.js +182 -0
  326. package/dist/slitherlinkLogic.d.ts +10 -0
  327. package/dist/slitherlinkLogic.js +267 -0
  328. package/dist/slitherlinkMount.d.ts +3 -0
  329. package/dist/slitherlinkMount.js +267 -0
  330. package/dist/slitherlinkPlay.types.d.ts +23 -0
  331. package/dist/slitherlinkPlay.types.js +1 -0
  332. package/dist/slitherlinkRate.d.ts +3 -0
  333. package/dist/slitherlinkRate.js +26 -0
  334. package/dist/slitherlinkSolve.d.ts +6 -0
  335. package/dist/slitherlinkSolve.js +19 -0
  336. package/dist/slitherlinkStrings.d.ts +86 -0
  337. package/dist/slitherlinkStrings.js +47 -0
  338. package/dist/slitherlinkStyle.d.ts +1 -0
  339. package/dist/slitherlinkStyle.js +18 -0
  340. package/dist/slitherlinkTemplate.d.ts +3 -0
  341. package/dist/slitherlinkTemplate.js +101 -0
  342. package/dist/version.d.ts +1 -1
  343. package/dist/version.js +1 -1
  344. package/dist/yajilin-draw-entry.d.ts +2 -0
  345. package/dist/yajilin-draw-entry.js +1 -0
  346. package/dist/yajilin-entry.d.ts +6 -0
  347. package/dist/yajilin-entry.js +6 -0
  348. package/dist/yajilin-play-entry.d.ts +4 -0
  349. package/dist/yajilin-play-entry.js +3 -0
  350. package/dist/yajilin.constants.d.ts +2 -0
  351. package/dist/yajilin.constants.js +2 -0
  352. package/dist/yajilin.types.d.ts +63 -0
  353. package/dist/yajilin.types.js +1 -0
  354. package/dist/yajilinBoard.d.ts +9 -0
  355. package/dist/yajilinBoard.js +91 -0
  356. package/dist/yajilinDraw.d.ts +2 -0
  357. package/dist/yajilinDraw.js +33 -0
  358. package/dist/yajilinGame.d.ts +15 -0
  359. package/dist/yajilinGame.js +58 -0
  360. package/dist/yajilinGenerate.d.ts +3 -0
  361. package/dist/yajilinGenerate.js +224 -0
  362. package/dist/yajilinMount.d.ts +2 -0
  363. package/dist/yajilinMount.js +162 -0
  364. package/dist/yajilinPlay.types.d.ts +1 -0
  365. package/dist/yajilinPlay.types.js +1 -0
  366. package/dist/yajilinSolve.d.ts +6 -0
  367. package/dist/yajilinSolve.js +82 -0
  368. package/dist/yajilinStrings.d.ts +32 -0
  369. package/dist/yajilinStrings.js +4 -0
  370. package/dist/yajilinStyle.d.ts +1 -0
  371. package/dist/yajilinStyle.js +12 -0
  372. package/package.json +180 -3
@@ -0,0 +1,106 @@
1
+ import { checkHeyawake, isHeyawakeBoard } from "./heyawakeBoard.js";
2
+ import { decodeHeyawake, encodeHeyawake, hintHeyawake, newHeyawake, restartHeyawake, setHeyawakeCell, undoHeyawake, heyawakeFinished } from "./heyawakeGame.js";
3
+ import { drawHeyawake } from "./heyawakeDraw.js";
4
+ import { KAZU_STYLE } from "./style.js";
5
+ import { HEYAWAKE_PLAY_STYLE } from "./heyawakeStyle.js";
6
+ import { heyawakeWords } from "./heyawakeStrings.js";
7
+ export function mountHeyawake(host, initial) {
8
+ if (!isHeyawakeBoard(initial.board))
9
+ throw new RangeError("Invalid Heyawake board");
10
+ const doc = host.ownerDocument;
11
+ let options = { ...initial }, game = initial.progress ? decodeHeyawake(initial.progress) : null;
12
+ if (!game || JSON.stringify(game.board) !== JSON.stringify(initial.board))
13
+ game = newHeyawake(initial.board);
14
+ let selected = 0, errors = [], message = "", dead = false, finished = heyawakeFinished(game);
15
+ const root = doc.createElement("div"), style = doc.createElement("style"), board = doc.createElement("div"), grid = doc.createElement("div"), status = doc.createElement("p"), tools = doc.createElement("div"), help = doc.createElement("p");
16
+ root.className = "kh-root";
17
+ style.textContent = KAZU_STYLE.replace(/\.kazu/g, ".kh-palette") + HEYAWAKE_PLAY_STYLE;
18
+ board.className = "kh-board";
19
+ grid.className = "kh-grid";
20
+ status.className = "kh-status";
21
+ status.setAttribute("role", "status");
22
+ tools.className = "kh-tools";
23
+ help.className = "kh-help";
24
+ root.append(style, board, status, tools, help);
25
+ host.append(root);
26
+ const copy = () => ({ ...game, board: { width: game.board.width, height: game.board.height, rooms: game.board.rooms.map(room => ({ ...room })) }, entries: [...game.entries], history: game.history.map(turn => [...turn]) });
27
+ const words = () => heyawakeWords(options.language);
28
+ const button = (label, action, disabled = false) => { const item = doc.createElement("button"); item.type = "button"; item.textContent = label; item.disabled = disabled; item.onclick = action; tools.append(item); };
29
+ const changed = () => {
30
+ errors = [];
31
+ message = "";
32
+ const won = heyawakeFinished(game);
33
+ render();
34
+ options.onChange?.(copy());
35
+ root.dispatchEvent(new CustomEvent("heyawake-change", { detail: copy(), bubbles: true }));
36
+ if (won && !finished) {
37
+ options.onFinish?.(copy());
38
+ root.dispatchEvent(new CustomEvent("heyawake-finish", { detail: copy(), bubbles: true }));
39
+ }
40
+ finished = won;
41
+ };
42
+ const choose = (cell) => {
43
+ if (finished)
44
+ return;
45
+ selected = cell;
46
+ const value = game.entries[cell] === null ? true : game.entries[cell] === true ? false : null;
47
+ game = setHeyawakeCell(game, cell, value);
48
+ changed();
49
+ };
50
+ const restart = () => { game = restartHeyawake(game); finished = false; changed(); };
51
+ const render = () => {
52
+ if (dead)
53
+ return;
54
+ const w = words(), b = game.board, focused = grid.contains(doc.activeElement);
55
+ board.classList.toggle("kh-palette", (options.material ?? "ivory") === "ivory");
56
+ board.innerHTML = drawHeyawake(b, { ...options, entries: game.entries, selected, errors });
57
+ grid.replaceChildren();
58
+ grid.style.gridTemplateColumns = `repeat(${b.width},1fr)`;
59
+ game.entries.forEach((entry, cellIndex) => {
60
+ const cell = doc.createElement("button");
61
+ cell.type = "button";
62
+ cell.className = "kh-cell";
63
+ cell.dataset.cell = String(cellIndex);
64
+ cell.tabIndex = cellIndex === selected ? 0 : -1;
65
+ cell.setAttribute("aria-label", `${w.row} ${Math.floor(cellIndex / b.width) + 1}, ${w.column} ${cellIndex % b.width + 1}: ${entry === true ? w.black : entry === false ? w.white : w.blank}`);
66
+ cell.setAttribute("aria-pressed", String(entry !== null));
67
+ cell.onclick = () => choose(cellIndex);
68
+ cell.onkeydown = event => {
69
+ const moves = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -b.width, ArrowDown: b.width };
70
+ if (event.key in moves) {
71
+ event.preventDefault();
72
+ selected = Math.max(0, Math.min(game.entries.length - 1, cellIndex + moves[event.key]));
73
+ render();
74
+ }
75
+ else if (event.key === " " || event.key === "Enter") {
76
+ event.preventDefault();
77
+ choose(cellIndex);
78
+ }
79
+ };
80
+ grid.append(cell);
81
+ });
82
+ board.append(grid);
83
+ if (focused)
84
+ grid.querySelector(`[data-cell="${selected}"]`)?.focus();
85
+ const checked = checkHeyawake(b, game.entries);
86
+ status.textContent = heyawakeFinished(game) ? game.helped ? w.helped : w.won : message || (errors.length ? w.wrong : "");
87
+ status.classList.toggle("kh-errors", errors.length > 0);
88
+ help.textContent = w.help;
89
+ tools.replaceChildren();
90
+ button(w.undo, () => { game = undoHeyawake(game); finished = heyawakeFinished(game); changed(); }, !game.history.length);
91
+ button(w.check, () => { errors = checked.errors; message = checked.errors.length === 0 ? w.checked : w.wrong; render(); });
92
+ button(w.hint, () => {
93
+ const hint = hintHeyawake(game);
94
+ if (!hint) {
95
+ message = w.noHint;
96
+ render();
97
+ return;
98
+ }
99
+ game = { ...setHeyawakeCell(game, hint.cell, hint.value), helped: true };
100
+ changed();
101
+ }, finished);
102
+ button(w.restart, restart);
103
+ };
104
+ render();
105
+ return { game: copy, progress: () => encodeHeyawake(game), restart, set: (next) => { options = { ...options, ...next }; render(); }, destroy: () => { dead = true; root.remove(); } };
106
+ }
@@ -0,0 +1,24 @@
1
+ import type { HeyawakeGame, HeyawakeBoard } from "./heyawake.types.ts";
2
+ export type HeyawakeAppearance = {
3
+ language?: "en" | "ja";
4
+ material?: "ivory" | "wood" | "slate";
5
+ pieces?: "ink" | "tiles";
6
+ };
7
+ export type HeyawakeMountOptions = HeyawakeAppearance & {
8
+ board: HeyawakeBoard;
9
+ progress?: string;
10
+ onChange?: (game: HeyawakeGame) => void;
11
+ onFinish?: (game: HeyawakeGame) => void;
12
+ };
13
+ export type HeyawakeDrawOptions = HeyawakeAppearance & {
14
+ entries?: readonly (boolean | null)[];
15
+ selected?: number;
16
+ errors?: readonly number[];
17
+ };
18
+ export type HeyawakeMount = {
19
+ game: () => HeyawakeGame;
20
+ progress: () => string;
21
+ restart: () => void;
22
+ set: (options: HeyawakeAppearance) => void;
23
+ destroy: () => void;
24
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ import type { HeyawakeBoard, HeyawakeSolve } from "./heyawake.types.ts";
2
+ export declare function solveHeyawake(board: HeyawakeBoard, entries?: readonly (boolean | null)[], options?: {
3
+ limit?: number;
4
+ nodes?: number;
5
+ }): HeyawakeSolve;
@@ -0,0 +1,52 @@
1
+ import { HEYAWAKE_MOST_NODES } from "./heyawake.constants.js";
2
+ import { checkHeyawake, heyawakeCells, heyawakeNeighbours, isHeyawakeBoard, roomForCell } from "./heyawakeBoard.js";
3
+ export function solveHeyawake(board, entries = Array(board.width * board.height).fill(null), options = {}) {
4
+ if (!isHeyawakeBoard(board))
5
+ throw new RangeError("Invalid Heyawake board");
6
+ const limit = Math.max(1, Math.floor(options.limit ?? 2)), maxNodes = Math.max(1, Math.floor(options.nodes ?? HEYAWAKE_MOST_NODES));
7
+ const work = [...entries], ids = roomForCell(board), roomCells = board.rooms.map(room => heyawakeCells(board, room));
8
+ if (work.length !== board.width * board.height || work.some(value => value !== true && value !== false && value !== null))
9
+ throw new RangeError("Invalid Heyawake entries");
10
+ let count = 0, nodes = 0, solution = null, stopped = false;
11
+ const visit = (index) => {
12
+ if (count >= limit || stopped)
13
+ return;
14
+ if (++nodes > maxNodes) {
15
+ stopped = true;
16
+ return;
17
+ }
18
+ if (index === work.length) {
19
+ const checked = checkHeyawake(board, work);
20
+ if (checked.ok) {
21
+ count += 1;
22
+ solution ?? (solution = work.map(value => value === true));
23
+ }
24
+ return;
25
+ }
26
+ if (work[index] !== null) {
27
+ visit(index + 1);
28
+ return;
29
+ }
30
+ const optionsAtCell = [false, true];
31
+ for (const black of optionsAtCell) {
32
+ if (black && heyawakeNeighbours(board, index).some(next => work[next] === true))
33
+ continue;
34
+ work[index] = black;
35
+ const room = board.rooms[ids[index]], cells = roomCells[ids[index]];
36
+ if (room.blacks !== null) {
37
+ const marked = cells.filter(cell => work[cell] === true).length;
38
+ const unknown = cells.filter(cell => work[cell] === null).length;
39
+ if (marked > room.blacks || marked + unknown < room.blacks) {
40
+ work[index] = null;
41
+ continue;
42
+ }
43
+ }
44
+ visit(index + 1);
45
+ work[index] = null;
46
+ if (count >= limit || stopped)
47
+ break;
48
+ }
49
+ };
50
+ visit(0);
51
+ return { count, solution, nodes: Math.min(nodes, maxNodes), complete: !stopped && count < limit };
52
+ }
@@ -0,0 +1,19 @@
1
+ export declare function heyawakeWords(language?: "en" | "ja"): {
2
+ title: string;
3
+ ready: string;
4
+ wrong: string;
5
+ checked: string;
6
+ won: string;
7
+ helped: string;
8
+ undo: string;
9
+ check: string;
10
+ hint: string;
11
+ restart: string;
12
+ help: string;
13
+ black: string;
14
+ white: string;
15
+ blank: string;
16
+ row: string;
17
+ column: string;
18
+ noHint: string;
19
+ };
@@ -0,0 +1,7 @@
1
+ export function heyawakeWords(language = "en") {
2
+ return language === "ja" ? {
3
+ title: "へやわけ", ready: "マスを選び、白・黒・空白を切り替えます", wrong: "ルールに合わないマスがあります", checked: "今のところ矛盾はありません", won: "完成しました", helped: "ヒントを使って完成しました", undo: "元に戻す", check: "確認", hint: "ヒント", restart: "やり直す", help: "黒マスは辺でつながらず、白マスは一続きです。白い直線は部屋を二つまで通ります。", black: "黒", white: "白", blank: "未入力", row: "行", column: "列", noHint: "一意に解ける状態ではありません"
4
+ } : {
5
+ title: "Heyawake", ready: "Select a cell to cycle white, black and blank", wrong: "Some cells break a rule", checked: "No contradiction found so far", won: "Puzzle complete", helped: "Solved with a hint", undo: "Undo", check: "Check", hint: "Hint", restart: "Restart", help: "Black cells do not touch; white cells stay connected. A straight white run spans at most two rooms.", black: "black", white: "white", blank: "blank", row: "row", column: "column", noHint: "A unique solution is not established"
6
+ };
7
+ }
@@ -0,0 +1 @@
1
+ export declare const HEYAWAKE_PLAY_STYLE = "\n.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,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,7 @@
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 "./hitoriRate.ts";
7
+ export * from "./hitoriGame.ts";
@@ -0,0 +1,7 @@
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 "./hitoriRate.js";
7
+ 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,8 @@
1
+ export declare const HITORI_MAX_NODES = 300000;
2
+ /** The sides on offer; any side from 4 to `HITORI_MOST_SIDE` is a board `isHitoriBoard` takes. */
3
+ export declare const HITORI_SIZES: readonly [5, 6, 7, 8, 9, 10, 12];
4
+ export declare const HITORI_MOST_SIDE = 12;
5
+ export declare const HITORI_LEAST_SIDE = 4;
6
+ /** How hard a puzzle is made, by what a person must do to solve it; see `rateHitori`. */
7
+ export declare const HITORI_LEVELS: readonly ["easy", "medium", "hard", "extra-hard"];
8
+ export declare const HITORI_MOST_ATTEMPTS = 600;
@@ -0,0 +1,8 @@
1
+ export const HITORI_MAX_NODES = 300000;
2
+ /** The sides on offer; any side from 4 to `HITORI_MOST_SIDE` is a board `isHitoriBoard` takes. */
3
+ export const HITORI_SIZES = [5, 6, 7, 8, 9, 10, 12];
4
+ export const HITORI_MOST_SIDE = 12;
5
+ export const HITORI_LEAST_SIDE = 4;
6
+ /** How hard a puzzle is made, by what a person must do to solve it; see `rateHitori`. */
7
+ export const HITORI_LEVELS = ["easy", "medium", "hard", "extra-hard"];
8
+ export const HITORI_MOST_ATTEMPTS = 600;
@@ -0,0 +1,37 @@
1
+ export type HitoriBoard = {
2
+ size: number;
3
+ numbers: readonly number[];
4
+ };
5
+ export type HitoriLevel = "easy" | "medium" | "hard" | "extra-hard";
6
+ export type HitoriPuzzle = HitoriBoard & {
7
+ seed: number;
8
+ level: HitoriLevel;
9
+ solution: readonly boolean[];
10
+ };
11
+ /**
12
+ * How hard a board is, measured by solving it. `depth` 0 means the rules solve it, 1 that somebody has to suppose a
13
+ * square shaded or white and watch it break, 2 that more than that is needed. `reach` says the rule that the white
14
+ * squares stay in one piece was needed at depth 0. `probes` is the suppositions depth 1 needed.
15
+ */
16
+ export type HitoriRating = {
17
+ depth: 0 | 1 | 2;
18
+ reach: boolean;
19
+ probes: number;
20
+ shaded: number;
21
+ /** Shaded squares as a share of the board. */
22
+ shadedShare: number;
23
+ /** Squares whose number repeats in their row or column, as a share of the board. */
24
+ repeatShare: number;
25
+ };
26
+ export type HitoriSolve = {
27
+ count: number;
28
+ solution: readonly boolean[] | null;
29
+ complete: boolean;
30
+ nodes: number;
31
+ };
32
+ export type HitoriGame = {
33
+ board: HitoriBoard;
34
+ shaded: readonly boolean[];
35
+ history: readonly (readonly boolean[])[];
36
+ helped: boolean;
37
+ };
@@ -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,73 @@
1
+ import { HITORI_LEAST_SIDE, HITORI_MOST_SIDE } from "./hitori.constants.js";
2
+ export function isHitoriBoard(board) {
3
+ return !!board && Number.isInteger(board.size) && board.size >= HITORI_LEAST_SIDE && board.size <= HITORI_MOST_SIDE
4
+ && Array.isArray(board.numbers)
5
+ && board.numbers.length === board.size * board.size
6
+ && board.numbers.every(value => Number.isInteger(value) && value >= 1 && value <= board.size);
7
+ }
8
+ export function hitoriNeighbors(size, cell) {
9
+ const x = cell % size, y = Math.floor(cell / size), neighbors = [];
10
+ if (x > 0)
11
+ neighbors.push(cell - 1);
12
+ if (x < size - 1)
13
+ neighbors.push(cell + 1);
14
+ if (y > 0)
15
+ neighbors.push(cell - size);
16
+ if (y < size - 1)
17
+ neighbors.push(cell + size);
18
+ return neighbors;
19
+ }
20
+ /** Checks unshaded row and column duplicates, adjacent shades, and white connectivity. */
21
+ export function checkHitori(board, shaded) {
22
+ if (!isHitoriBoard(board))
23
+ return { ok: false, errors: [] };
24
+ const size = board.size, total = size * size;
25
+ if (shaded.length !== total || !shaded.every(value => typeof value === "boolean"))
26
+ return { ok: false, errors: [] };
27
+ const errors = new Set();
28
+ for (let y = 0; y < size; y += 1)
29
+ for (let x = 0; x < size; x += 1) {
30
+ const cell = y * size + x;
31
+ if (shaded[cell])
32
+ continue;
33
+ for (let otherX = x + 1; otherX < size; otherX += 1) {
34
+ const other = y * size + otherX;
35
+ if (!shaded[other] && board.numbers[cell] === board.numbers[other]) {
36
+ errors.add(cell);
37
+ errors.add(other);
38
+ }
39
+ }
40
+ for (let otherY = y + 1; otherY < size; otherY += 1) {
41
+ const other = otherY * size + x;
42
+ if (!shaded[other] && board.numbers[cell] === board.numbers[other]) {
43
+ errors.add(cell);
44
+ errors.add(other);
45
+ }
46
+ }
47
+ }
48
+ for (let cell = 0; cell < total; cell += 1)
49
+ if (shaded[cell]) {
50
+ for (const neighbor of hitoriNeighbors(size, cell))
51
+ if (shaded[neighbor]) {
52
+ errors.add(cell);
53
+ errors.add(neighbor);
54
+ }
55
+ }
56
+ const white = shaded.map((isShaded, cell) => isShaded ? -1 : cell).filter(cell => cell >= 0), reached = new Set();
57
+ if (white.length) {
58
+ const stack = [white[0]];
59
+ reached.add(white[0]);
60
+ while (stack.length)
61
+ for (const neighbor of hitoriNeighbors(size, stack.pop())) {
62
+ if (!shaded[neighbor] && !reached.has(neighbor)) {
63
+ reached.add(neighbor);
64
+ stack.push(neighbor);
65
+ }
66
+ }
67
+ }
68
+ if (reached.size !== white.length)
69
+ for (const cell of white)
70
+ if (!reached.has(cell))
71
+ errors.add(cell);
72
+ return { ok: errors.size === 0, errors: [...errors].sort((a, b) => a - b) };
73
+ }
@@ -0,0 +1,12 @@
1
+ import type { HitoriBoard, HitoriLevel } from "./hitori.types.ts";
2
+ import type { Random } from "./random.ts";
3
+ export declare const SHARE: Record<HitoriLevel, number>;
4
+ /** A board with one answer, or null when the random choices did not lead to one. How the numbers are chosen leans on the level. */
5
+ export declare function candidateHitori(size: number, level: HitoriLevel, random: Random, base?: readonly number[]): {
6
+ board: HitoriBoard;
7
+ shaded: readonly boolean[];
8
+ } | null;
9
+ /** A random Latin square: every number once in each row and column, built row by row with backtracking. */
10
+ export declare function latinSquare(size: number, random: Random): number[] | null;
11
+ /** The same square with its rows, columns and numbers renamed at random: still one of each in every line. */
12
+ export declare function isotope(square: readonly number[], size: number, random: Random): number[];
@@ -0,0 +1,117 @@
1
+ import { hitoriNeighbors } from "./hitoriBoard.js";
2
+ import { hitoriModel } from "./hitoriLogic.js";
3
+ import { countCsp, openSlots } from "./csp.js";
4
+ import { shuffled } from "./random.js";
5
+ export const SHARE = { easy: .26, medium: .27, hard: .28, "extra-hard": .3 };
6
+ /** A board with one answer, or null when the random choices did not lead to one. How the numbers are chosen leans on the level. */
7
+ export function candidateHitori(size, level, random, base = latinSquare(size, random) ?? []) {
8
+ const total = size * size;
9
+ if (base.length !== total)
10
+ return null;
11
+ const target = Math.round(total * SHARE[level] * (.9 + random() * .2));
12
+ const shaded = Array(total).fill(false);
13
+ let count = 0;
14
+ const around = Array.from({ length: total }, (_, cell) => hitoriNeighbors(size, cell));
15
+ const mark = new Int32Array(total), stack = new Int32Array(total);
16
+ let stamp = 0;
17
+ const connected = () => {
18
+ const first = shaded.indexOf(false);
19
+ stamp += 1;
20
+ let top = 0, reached = 1;
21
+ stack[top++] = first;
22
+ mark[first] = stamp;
23
+ while (top)
24
+ for (const next of around[stack[--top]])
25
+ if (!shaded[next] && mark[next] !== stamp) {
26
+ mark[next] = stamp;
27
+ reached += 1;
28
+ stack[top++] = next;
29
+ }
30
+ return reached === total - count;
31
+ };
32
+ for (const cell of shuffled(Array.from({ length: total }, (_, i) => i), random)) {
33
+ if (count >= target)
34
+ break;
35
+ if (around[cell].some(next => shaded[next]))
36
+ continue;
37
+ shaded[cell] = true;
38
+ count += 1;
39
+ if (!connected()) {
40
+ shaded[cell] = false;
41
+ count -= 1;
42
+ }
43
+ }
44
+ if (count < Math.max(2, target - 2))
45
+ return null;
46
+ const numbers = isotope(base, size, random).map((n, cell) => shaded[cell] ? 0 : n);
47
+ const partnersOf = (cell) => {
48
+ const x = cell % size, y = Math.floor(cell / size), found = [];
49
+ for (let k = 0; k < size; k += 1) {
50
+ if (k !== x && !shaded[y * size + k])
51
+ found.push(y * size + k);
52
+ if (k !== y && !shaded[k * size + x])
53
+ found.push(k * size + x);
54
+ }
55
+ return found;
56
+ };
57
+ const blacks = shaded.flatMap((on, cell) => on ? [cell] : []);
58
+ for (const cell of blacks) {
59
+ const options = partnersOf(cell);
60
+ if (!options.length)
61
+ return null;
62
+ numbers[cell] = numbers[options[Math.floor(random() * options.length)]];
63
+ }
64
+ const board = { size, numbers };
65
+ const { csp } = hitoriModel(board, 2);
66
+ let proof = countCsp(csp, openSlots(csp), 2, 20000);
67
+ for (let repair = 0; repair < 60 && proof.count > 1 && !proof.exhausted; repair += 1) {
68
+ const other = proof.solutions[1].slice(), mine = (alive, cell) => alive[2 * cell + 1] === 1;
69
+ const candidates = shuffled(blacks.filter(cell => !mine(other, cell)), random);
70
+ let changed = false;
71
+ for (const cell of candidates) {
72
+ const keepers = partnersOf(cell).filter(p => !mine(other, p));
73
+ if (!keepers.length)
74
+ continue;
75
+ numbers[cell] = numbers[keepers[Math.floor(random() * keepers.length)]];
76
+ changed = true;
77
+ break;
78
+ }
79
+ if (!changed)
80
+ return null;
81
+ proof = countCsp(csp, openSlots(csp), 2, 20000);
82
+ }
83
+ if (proof.count !== 1 || proof.exhausted)
84
+ return null;
85
+ return { board, shaded };
86
+ }
87
+ /** A random Latin square: every number once in each row and column, built row by row with backtracking. */
88
+ export function latinSquare(size, random) {
89
+ const cells = Array(size * size).fill(0);
90
+ let steps = 0;
91
+ const fill = (at) => {
92
+ if (at === cells.length)
93
+ return true;
94
+ if (++steps > 20000)
95
+ return false;
96
+ const x = at % size, y = Math.floor(at / size);
97
+ const used = new Set();
98
+ for (let k = 0; k < x; k += 1)
99
+ used.add(cells[y * size + k]);
100
+ for (let k = 0; k < y; k += 1)
101
+ used.add(cells[k * size + x]);
102
+ for (const n of shuffled(Array.from({ length: size }, (_, i) => i + 1).filter(i => !used.has(i)), random)) {
103
+ cells[at] = n;
104
+ if (fill(at + 1))
105
+ return true;
106
+ }
107
+ cells[at] = 0;
108
+ return false;
109
+ };
110
+ return fill(0) ? cells : null;
111
+ }
112
+ /** The same square with its rows, columns and numbers renamed at random: still one of each in every line. */
113
+ export function isotope(square, size, random) {
114
+ const rows = shuffled(Array.from({ length: size }, (_, i) => i), random), cols = shuffled(Array.from({ length: size }, (_, i) => i), random);
115
+ const names = shuffled(Array.from({ length: size }, (_, i) => i + 1), random);
116
+ return Array.from({ length: size * size }, (_, cell) => names[square[rows[Math.floor(cell / size)] * size + cols[cell % size]] - 1]);
117
+ }
@@ -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;