@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
package/dist/index.d.ts CHANGED
@@ -35,3 +35,14 @@ export { seededRandom, shuffled, freshKazuSeed, isKazuSeed, KAZU_SEED_MOST } fro
35
35
  export type { Random } from "./random.ts";
36
36
  export { VERSION } from "./version.ts";
37
37
  export * from "./shikaku-entry.ts";
38
+ export * from "./hitori-entry.ts";
39
+ export * from "./nurikabe-entry.ts";
40
+ export * from "./akari-entry.ts";
41
+ export * from "./juosan-entry.ts";
42
+ export * from "./slitherlink-entry.ts";
43
+ export * from "./ripple-entry.ts";
44
+ export * from "./kakuro-entry.ts";
45
+ export * from "./fillomino-entry.ts";
46
+ export * from "./heyawake-entry.ts";
47
+ export * from "./masyu-entry.ts";
48
+ export * from "./yajilin-entry.ts";
package/dist/index.js CHANGED
@@ -30,3 +30,14 @@ export { boxedLayout, boxOf, KAZU_BOXES, neighbours, regionsAreSound } from "./l
30
30
  export { seededRandom, shuffled, freshKazuSeed, isKazuSeed, KAZU_SEED_MOST } from "./random.js";
31
31
  export { VERSION } from "./version.js";
32
32
  export * from "./shikaku-entry.js";
33
+ export * from "./hitori-entry.js";
34
+ export * from "./nurikabe-entry.js";
35
+ export * from "./akari-entry.js";
36
+ export * from "./juosan-entry.js";
37
+ export * from "./slitherlink-entry.js";
38
+ export * from "./ripple-entry.js";
39
+ export * from "./kakuro-entry.js";
40
+ export * from "./fillomino-entry.js";
41
+ export * from "./heyawake-entry.js";
42
+ export * from "./masyu-entry.js";
43
+ export * from "./yajilin-entry.js";
@@ -0,0 +1,2 @@
1
+ export { drawJuosan } from "./juosanDraw.ts";
2
+ export type { JuosanDrawOptions } from "./juosanPlay.types.ts";
@@ -0,0 +1 @@
1
+ export { drawJuosan } from "./juosanDraw.js";
@@ -0,0 +1,11 @@
1
+ export * from "./juosan.types.ts";
2
+ export * from "./juosan.constants.ts";
3
+ export * from "./juosanBoard.ts";
4
+ export * from "./juosanSolve.ts";
5
+ export * from "./juosanGenerate.ts";
6
+ export * from "./juosanGame.ts";
7
+ export { drawJuosan } from "./juosanDraw.ts";
8
+ export { mountJuosan } from "./juosanMount.ts";
9
+ export { JUOSAN_PLAY_STYLE } from "./juosanStyle.ts";
10
+ export { JUOSAN_STRINGS } from "./juosanStrings.ts";
11
+ export type * from "./juosanPlay.types.ts";
@@ -0,0 +1,10 @@
1
+ export * from "./juosan.types.js";
2
+ export * from "./juosan.constants.js";
3
+ export * from "./juosanBoard.js";
4
+ export * from "./juosanSolve.js";
5
+ export * from "./juosanGenerate.js";
6
+ export * from "./juosanGame.js";
7
+ export { drawJuosan } from "./juosanDraw.js";
8
+ export { mountJuosan } from "./juosanMount.js";
9
+ export { JUOSAN_PLAY_STYLE } from "./juosanStyle.js";
10
+ export { JUOSAN_STRINGS } from "./juosanStrings.js";
@@ -0,0 +1,4 @@
1
+ export { mountJuosan } from "./juosanMount.ts";
2
+ export { JUOSAN_PLAY_STYLE } from "./juosanStyle.ts";
3
+ export { JUOSAN_STRINGS } from "./juosanStrings.ts";
4
+ export type * from "./juosanPlay.types.ts";
@@ -0,0 +1,3 @@
1
+ export { mountJuosan } from "./juosanMount.js";
2
+ export { JUOSAN_PLAY_STYLE } from "./juosanStyle.js";
3
+ export { JUOSAN_STRINGS } from "./juosanStrings.js";
@@ -0,0 +1,4 @@
1
+ export declare const JUOSAN_MOST_SIDE = 16;
2
+ export declare const JUOSAN_MOST_NODES = 250000;
3
+ export declare const JUOSAN_MOST_ATTEMPTS = 500;
4
+ export declare const JUOSAN_LEVELS: readonly ["easy", "medium", "hard"];
@@ -0,0 +1,4 @@
1
+ export const JUOSAN_MOST_SIDE = 16;
2
+ export const JUOSAN_MOST_NODES = 250000;
3
+ export const JUOSAN_MOST_ATTEMPTS = 500;
4
+ export const JUOSAN_LEVELS = ["easy", "medium", "hard"];
@@ -0,0 +1,33 @@
1
+ /** A territory is a list of zero-based cell indexes and an absolute orientation difference. */
2
+ export type JuosanTerritory = {
3
+ cells: readonly number[];
4
+ difference: number | null;
5
+ };
6
+ export type JuosanBoard = {
7
+ width: number;
8
+ height: number;
9
+ territories: readonly JuosanTerritory[];
10
+ };
11
+ export type JuosanMark = 0 | 1 | 2;
12
+ export type JuosanLevel = "easy" | "medium" | "hard";
13
+ export type JuosanPuzzle = JuosanBoard & {
14
+ seed: number;
15
+ level: JuosanLevel;
16
+ solution: readonly JuosanMark[];
17
+ };
18
+ export type JuosanCheck = {
19
+ ok: boolean;
20
+ errors: readonly number[];
21
+ };
22
+ export type JuosanSolve = {
23
+ count: number;
24
+ solution: readonly JuosanMark[] | null;
25
+ complete: boolean;
26
+ nodes: number;
27
+ };
28
+ export type JuosanGame = {
29
+ board: JuosanBoard;
30
+ marks: readonly JuosanMark[];
31
+ history: readonly (readonly JuosanMark[])[];
32
+ helped: boolean;
33
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ import type { JuosanBoard, JuosanCheck, JuosanMark } from "./juosan.types.ts";
2
+ /** Whether territories partition a bounded board and each territory is connected. */
3
+ export declare function isJuosanBoard(value: unknown): value is JuosanBoard;
4
+ /** Returns the index of the territory containing a cell, or -1 when absent. */
5
+ export declare function juosanTerritoryAt(board: JuosanBoard, cell: number): number;
6
+ /** Checks public rules directly, independently of the solver and stored answer. */
7
+ export declare function checkJuosan(board: JuosanBoard, marks: readonly JuosanMark[]): JuosanCheck;
@@ -0,0 +1,102 @@
1
+ import { JUOSAN_MOST_SIDE } from "./juosan.constants.js";
2
+ /** Whether territories partition a bounded board and each territory is connected. */
3
+ export function isJuosanBoard(value) {
4
+ if (!value || typeof value !== "object")
5
+ return false;
6
+ const board = value;
7
+ if (!Number.isInteger(board.width) ||
8
+ board.width < 2 ||
9
+ board.width > JUOSAN_MOST_SIDE ||
10
+ !Number.isInteger(board.height) ||
11
+ board.height < 2 ||
12
+ board.height > JUOSAN_MOST_SIDE ||
13
+ !Array.isArray(board.territories)) {
14
+ return false;
15
+ }
16
+ const cellCount = board.width * board.height;
17
+ const occupied = new Set();
18
+ for (const territory of board.territories) {
19
+ if (!territory ||
20
+ !Array.isArray(territory.cells) ||
21
+ territory.cells.length === 0 ||
22
+ !(territory.difference === null ||
23
+ (Number.isInteger(territory.difference) &&
24
+ territory.difference >= 0 &&
25
+ territory.difference <= territory.cells.length))) {
26
+ return false;
27
+ }
28
+ for (const cell of territory.cells) {
29
+ if (!Number.isInteger(cell) ||
30
+ cell < 0 ||
31
+ cell >= cellCount ||
32
+ occupied.has(cell)) {
33
+ return false;
34
+ }
35
+ occupied.add(cell);
36
+ }
37
+ const members = new Set(territory.cells);
38
+ const reached = new Set([territory.cells[0]]);
39
+ const queue = [territory.cells[0]];
40
+ while (queue.length > 0) {
41
+ const cell = queue.pop();
42
+ const x = cell % board.width;
43
+ const y = Math.floor(cell / board.width);
44
+ const neighbors = [
45
+ x > 0 ? cell - 1 : -1,
46
+ x + 1 < board.width ? cell + 1 : -1,
47
+ y > 0 ? cell - board.width : -1,
48
+ y + 1 < board.height ? cell + board.width : -1,
49
+ ];
50
+ for (const neighbor of neighbors) {
51
+ if (members.has(neighbor) && !reached.has(neighbor)) {
52
+ reached.add(neighbor);
53
+ queue.push(neighbor);
54
+ }
55
+ }
56
+ }
57
+ if (reached.size !== members.size)
58
+ return false;
59
+ }
60
+ return occupied.size === cellCount;
61
+ }
62
+ /** Returns the index of the territory containing a cell, or -1 when absent. */
63
+ export function juosanTerritoryAt(board, cell) {
64
+ return board.territories.findIndex(territory => territory.cells.includes(cell));
65
+ }
66
+ /** Checks public rules directly, independently of the solver and stored answer. */
67
+ export function checkJuosan(board, marks) {
68
+ if (!isJuosanBoard(board))
69
+ throw new RangeError("Invalid Juosan board");
70
+ if (!Array.isArray(marks) ||
71
+ marks.length !== board.width * board.height ||
72
+ marks.some(mark => mark !== 1 && mark !== 2)) {
73
+ return { ok: false, errors: [-1] };
74
+ }
75
+ const errors = [];
76
+ board.territories.forEach((territory, index) => {
77
+ if (territory.difference === null)
78
+ return;
79
+ const horizontal = territory.cells.filter(cell => marks[cell] === 1).length;
80
+ const vertical = territory.cells.length - horizontal;
81
+ if (Math.abs(horizontal - vertical) !== territory.difference)
82
+ errors.push(index);
83
+ });
84
+ for (let y = 0; y < board.height; y += 1) {
85
+ for (let x = 0; x < board.width; x += 1) {
86
+ const cell = y * board.width + x;
87
+ if (x + 2 < board.width &&
88
+ marks[cell] === 2 &&
89
+ marks[cell + 1] === 2 &&
90
+ marks[cell + 2] === 2) {
91
+ errors.push(-2);
92
+ }
93
+ if (y + 2 < board.height &&
94
+ marks[cell] === 1 &&
95
+ marks[cell + board.width] === 1 &&
96
+ marks[cell + 2 * board.width] === 1) {
97
+ errors.push(-3);
98
+ }
99
+ }
100
+ }
101
+ return { ok: errors.length === 0, errors: [...new Set(errors)] };
102
+ }
@@ -0,0 +1,4 @@
1
+ import type { JuosanBoard } from "./juosan.types.ts";
2
+ import type { JuosanDrawOptions } from "./juosanPlay.types.ts";
3
+ /** Draws a public Juosan board as an SVG string. */
4
+ export declare function drawJuosan(board: JuosanBoard, options?: JuosanDrawOptions): string;
@@ -0,0 +1,49 @@
1
+ import { isJuosanBoard } from "./juosanBoard.js";
2
+ import { juosanWords } from "./juosanStrings.js";
3
+ /** Draws a public Juosan board as an SVG string. */
4
+ export function drawJuosan(board, options = {}) {
5
+ if (!isJuosanBoard(board))
6
+ throw new RangeError("Invalid Juosan board");
7
+ const cellSize = 48;
8
+ const words = juosanWords(options.language);
9
+ const palettes = {
10
+ ivory: ["var(--kz-paper,#fbf8f1)", "var(--kz-ink,#1f2320)", "var(--kz-grid,#cfc6b2)"],
11
+ wood: ["#e5cda6", "#493e2f", "#bca582"],
12
+ slate: ["#262a27", "#ece8dc", "#454a44"],
13
+ };
14
+ const [paper, ink, grid] = palettes[options.material ?? "ivory"];
15
+ const territoryByCell = Array(board.width * board.height).fill(-1);
16
+ board.territories.forEach((territory, index) => {
17
+ territory.cells.forEach(cell => {
18
+ territoryByCell[cell] = index;
19
+ });
20
+ });
21
+ const cells = territoryByCell.map((territoryIndex, cell) => {
22
+ const x = (cell % board.width) * cellSize;
23
+ const y = Math.floor(cell / board.width) * cellSize;
24
+ const territory = board.territories[territoryIndex];
25
+ const mark = options.marks?.[cell] ?? 0;
26
+ const selected = options.selected === cell;
27
+ const tile = options.pieces === "tiles"
28
+ ? `<rect x="${x + 10}" y="${y + 10}" width="28" height="28" rx="6" fill="${paper}" stroke="${grid}"/>`
29
+ : "";
30
+ const glyph = mark === 1 ? "—" : mark === 2 ? "|" : "";
31
+ const clue = territory.cells[0] === cell && territory.difference !== null
32
+ ? `<text x="${x + 5}" y="${y + 13}" fill="${ink}" font-size="10" font-weight="700" font-family="system-ui,sans-serif">${territory.difference}</text>`
33
+ : "";
34
+ const right = cell % board.width + 1 < board.width
35
+ ? territoryByCell[cell + 1]
36
+ : -1;
37
+ const below = cell + board.width < territoryByCell.length
38
+ ? territoryByCell[cell + board.width]
39
+ : -1;
40
+ const rightEdge = right !== territoryIndex
41
+ ? `<path d="M${x + cellSize} ${y}v${cellSize}" stroke="${ink}" stroke-width="3"/>`
42
+ : "";
43
+ const bottomEdge = below !== territoryIndex
44
+ ? `<path d="M${x} ${y + cellSize}h${cellSize}" stroke="${ink}" stroke-width="3"/>`
45
+ : "";
46
+ return `<g data-juosan-cell="${cell}"><rect x="${x}" y="${y}" width="${cellSize}" height="${cellSize}" fill="${selected ? ink : "transparent"}" fill-opacity="${selected ? ".12" : "1"}" stroke="${grid}" stroke-width="1"/>${tile}${clue}<text x="${x + 24}" y="${y + 27}" dominant-baseline="middle" text-anchor="middle" fill="${ink}" font-size="29" font-weight="700" font-family="system-ui,sans-serif">${glyph}</text>${rightEdge}${bottomEdge}</g>`;
47
+ }).join("");
48
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 ${board.width * cellSize + 4} ${board.height * cellSize + 4}" role="img" aria-label="${words.title}" style="display:block;width:100%;height:auto;background:${paper}">${cells}<rect width="${board.width * cellSize}" height="${board.height * cellSize}" fill="none" stroke="var(--kz-frame,#a98954)" stroke-width="2"/></svg>`;
49
+ }
@@ -0,0 +1,20 @@
1
+ import type { JuosanBoard, JuosanGame, JuosanMark } from "./juosan.types.ts";
2
+ /** Starts a game with a copy of its public board and no generated answer. */
3
+ export declare function newJuosan(board: JuosanBoard): JuosanGame;
4
+ /** Writes a mark and records the previous state; invalid or unchanged moves are ignored. */
5
+ export declare function setJuosanMark(game: JuosanGame, cell: number, mark: JuosanMark): JuosanGame;
6
+ /** Restores the preceding mark state, if one exists. */
7
+ export declare function undoJuosan(game: JuosanGame): JuosanGame;
8
+ /** Clears the board while retaining whether the player used a hint. */
9
+ export declare function restartJuosan(game: JuosanGame): JuosanGame;
10
+ /** Whether every cell is filled and the independent checker accepts the board. */
11
+ export declare function juosanFinished(game: JuosanGame): boolean;
12
+ /** Returns one next mark only when the public clues prove a unique answer. */
13
+ export declare function hintJuosan(game: JuosanGame): {
14
+ cell: number;
15
+ mark: JuosanMark;
16
+ } | null;
17
+ /** Serializes public clues, placements, and assisted status. */
18
+ export declare function encodeJuosan(game: JuosanGame): string;
19
+ /** Reads a bounded public save; malformed or incompatible data returns null. */
20
+ export declare function decodeJuosan(code: string): JuosanGame | null;
@@ -0,0 +1,86 @@
1
+ import { checkJuosan, isJuosanBoard } from "./juosanBoard.js";
2
+ import { solveJuosan } from "./juosanSolve.js";
3
+ /** Starts a game with a copy of its public board and no generated answer. */
4
+ export function newJuosan(board) {
5
+ if (!isJuosanBoard(board))
6
+ throw new RangeError("Invalid Juosan board");
7
+ return {
8
+ board: {
9
+ width: board.width,
10
+ height: board.height,
11
+ territories: board.territories.map(territory => ({
12
+ cells: [...territory.cells],
13
+ difference: territory.difference,
14
+ })),
15
+ },
16
+ marks: Array(board.width * board.height).fill(0),
17
+ history: [],
18
+ helped: false,
19
+ };
20
+ }
21
+ /** Writes a mark and records the previous state; invalid or unchanged moves are ignored. */
22
+ export function setJuosanMark(game, cell, mark) {
23
+ if (!Number.isInteger(cell) ||
24
+ cell < 0 ||
25
+ cell >= game.marks.length ||
26
+ (mark !== 1 && mark !== 2) ||
27
+ game.marks[cell] === mark) {
28
+ return game;
29
+ }
30
+ const marks = [...game.marks];
31
+ marks[cell] = mark;
32
+ return { ...game, marks, history: [...game.history, [...game.marks]] };
33
+ }
34
+ /** Restores the preceding mark state, if one exists. */
35
+ export function undoJuosan(game) {
36
+ const previous = game.history.at(-1);
37
+ return previous
38
+ ? { ...game, marks: [...previous], history: game.history.slice(0, -1) }
39
+ : game;
40
+ }
41
+ /** Clears the board while retaining whether the player used a hint. */
42
+ export function restartJuosan(game) {
43
+ return { ...newJuosan(game.board), helped: game.helped };
44
+ }
45
+ /** Whether every cell is filled and the independent checker accepts the board. */
46
+ export function juosanFinished(game) {
47
+ return (game.marks.every(mark => mark !== 0) &&
48
+ checkJuosan(game.board, game.marks).ok);
49
+ }
50
+ /** Returns one next mark only when the public clues prove a unique answer. */
51
+ export function hintJuosan(game) {
52
+ const counted = solveJuosan(game.board, 2);
53
+ if (!counted.complete || counted.count !== 1 || !counted.solution)
54
+ return null;
55
+ const cell = game.marks.findIndex((mark, index) => mark !== counted.solution[index]);
56
+ return cell < 0 ? null : { cell, mark: counted.solution[cell] };
57
+ }
58
+ /** Serializes public clues, placements, and assisted status. */
59
+ export function encodeJuosan(game) {
60
+ return JSON.stringify({
61
+ version: 1,
62
+ board: game.board,
63
+ marks: game.marks,
64
+ helped: game.helped,
65
+ });
66
+ }
67
+ /** Reads a bounded public save; malformed or incompatible data returns null. */
68
+ export function decodeJuosan(code) {
69
+ try {
70
+ if (code.length > 40000)
71
+ return null;
72
+ const value = JSON.parse(code);
73
+ if (value.version !== 1 ||
74
+ !isJuosanBoard(value.board) ||
75
+ !Array.isArray(value.marks) ||
76
+ value.marks.length !== value.board.width * value.board.height ||
77
+ value.marks.some((mark) => mark !== 0 && mark !== 1 && mark !== 2) ||
78
+ typeof value.helped !== "boolean") {
79
+ return null;
80
+ }
81
+ return { ...newJuosan(value.board), marks: [...value.marks], helped: value.helped };
82
+ }
83
+ catch {
84
+ return null;
85
+ }
86
+ }
@@ -0,0 +1,3 @@
1
+ import type { JuosanLevel, JuosanPuzzle } from "./juosan.types.ts";
2
+ /** Makes an original small training puzzle and returns it only after uniqueness is proved. */
3
+ export declare function generateJuosan(width?: number, height?: number, level?: JuosanLevel, seed?: number): JuosanPuzzle;
@@ -0,0 +1,31 @@
1
+ import { JUOSAN_LEVELS, JUOSAN_MOST_NODES } from "./juosan.constants.js";
2
+ import { isJuosanBoard } from "./juosanBoard.js";
3
+ import { solveJuosan } from "./juosanSolve.js";
4
+ import { isKazuSeed } from "./random.js";
5
+ /** Makes an original small training puzzle and returns it only after uniqueness is proved. */
6
+ export function generateJuosan(width = 3, height = 2, level = "medium", seed = 1) {
7
+ const supported = (width === 3 && height === 2) || (width === 2 && height === 3);
8
+ if (!isKazuSeed(seed) || !JUOSAN_LEVELS.includes(level) || !supported) {
9
+ throw new RangeError("Juosan generation currently supports 3 × 2 and 2 × 3 boards");
10
+ }
11
+ const mark = width > height ? 1 : 2;
12
+ const territories = makeTrainingTerritories(width, height, seed);
13
+ const board = { width, height, territories };
14
+ if (!isJuosanBoard(board))
15
+ throw new RangeError("Invalid Juosan board");
16
+ const counted = solveJuosan(board, 2, JUOSAN_MOST_NODES);
17
+ if (!counted.complete ||
18
+ counted.count !== 1 ||
19
+ !counted.solution ||
20
+ counted.solution.some(value => value !== mark)) {
21
+ throw new Error("The generated Juosan did not pass its uniqueness proof");
22
+ }
23
+ return { ...board, seed, level, solution: counted.solution };
24
+ }
25
+ function makeTrainingTerritories(width, height, seed) {
26
+ const split = seed % 2 === 0;
27
+ const cells = width > height
28
+ ? (split ? [[0, 1, 2], [3, 4, 5]] : [[0, 1, 2, 3, 4, 5]])
29
+ : (split ? [[0, 2, 4], [1, 3, 5]] : [[0, 1, 2, 3, 4, 5]]);
30
+ return cells.map(group => ({ cells: group, difference: group.length }));
31
+ }
@@ -0,0 +1,3 @@
1
+ import type { JuosanMount, JuosanMountOptions } from "./juosanPlay.types.ts";
2
+ /** Mounts a touch, mouse, and keyboard player into an existing page element. */
3
+ export declare function mountJuosan(host: HTMLElement, initial: JuosanMountOptions): JuosanMount;
@@ -0,0 +1,206 @@
1
+ import { checkJuosan } from "./juosanBoard.js";
2
+ import { decodeJuosan, encodeJuosan, hintJuosan, juosanFinished, newJuosan, restartJuosan, setJuosanMark, undoJuosan, } from "./juosanGame.js";
3
+ import { drawJuosan } from "./juosanDraw.js";
4
+ import { KAZU_STYLE } from "./style.js";
5
+ import { JUOSAN_PLAY_STYLE } from "./juosanStyle.js";
6
+ import { juosanWords } from "./juosanStrings.js";
7
+ /** Mounts a touch, mouse, and keyboard player into an existing page element. */
8
+ export function mountJuosan(host, initial) {
9
+ const doc = host.ownerDocument;
10
+ let options = { ...initial };
11
+ let game = initial.progress ? decodeJuosan(initial.progress) : null;
12
+ game ?? (game = newJuosan(initial.board));
13
+ let selected = 0;
14
+ let message = "";
15
+ let finished = juosanFinished(game);
16
+ let destroyed = false;
17
+ const root = doc.createElement("div");
18
+ const style = doc.createElement("style");
19
+ const board = doc.createElement("div");
20
+ const status = doc.createElement("p");
21
+ const tools = doc.createElement("div");
22
+ const help = doc.createElement("p");
23
+ root.className = "js-root";
24
+ style.textContent = KAZU_STYLE.replace(/\.kazu/g, ".js-palette") + JUOSAN_PLAY_STYLE;
25
+ board.className = "js-board";
26
+ status.className = "js-status";
27
+ status.setAttribute("role", "status");
28
+ tools.className = "js-tools";
29
+ help.className = "js-help";
30
+ root.append(style, board, status, tools, help);
31
+ host.append(root);
32
+ const copyGame = () => ({
33
+ ...game,
34
+ board: {
35
+ ...game.board,
36
+ territories: game.board.territories.map(territory => ({
37
+ ...territory,
38
+ cells: [...territory.cells],
39
+ })),
40
+ },
41
+ marks: [...game.marks],
42
+ history: game.history.map(turn => [...turn]),
43
+ });
44
+ const words = () => juosanWords(options.language);
45
+ const addButton = (label, action, disabled = false) => {
46
+ const button = doc.createElement("button");
47
+ button.type = "button";
48
+ button.textContent = label;
49
+ button.disabled = disabled;
50
+ button.onclick = action;
51
+ tools.append(button);
52
+ };
53
+ const changed = () => {
54
+ message = "";
55
+ const wasFinished = finished;
56
+ finished = juosanFinished(game);
57
+ render();
58
+ options.onChange?.(copyGame());
59
+ if (finished && !wasFinished)
60
+ options.onFinish?.(copyGame());
61
+ };
62
+ const apply = (mark) => {
63
+ if (finished)
64
+ return;
65
+ if (mark === 0) {
66
+ if (!game.marks[selected])
67
+ return;
68
+ const marks = [...game.marks];
69
+ marks[selected] = 0;
70
+ game = { ...game, marks, history: [...game.history, [...game.marks]] };
71
+ }
72
+ else {
73
+ game = setJuosanMark(game, selected, mark);
74
+ }
75
+ changed();
76
+ };
77
+ const restart = () => {
78
+ game = restartJuosan(game);
79
+ finished = false;
80
+ changed();
81
+ };
82
+ const moveSelection = (cell) => {
83
+ selected = Math.max(0, Math.min(game.marks.length - 1, cell));
84
+ render();
85
+ board.querySelector(`[data-cell="${selected}"]`)?.focus();
86
+ };
87
+ const onCellKey = (event, cell) => {
88
+ const step = {
89
+ ArrowLeft: -1,
90
+ ArrowRight: 1,
91
+ ArrowUp: -game.board.width,
92
+ ArrowDown: game.board.width,
93
+ }[event.key];
94
+ if (step !== undefined) {
95
+ event.preventDefault();
96
+ moveSelection(cell + step);
97
+ }
98
+ else if (event.key.toLowerCase() === "h") {
99
+ event.preventDefault();
100
+ selected = cell;
101
+ apply(1);
102
+ }
103
+ else if (event.key.toLowerCase() === "v") {
104
+ event.preventDefault();
105
+ selected = cell;
106
+ apply(2);
107
+ }
108
+ else if (event.key === "Delete" || event.key === "Backspace") {
109
+ event.preventDefault();
110
+ selected = cell;
111
+ apply(0);
112
+ }
113
+ };
114
+ const render = () => {
115
+ if (destroyed)
116
+ return;
117
+ const { board: puzzleBoard } = game;
118
+ const hadFocus = board.contains(doc.activeElement);
119
+ board.style.aspectRatio = `${puzzleBoard.width}/${puzzleBoard.height}`;
120
+ board.style.setProperty("--juosan-width", String(puzzleBoard.width));
121
+ board.style.setProperty("--juosan-height", String(puzzleBoard.height));
122
+ board.classList.toggle("js-palette", (options.material ?? "ivory") === "ivory");
123
+ board.innerHTML = drawJuosan(puzzleBoard, {
124
+ ...options,
125
+ marks: game.marks,
126
+ selected,
127
+ });
128
+ const cellButtons = doc.createElement("div");
129
+ cellButtons.className = "js-cells";
130
+ for (let cell = 0; cell < game.marks.length; cell += 1) {
131
+ const button = doc.createElement("button");
132
+ button.type = "button";
133
+ button.className = "js-cell";
134
+ button.tabIndex = cell === selected ? 0 : -1;
135
+ button.dataset.cell = String(cell);
136
+ button.setAttribute("aria-label", markLabel(cell));
137
+ button.addEventListener("click", () => {
138
+ selected = cell;
139
+ const next = game.marks[cell] === 0 ? 1 : game.marks[cell] === 1 ? 2 : 0;
140
+ apply(next);
141
+ });
142
+ button.addEventListener("keydown", event => onCellKey(event, cell));
143
+ cellButtons.append(button);
144
+ }
145
+ board.append(cellButtons);
146
+ if (hadFocus)
147
+ board.querySelector(`[data-cell="${selected}"]`)?.focus();
148
+ status.textContent = juosanFinished(game)
149
+ ? game.helped ? words().assisted : words().won
150
+ : message || words().ready;
151
+ help.textContent = words().help;
152
+ tools.replaceChildren();
153
+ addButton(words().undo, () => {
154
+ game = undoJuosan(game);
155
+ finished = juosanFinished(game);
156
+ changed();
157
+ }, game.history.length === 0);
158
+ addButton(words().hint, giveHint, finished);
159
+ addButton(words().check, checkCurrent);
160
+ addButton(words().restart, restart);
161
+ };
162
+ const markLabel = (cell) => {
163
+ const mark = game.marks[cell];
164
+ const kind = mark === 1
165
+ ? words().horizontal
166
+ : mark === 2 ? words().vertical : words().blank;
167
+ const row = Math.floor(cell / game.board.width) + 1;
168
+ const column = cell % game.board.width + 1;
169
+ return `${words().row} ${row}, ${words().column} ${column}: ${kind}`;
170
+ };
171
+ const giveHint = () => {
172
+ const hint = hintJuosan(game);
173
+ if (!hint) {
174
+ message = words().wrong;
175
+ render();
176
+ return;
177
+ }
178
+ selected = hint.cell;
179
+ game = setJuosanMark(game, hint.cell, hint.mark);
180
+ game = { ...game, helped: true };
181
+ changed();
182
+ message = words().hintWhy;
183
+ render();
184
+ };
185
+ const checkCurrent = () => {
186
+ const complete = game.marks.every(mark => mark !== 0);
187
+ message = complete && checkJuosan(game.board, game.marks).ok
188
+ ? words().checked
189
+ : words().wrong;
190
+ render();
191
+ };
192
+ render();
193
+ return {
194
+ game: copyGame,
195
+ progress: () => encodeJuosan(game),
196
+ restart,
197
+ set: (next) => {
198
+ options = { ...options, ...next };
199
+ render();
200
+ },
201
+ destroy: () => {
202
+ destroyed = true;
203
+ root.remove();
204
+ },
205
+ };
206
+ }