@johnmorrisdotca/kazu 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (323) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +379 -7
  3. package/dist/akari-draw-entry.d.ts +2 -0
  4. package/dist/akari-draw-entry.js +1 -0
  5. package/dist/akari-entry.d.ts +12 -0
  6. package/dist/akari-entry.js +10 -0
  7. package/dist/akari-play-entry.d.ts +2 -0
  8. package/dist/akari-play-entry.js +1 -0
  9. package/dist/akari.constants.d.ts +2 -0
  10. package/dist/akari.constants.js +2 -0
  11. package/dist/akari.types.d.ts +33 -0
  12. package/dist/akari.types.js +1 -0
  13. package/dist/akariBoard.d.ts +11 -0
  14. package/dist/akariBoard.js +96 -0
  15. package/dist/akariDraw.d.ts +4 -0
  16. package/dist/akariDraw.js +38 -0
  17. package/dist/akariGame.d.ts +15 -0
  18. package/dist/akariGame.js +71 -0
  19. package/dist/akariGenerate.d.ts +7 -0
  20. package/dist/akariGenerate.js +78 -0
  21. package/dist/akariMount.d.ts +3 -0
  22. package/dist/akariMount.js +214 -0
  23. package/dist/akariPlay.types.d.ts +25 -0
  24. package/dist/akariPlay.types.js +1 -0
  25. package/dist/akariSolve.d.ts +6 -0
  26. package/dist/akariSolve.js +106 -0
  27. package/dist/akariStrings.d.ts +98 -0
  28. package/dist/akariStrings.js +53 -0
  29. package/dist/akariStyle.d.ts +1 -0
  30. package/dist/akariStyle.js +9 -0
  31. package/dist/fillomino-draw-entry.d.ts +2 -0
  32. package/dist/fillomino-draw-entry.js +1 -0
  33. package/dist/fillomino-entry.d.ts +8 -0
  34. package/dist/fillomino-entry.js +7 -0
  35. package/dist/fillomino-play-entry.d.ts +4 -0
  36. package/dist/fillomino-play-entry.js +3 -0
  37. package/dist/fillomino.constants.d.ts +5 -0
  38. package/dist/fillomino.constants.js +5 -0
  39. package/dist/fillomino.types.d.ts +30 -0
  40. package/dist/fillomino.types.js +1 -0
  41. package/dist/fillominoBoard.d.ts +6 -0
  42. package/dist/fillominoBoard.js +84 -0
  43. package/dist/fillominoDraw.d.ts +3 -0
  44. package/dist/fillominoDraw.js +31 -0
  45. package/dist/fillominoGame.d.ts +13 -0
  46. package/dist/fillominoGame.js +65 -0
  47. package/dist/fillominoGenerate.d.ts +3 -0
  48. package/dist/fillominoGenerate.js +125 -0
  49. package/dist/fillominoMount.d.ts +2 -0
  50. package/dist/fillominoMount.js +211 -0
  51. package/dist/fillominoPlay.types.d.ts +25 -0
  52. package/dist/fillominoPlay.types.js +1 -0
  53. package/dist/fillominoSolve.d.ts +6 -0
  54. package/dist/fillominoSolve.js +111 -0
  55. package/dist/fillominoStrings.d.ts +102 -0
  56. package/dist/fillominoStrings.js +13 -0
  57. package/dist/fillominoStyle.d.ts +1 -0
  58. package/dist/fillominoStyle.js +13 -0
  59. package/dist/fillominoWorker.d.ts +1 -0
  60. package/dist/fillominoWorker.js +10 -0
  61. package/dist/heyawake-draw-entry.d.ts +2 -0
  62. package/dist/heyawake-draw-entry.js +1 -0
  63. package/dist/heyawake-entry.d.ts +13 -0
  64. package/dist/heyawake-entry.js +11 -0
  65. package/dist/heyawake-play-entry.d.ts +2 -0
  66. package/dist/heyawake-play-entry.js +1 -0
  67. package/dist/heyawake.constants.d.ts +5 -0
  68. package/dist/heyawake.constants.js +5 -0
  69. package/dist/heyawake.types.d.ts +35 -0
  70. package/dist/heyawake.types.js +1 -0
  71. package/dist/heyawakeBoard.d.ts +7 -0
  72. package/dist/heyawakeBoard.js +116 -0
  73. package/dist/heyawakeDraw.d.ts +3 -0
  74. package/dist/heyawakeDraw.js +27 -0
  75. package/dist/heyawakeGame.d.ts +12 -0
  76. package/dist/heyawakeGame.js +51 -0
  77. package/dist/heyawakeGenerate.d.ts +2 -0
  78. package/dist/heyawakeGenerate.js +114 -0
  79. package/dist/heyawakeMount.d.ts +2 -0
  80. package/dist/heyawakeMount.js +106 -0
  81. package/dist/heyawakePlay.types.d.ts +24 -0
  82. package/dist/heyawakePlay.types.js +1 -0
  83. package/dist/heyawakeSolve.d.ts +5 -0
  84. package/dist/heyawakeSolve.js +52 -0
  85. package/dist/heyawakeStrings.d.ts +19 -0
  86. package/dist/heyawakeStrings.js +7 -0
  87. package/dist/heyawakeStyle.d.ts +1 -0
  88. package/dist/heyawakeStyle.js +2 -0
  89. package/dist/heyawakeWorker.d.ts +1 -0
  90. package/dist/heyawakeWorker.js +10 -0
  91. package/dist/hitori-draw-entry.d.ts +2 -0
  92. package/dist/hitori-draw-entry.js +1 -0
  93. package/dist/hitori-entry.d.ts +6 -0
  94. package/dist/hitori-entry.js +6 -0
  95. package/dist/hitori-play-entry.d.ts +4 -0
  96. package/dist/hitori-play-entry.js +3 -0
  97. package/dist/hitori.constants.d.ts +2 -0
  98. package/dist/hitori.constants.js +2 -0
  99. package/dist/hitori.types.d.ts +20 -0
  100. package/dist/hitori.types.js +1 -0
  101. package/dist/hitoriBoard.d.ts +8 -0
  102. package/dist/hitoriBoard.js +72 -0
  103. package/dist/hitoriDraw.d.ts +4 -0
  104. package/dist/hitoriDraw.js +25 -0
  105. package/dist/hitoriGame.d.ts +10 -0
  106. package/dist/hitoriGame.js +48 -0
  107. package/dist/hitoriGenerate.d.ts +3 -0
  108. package/dist/hitoriGenerate.js +163 -0
  109. package/dist/hitoriMount.d.ts +3 -0
  110. package/dist/hitoriMount.js +111 -0
  111. package/dist/hitoriPlay.types.d.ts +25 -0
  112. package/dist/hitoriPlay.types.js +1 -0
  113. package/dist/hitoriSolve.d.ts +6 -0
  114. package/dist/hitoriSolve.js +69 -0
  115. package/dist/hitoriStrings.d.ts +70 -0
  116. package/dist/hitoriStrings.js +17 -0
  117. package/dist/hitoriStyle.d.ts +1 -0
  118. package/dist/hitoriStyle.js +9 -0
  119. package/dist/index.d.ts +11 -0
  120. package/dist/index.js +11 -0
  121. package/dist/juosan-draw-entry.d.ts +2 -0
  122. package/dist/juosan-draw-entry.js +1 -0
  123. package/dist/juosan-entry.d.ts +11 -0
  124. package/dist/juosan-entry.js +10 -0
  125. package/dist/juosan-play-entry.d.ts +4 -0
  126. package/dist/juosan-play-entry.js +3 -0
  127. package/dist/juosan.constants.d.ts +4 -0
  128. package/dist/juosan.constants.js +4 -0
  129. package/dist/juosan.types.d.ts +33 -0
  130. package/dist/juosan.types.js +1 -0
  131. package/dist/juosanBoard.d.ts +7 -0
  132. package/dist/juosanBoard.js +102 -0
  133. package/dist/juosanDraw.d.ts +4 -0
  134. package/dist/juosanDraw.js +49 -0
  135. package/dist/juosanGame.d.ts +20 -0
  136. package/dist/juosanGame.js +86 -0
  137. package/dist/juosanGenerate.d.ts +3 -0
  138. package/dist/juosanGenerate.js +31 -0
  139. package/dist/juosanMount.d.ts +3 -0
  140. package/dist/juosanMount.js +206 -0
  141. package/dist/juosanPlay.types.d.ts +24 -0
  142. package/dist/juosanPlay.types.js +1 -0
  143. package/dist/juosanSolve.d.ts +3 -0
  144. package/dist/juosanSolve.js +82 -0
  145. package/dist/juosanStrings.d.ts +77 -0
  146. package/dist/juosanStrings.js +43 -0
  147. package/dist/juosanStyle.d.ts +1 -0
  148. package/dist/juosanStyle.js +1 -0
  149. package/dist/kakuro-draw-entry.d.ts +2 -0
  150. package/dist/kakuro-draw-entry.js +1 -0
  151. package/dist/kakuro-entry.d.ts +12 -0
  152. package/dist/kakuro-entry.js +10 -0
  153. package/dist/kakuro-play-entry.d.ts +4 -0
  154. package/dist/kakuro-play-entry.js +3 -0
  155. package/dist/kakuro.constants.d.ts +6 -0
  156. package/dist/kakuro.constants.js +6 -0
  157. package/dist/kakuro.types.d.ts +46 -0
  158. package/dist/kakuro.types.js +1 -0
  159. package/dist/kakuroBoard.d.ts +8 -0
  160. package/dist/kakuroBoard.js +117 -0
  161. package/dist/kakuroDraw.d.ts +10 -0
  162. package/dist/kakuroDraw.js +21 -0
  163. package/dist/kakuroGame.d.ts +12 -0
  164. package/dist/kakuroGame.js +74 -0
  165. package/dist/kakuroGenerate.d.ts +3 -0
  166. package/dist/kakuroGenerate.js +130 -0
  167. package/dist/kakuroMount.d.ts +2 -0
  168. package/dist/kakuroMount.js +109 -0
  169. package/dist/kakuroPlay.types.d.ts +20 -0
  170. package/dist/kakuroPlay.types.js +1 -0
  171. package/dist/kakuroSolve.d.ts +6 -0
  172. package/dist/kakuroSolve.js +85 -0
  173. package/dist/kakuroStrings.d.ts +82 -0
  174. package/dist/kakuroStrings.js +5 -0
  175. package/dist/kakuroStyle.d.ts +1 -0
  176. package/dist/kakuroStyle.js +1 -0
  177. package/dist/masyu-draw-entry.d.ts +2 -0
  178. package/dist/masyu-draw-entry.js +1 -0
  179. package/dist/masyu-entry.d.ts +6 -0
  180. package/dist/masyu-entry.js +6 -0
  181. package/dist/masyu-play-entry.d.ts +4 -0
  182. package/dist/masyu-play-entry.js +3 -0
  183. package/dist/masyu.constants.d.ts +2 -0
  184. package/dist/masyu.constants.js +2 -0
  185. package/dist/masyu.types.d.ts +46 -0
  186. package/dist/masyu.types.js +1 -0
  187. package/dist/masyuBoard.d.ts +10 -0
  188. package/dist/masyuBoard.js +88 -0
  189. package/dist/masyuDraw.d.ts +2 -0
  190. package/dist/masyuDraw.js +33 -0
  191. package/dist/masyuGame.d.ts +9 -0
  192. package/dist/masyuGame.js +46 -0
  193. package/dist/masyuGenerate.d.ts +3 -0
  194. package/dist/masyuGenerate.js +56 -0
  195. package/dist/masyuMount.d.ts +2 -0
  196. package/dist/masyuMount.js +118 -0
  197. package/dist/masyuPlay.types.d.ts +1 -0
  198. package/dist/masyuPlay.types.js +1 -0
  199. package/dist/masyuSolve.d.ts +6 -0
  200. package/dist/masyuSolve.js +88 -0
  201. package/dist/masyuStrings.d.ts +28 -0
  202. package/dist/masyuStrings.js +4 -0
  203. package/dist/masyuStyle.d.ts +1 -0
  204. package/dist/masyuStyle.js +11 -0
  205. package/dist/nurikabe-draw-entry.d.ts +2 -0
  206. package/dist/nurikabe-draw-entry.js +1 -0
  207. package/dist/nurikabe-entry.d.ts +6 -0
  208. package/dist/nurikabe-entry.js +6 -0
  209. package/dist/nurikabe-play-entry.d.ts +4 -0
  210. package/dist/nurikabe-play-entry.js +3 -0
  211. package/dist/nurikabe.constants.d.ts +2 -0
  212. package/dist/nurikabe.constants.js +2 -0
  213. package/dist/nurikabe.types.d.ts +20 -0
  214. package/dist/nurikabe.types.js +1 -0
  215. package/dist/nurikabeBoard.d.ts +8 -0
  216. package/dist/nurikabeBoard.js +72 -0
  217. package/dist/nurikabeDraw.d.ts +4 -0
  218. package/dist/nurikabeDraw.js +15 -0
  219. package/dist/nurikabeGame.d.ts +8 -0
  220. package/dist/nurikabeGame.js +40 -0
  221. package/dist/nurikabeGenerate.d.ts +3 -0
  222. package/dist/nurikabeGenerate.js +31 -0
  223. package/dist/nurikabeMount.d.ts +3 -0
  224. package/dist/nurikabeMount.js +99 -0
  225. package/dist/nurikabePlay.types.d.ts +25 -0
  226. package/dist/nurikabePlay.types.js +1 -0
  227. package/dist/nurikabeSolve.d.ts +6 -0
  228. package/dist/nurikabeSolve.js +75 -0
  229. package/dist/nurikabeStrings.d.ts +70 -0
  230. package/dist/nurikabeStrings.js +5 -0
  231. package/dist/nurikabeStyle.d.ts +1 -0
  232. package/dist/nurikabeStyle.js +9 -0
  233. package/dist/ripple-draw-entry.d.ts +2 -0
  234. package/dist/ripple-draw-entry.js +1 -0
  235. package/dist/ripple-entry.d.ts +12 -0
  236. package/dist/ripple-entry.js +11 -0
  237. package/dist/ripple-play-entry.d.ts +4 -0
  238. package/dist/ripple-play-entry.js +3 -0
  239. package/dist/ripple.constants.d.ts +4 -0
  240. package/dist/ripple.constants.js +4 -0
  241. package/dist/ripple.types.d.ts +34 -0
  242. package/dist/ripple.types.js +1 -0
  243. package/dist/rippleBoard.d.ts +7 -0
  244. package/dist/rippleBoard.js +99 -0
  245. package/dist/rippleDraw.d.ts +3 -0
  246. package/dist/rippleDraw.js +32 -0
  247. package/dist/rippleGame.d.ts +13 -0
  248. package/dist/rippleGame.js +85 -0
  249. package/dist/rippleGenerate.d.ts +3 -0
  250. package/dist/rippleGenerate.js +57 -0
  251. package/dist/rippleMount.d.ts +2 -0
  252. package/dist/rippleMount.js +179 -0
  253. package/dist/ripplePlay.types.d.ts +23 -0
  254. package/dist/ripplePlay.types.js +1 -0
  255. package/dist/rippleSolve.d.ts +6 -0
  256. package/dist/rippleSolve.js +83 -0
  257. package/dist/rippleStrings.d.ts +86 -0
  258. package/dist/rippleStrings.js +5 -0
  259. package/dist/rippleStyle.d.ts +1 -0
  260. package/dist/rippleStyle.js +1 -0
  261. package/dist/shikaku-entry.d.ts +1 -0
  262. package/dist/shikaku-entry.js +1 -0
  263. package/dist/shikakuPacks.d.ts +27 -0
  264. package/dist/shikakuPacks.js +26 -0
  265. package/dist/slitherlink-draw-entry.d.ts +2 -0
  266. package/dist/slitherlink-draw-entry.js +1 -0
  267. package/dist/slitherlink-entry.d.ts +11 -0
  268. package/dist/slitherlink-entry.js +9 -0
  269. package/dist/slitherlink-play-entry.d.ts +4 -0
  270. package/dist/slitherlink-play-entry.js +3 -0
  271. package/dist/slitherlink.constants.d.ts +2 -0
  272. package/dist/slitherlink.constants.js +2 -0
  273. package/dist/slitherlink.types.d.ts +34 -0
  274. package/dist/slitherlink.types.js +1 -0
  275. package/dist/slitherlinkBoard.d.ts +12 -0
  276. package/dist/slitherlinkBoard.js +132 -0
  277. package/dist/slitherlinkDraw.d.ts +4 -0
  278. package/dist/slitherlinkDraw.js +51 -0
  279. package/dist/slitherlinkGame.d.ts +8 -0
  280. package/dist/slitherlinkGame.js +63 -0
  281. package/dist/slitherlinkGenerate.d.ts +3 -0
  282. package/dist/slitherlinkGenerate.js +101 -0
  283. package/dist/slitherlinkMount.d.ts +3 -0
  284. package/dist/slitherlinkMount.js +267 -0
  285. package/dist/slitherlinkPlay.types.d.ts +23 -0
  286. package/dist/slitherlinkPlay.types.js +1 -0
  287. package/dist/slitherlinkSolve.d.ts +6 -0
  288. package/dist/slitherlinkSolve.js +127 -0
  289. package/dist/slitherlinkStrings.d.ts +86 -0
  290. package/dist/slitherlinkStrings.js +47 -0
  291. package/dist/slitherlinkStyle.d.ts +1 -0
  292. package/dist/slitherlinkStyle.js +18 -0
  293. package/dist/version.d.ts +1 -1
  294. package/dist/version.js +1 -1
  295. package/dist/yajilin-draw-entry.d.ts +2 -0
  296. package/dist/yajilin-draw-entry.js +1 -0
  297. package/dist/yajilin-entry.d.ts +6 -0
  298. package/dist/yajilin-entry.js +6 -0
  299. package/dist/yajilin-play-entry.d.ts +4 -0
  300. package/dist/yajilin-play-entry.js +3 -0
  301. package/dist/yajilin.constants.d.ts +2 -0
  302. package/dist/yajilin.constants.js +2 -0
  303. package/dist/yajilin.types.d.ts +63 -0
  304. package/dist/yajilin.types.js +1 -0
  305. package/dist/yajilinBoard.d.ts +9 -0
  306. package/dist/yajilinBoard.js +91 -0
  307. package/dist/yajilinDraw.d.ts +2 -0
  308. package/dist/yajilinDraw.js +33 -0
  309. package/dist/yajilinGame.d.ts +15 -0
  310. package/dist/yajilinGame.js +58 -0
  311. package/dist/yajilinGenerate.d.ts +3 -0
  312. package/dist/yajilinGenerate.js +224 -0
  313. package/dist/yajilinMount.d.ts +2 -0
  314. package/dist/yajilinMount.js +162 -0
  315. package/dist/yajilinPlay.types.d.ts +1 -0
  316. package/dist/yajilinPlay.types.js +1 -0
  317. package/dist/yajilinSolve.d.ts +6 -0
  318. package/dist/yajilinSolve.js +82 -0
  319. package/dist/yajilinStrings.d.ts +32 -0
  320. package/dist/yajilinStrings.js +4 -0
  321. package/dist/yajilinStyle.d.ts +1 -0
  322. package/dist/yajilinStyle.js +12 -0
  323. package/package.json +180 -3
@@ -0,0 +1,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
+ }
@@ -0,0 +1,24 @@
1
+ import type { JuosanBoard, JuosanGame } from "./juosan.types.ts";
2
+ export type JuosanMaterial = "ivory" | "wood" | "slate";
3
+ export type JuosanPieces = "ink" | "tiles";
4
+ export type JuosanLanguage = "en" | "ja";
5
+ export type JuosanDrawOptions = {
6
+ marks?: JuosanGame["marks"];
7
+ selected?: number | null;
8
+ material?: JuosanMaterial;
9
+ pieces?: JuosanPieces;
10
+ language?: JuosanLanguage;
11
+ };
12
+ export type JuosanMountOptions = JuosanDrawOptions & {
13
+ board: JuosanBoard;
14
+ progress?: string;
15
+ onChange?: (game: JuosanGame) => void;
16
+ onFinish?: (game: JuosanGame) => void;
17
+ };
18
+ export type JuosanMount = {
19
+ game: () => JuosanGame;
20
+ progress: () => string;
21
+ set: (options: JuosanDrawOptions) => void;
22
+ restart: () => void;
23
+ destroy: () => void;
24
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import type { JuosanBoard, JuosanSolve } from "./juosan.types.ts";
2
+ /** Counts labeled solutions up to a limit; `complete` distinguishes exhaustion from cutoff. */
3
+ export declare function solveJuosan(board: JuosanBoard, limit?: number, maxNodes?: number): JuosanSolve;
@@ -0,0 +1,82 @@
1
+ import { JUOSAN_MOST_NODES } from "./juosan.constants.js";
2
+ import { checkJuosan, isJuosanBoard } from "./juosanBoard.js";
3
+ /** Counts labeled solutions up to a limit; `complete` distinguishes exhaustion from cutoff. */
4
+ export function solveJuosan(board, limit = 2, maxNodes = JUOSAN_MOST_NODES) {
5
+ if (!isJuosanBoard(board) ||
6
+ !Number.isInteger(limit) ||
7
+ limit < 1 ||
8
+ !Number.isInteger(maxNodes) ||
9
+ maxNodes < 1) {
10
+ throw new RangeError("Invalid Juosan search settings");
11
+ }
12
+ const cellCount = board.width * board.height;
13
+ const marks = Array(cellCount).fill(0);
14
+ const solutions = [];
15
+ const territoryAt = Array(cellCount).fill(-1);
16
+ board.territories.forEach((territory, index) => {
17
+ territory.cells.forEach(cell => {
18
+ territoryAt[cell] = index;
19
+ });
20
+ });
21
+ let nodes = 0;
22
+ let cutOff = false;
23
+ const visit = (cell) => {
24
+ if (solutions.length >= limit)
25
+ return;
26
+ if (nodes >= maxNodes) {
27
+ cutOff = true;
28
+ return;
29
+ }
30
+ nodes += 1;
31
+ if (cell === cellCount) {
32
+ const answer = marks;
33
+ if (checkJuosan(board, answer).ok)
34
+ solutions.push([...answer]);
35
+ return;
36
+ }
37
+ const x = cell % board.width;
38
+ const y = Math.floor(cell / board.width);
39
+ const territory = board.territories[territoryAt[cell]];
40
+ for (const mark of [1, 2]) {
41
+ if (mark === 2 &&
42
+ x >= 2 &&
43
+ marks[cell - 1] === 2 &&
44
+ marks[cell - 2] === 2) {
45
+ continue;
46
+ }
47
+ if (mark === 1 &&
48
+ y >= 2 &&
49
+ marks[cell - board.width] === 1 &&
50
+ marks[cell - 2 * board.width] === 1) {
51
+ continue;
52
+ }
53
+ marks[cell] = mark;
54
+ if (!canStillMeetDifference(territory.cells, marks, territory.difference)) {
55
+ marks[cell] = 0;
56
+ continue;
57
+ }
58
+ visit(cell + 1);
59
+ marks[cell] = 0;
60
+ if (solutions.length >= limit || cutOff)
61
+ return;
62
+ }
63
+ };
64
+ visit(0);
65
+ return {
66
+ count: solutions.length,
67
+ solution: solutions[0] ?? null,
68
+ complete: !cutOff && solutions.length < limit,
69
+ nodes,
70
+ };
71
+ }
72
+ function canStillMeetDifference(cells, marks, difference) {
73
+ if (difference === null)
74
+ return true;
75
+ const assigned = cells.filter(cell => marks[cell] !== 0);
76
+ const horizontal = assigned.filter(cell => marks[cell] === 1).length;
77
+ const vertical = assigned.length - horizontal;
78
+ const remaining = cells.length - assigned.length;
79
+ const smallestDifference = Math.max(0, Math.abs(horizontal - vertical) - remaining);
80
+ const largestDifference = Math.abs(horizontal - vertical) + remaining;
81
+ return smallestDifference <= difference && largestDifference >= difference;
82
+ }
@@ -0,0 +1,77 @@
1
+ export declare const JUOSAN_STRINGS: {
2
+ readonly en: {
3
+ readonly title: "Juosan";
4
+ readonly ready: "Fill every cell with a horizontal or vertical mark.";
5
+ readonly won: "Solved.";
6
+ readonly assisted: "Solved with a hint.";
7
+ readonly help: "Click a cell to cycle through horizontal, vertical and empty. Press H / V to write; arrow keys move and Delete clears.";
8
+ readonly undo: "Undo";
9
+ readonly hint: "Hint";
10
+ readonly restart: "Restart";
11
+ readonly check: "Check";
12
+ readonly checked: "The marks follow the rules.";
13
+ readonly wrong: "Some marks break a territory clue or run rule.";
14
+ readonly horizontal: "horizontal";
15
+ readonly vertical: "vertical";
16
+ readonly blank: "unmarked";
17
+ readonly row: "row";
18
+ readonly column: "column";
19
+ readonly hintWhy: "This mark follows the printed territory clue and line rules.";
20
+ };
21
+ readonly ja: {
22
+ readonly title: "じゅうさん";
23
+ readonly ready: "各マスに横線か縦線を入れます。";
24
+ readonly won: "できました。";
25
+ readonly assisted: "ヒントを使ってできました。";
26
+ readonly help: "マスをクリックすると、横線、縦線、空欄の順に切り替わります。H / V キーで記入し、矢印キーで移動、Delete で消します。";
27
+ readonly undo: "戻す";
28
+ readonly hint: "ヒント";
29
+ readonly restart: "最初から";
30
+ readonly check: "確認";
31
+ readonly checked: "ルールに合っています。";
32
+ readonly wrong: "陣地の数字か線の並びを確認してください。";
33
+ readonly horizontal: "横線";
34
+ readonly vertical: "縦線";
35
+ readonly blank: "未記入";
36
+ readonly row: "行";
37
+ readonly column: "列";
38
+ readonly hintWhy: "この線は、陣地の数字と線のルールに合います。";
39
+ };
40
+ };
41
+ export declare function juosanWords(language?: "en" | "ja"): {
42
+ readonly title: "Juosan";
43
+ readonly ready: "Fill every cell with a horizontal or vertical mark.";
44
+ readonly won: "Solved.";
45
+ readonly assisted: "Solved with a hint.";
46
+ readonly help: "Click a cell to cycle through horizontal, vertical and empty. Press H / V to write; arrow keys move and Delete clears.";
47
+ readonly undo: "Undo";
48
+ readonly hint: "Hint";
49
+ readonly restart: "Restart";
50
+ readonly check: "Check";
51
+ readonly checked: "The marks follow the rules.";
52
+ readonly wrong: "Some marks break a territory clue or run rule.";
53
+ readonly horizontal: "horizontal";
54
+ readonly vertical: "vertical";
55
+ readonly blank: "unmarked";
56
+ readonly row: "row";
57
+ readonly column: "column";
58
+ readonly hintWhy: "This mark follows the printed territory clue and line rules.";
59
+ } | {
60
+ readonly title: "じゅうさん";
61
+ readonly ready: "各マスに横線か縦線を入れます。";
62
+ readonly won: "できました。";
63
+ readonly assisted: "ヒントを使ってできました。";
64
+ readonly help: "マスをクリックすると、横線、縦線、空欄の順に切り替わります。H / V キーで記入し、矢印キーで移動、Delete で消します。";
65
+ readonly undo: "戻す";
66
+ readonly hint: "ヒント";
67
+ readonly restart: "最初から";
68
+ readonly check: "確認";
69
+ readonly checked: "ルールに合っています。";
70
+ readonly wrong: "陣地の数字か線の並びを確認してください。";
71
+ readonly horizontal: "横線";
72
+ readonly vertical: "縦線";
73
+ readonly blank: "未記入";
74
+ readonly row: "行";
75
+ readonly column: "列";
76
+ readonly hintWhy: "この線は、陣地の数字と線のルールに合います。";
77
+ };
@@ -0,0 +1,43 @@
1
+ export const JUOSAN_STRINGS = {
2
+ en: {
3
+ title: "Juosan",
4
+ ready: "Fill every cell with a horizontal or vertical mark.",
5
+ won: "Solved.",
6
+ assisted: "Solved with a hint.",
7
+ help: "Click a cell to cycle through horizontal, vertical and empty. Press H / V to write; arrow keys move and Delete clears.",
8
+ undo: "Undo",
9
+ hint: "Hint",
10
+ restart: "Restart",
11
+ check: "Check",
12
+ checked: "The marks follow the rules.",
13
+ wrong: "Some marks break a territory clue or run rule.",
14
+ horizontal: "horizontal",
15
+ vertical: "vertical",
16
+ blank: "unmarked",
17
+ row: "row",
18
+ column: "column",
19
+ hintWhy: "This mark follows the printed territory clue and line rules.",
20
+ },
21
+ ja: {
22
+ title: "じゅうさん",
23
+ ready: "各マスに横線か縦線を入れます。",
24
+ won: "できました。",
25
+ assisted: "ヒントを使ってできました。",
26
+ help: "マスをクリックすると、横線、縦線、空欄の順に切り替わります。H / V キーで記入し、矢印キーで移動、Delete で消します。",
27
+ undo: "戻す",
28
+ hint: "ヒント",
29
+ restart: "最初から",
30
+ check: "確認",
31
+ checked: "ルールに合っています。",
32
+ wrong: "陣地の数字か線の並びを確認してください。",
33
+ horizontal: "横線",
34
+ vertical: "縦線",
35
+ blank: "未記入",
36
+ row: "行",
37
+ column: "列",
38
+ hintWhy: "この線は、陣地の数字と線のルールに合います。",
39
+ },
40
+ };
41
+ export function juosanWords(language = "en") {
42
+ return JUOSAN_STRINGS[language];
43
+ }
@@ -0,0 +1 @@
1
+ export declare const JUOSAN_PLAY_STYLE = ".js-board{position:relative;outline:none}.js-cells{position:absolute;inset:0;display:grid;grid-template-columns:repeat(var(--juosan-width),1fr);grid-template-rows:repeat(var(--juosan-height),1fr)}.js-cell{border:0;background:transparent;color:transparent;min-width:0;min-height:0;cursor:pointer}.js-cell:focus-visible{outline:2px solid var(--kz-focus,#316c50);outline-offset:-3px}.js-tools{display:flex;gap:.5rem;flex-wrap:wrap;margin-top:.75rem}.js-tools button{font:inherit;padding:.45rem .8rem;border-radius:.5rem}.js-status{min-height:1.5em}.js-help{opacity:.8;font-size:.9em}";
@@ -0,0 +1 @@
1
+ export const JUOSAN_PLAY_STYLE = `.js-board{position:relative;outline:none}.js-cells{position:absolute;inset:0;display:grid;grid-template-columns:repeat(var(--juosan-width),1fr);grid-template-rows:repeat(var(--juosan-height),1fr)}.js-cell{border:0;background:transparent;color:transparent;min-width:0;min-height:0;cursor:pointer}.js-cell:focus-visible{outline:2px solid var(--kz-focus,#316c50);outline-offset:-3px}.js-tools{display:flex;gap:.5rem;flex-wrap:wrap;margin-top:.75rem}.js-tools button{font:inherit;padding:.45rem .8rem;border-radius:.5rem}.js-status{min-height:1.5em}.js-help{opacity:.8;font-size:.9em}`;
@@ -0,0 +1,2 @@
1
+ export { drawKakuro } from "./kakuroDraw.ts";
2
+ export type { KakuroDrawOptions } from "./kakuroDraw.ts";
@@ -0,0 +1 @@
1
+ export { drawKakuro } from "./kakuroDraw.js";
@@ -0,0 +1,12 @@
1
+ export * from "./kakuro.types.ts";
2
+ export * from "./kakuro.constants.ts";
3
+ export * from "./kakuroBoard.ts";
4
+ export * from "./kakuroSolve.ts";
5
+ export * from "./kakuroGenerate.ts";
6
+ export * from "./kakuroGame.ts";
7
+ export { drawKakuro } from "./kakuroDraw.ts";
8
+ export type { KakuroDrawOptions } from "./kakuroDraw.ts";
9
+ export { mountKakuro } from "./kakuroMount.ts";
10
+ export { KAKURO_PLAY_STYLE } from "./kakuroStyle.ts";
11
+ export { KAKURO_STRINGS } from "./kakuroStrings.ts";
12
+ export type * from "./kakuroPlay.types.ts";
@@ -0,0 +1,10 @@
1
+ export * from "./kakuro.types.js";
2
+ export * from "./kakuro.constants.js";
3
+ export * from "./kakuroBoard.js";
4
+ export * from "./kakuroSolve.js";
5
+ export * from "./kakuroGenerate.js";
6
+ export * from "./kakuroGame.js";
7
+ export { drawKakuro } from "./kakuroDraw.js";
8
+ export { mountKakuro } from "./kakuroMount.js";
9
+ export { KAKURO_PLAY_STYLE } from "./kakuroStyle.js";
10
+ export { KAKURO_STRINGS } from "./kakuroStrings.js";
@@ -0,0 +1,4 @@
1
+ export { mountKakuro } from "./kakuroMount.ts";
2
+ export { KAKURO_PLAY_STYLE } from "./kakuroStyle.ts";
3
+ export { KAKURO_STRINGS } from "./kakuroStrings.ts";
4
+ export type * from "./kakuroPlay.types.ts";
@@ -0,0 +1,3 @@
1
+ export { mountKakuro } from "./kakuroMount.js";
2
+ export { KAKURO_PLAY_STYLE } from "./kakuroStyle.js";
3
+ export { KAKURO_STRINGS } from "./kakuroStrings.js";
@@ -0,0 +1,6 @@
1
+ export declare const KAKURO_MIN_SIDE = 3;
2
+ export declare const KAKURO_MAX_SIDE = 10;
3
+ export declare const KAKURO_MAX_NODES = 250000;
4
+ export declare const KAKURO_MAX_ATTEMPTS = 120;
5
+ export declare const KAKURO_GENERATION_ATTEMPTS = 1200;
6
+ export declare const KAKURO_GENERATION_NODES = 1000000;
@@ -0,0 +1,6 @@
1
+ export const KAKURO_MIN_SIDE = 3;
2
+ export const KAKURO_MAX_SIDE = 10;
3
+ export const KAKURO_MAX_NODES = 250000;
4
+ export const KAKURO_MAX_ATTEMPTS = 120;
5
+ export const KAKURO_GENERATION_ATTEMPTS = 1200;
6
+ export const KAKURO_GENERATION_NODES = 1000000;
@@ -0,0 +1,46 @@
1
+ export type KakuroCell = {
2
+ kind: "white";
3
+ } | {
4
+ kind: "black";
5
+ across: number | null;
6
+ down: number | null;
7
+ };
8
+ export type KakuroBoard = {
9
+ width: number;
10
+ height: number;
11
+ cells: readonly KakuroCell[];
12
+ };
13
+ export type KakuroRun = {
14
+ direction: "across" | "down";
15
+ clueCell: number;
16
+ cells: readonly number[];
17
+ sum: number;
18
+ };
19
+ export type KakuroPuzzle = KakuroBoard & {
20
+ seed: number;
21
+ solution: readonly number[];
22
+ };
23
+ export type KakuroCheck = {
24
+ ok: boolean;
25
+ complete: boolean;
26
+ errors: readonly number[];
27
+ };
28
+ export type KakuroSolve = {
29
+ count: number;
30
+ solution: readonly number[] | null;
31
+ complete: boolean;
32
+ nodes: number;
33
+ };
34
+ export type KakuroGame = {
35
+ board: KakuroBoard;
36
+ values: readonly number[];
37
+ notes: readonly (readonly number[])[];
38
+ history: readonly {
39
+ values: readonly number[];
40
+ notes: readonly (readonly number[])[];
41
+ }[];
42
+ helped: boolean;
43
+ };
44
+ export type KakuroLanguage = "en" | "ja";
45
+ export type KakuroMaterial = "ivory" | "wood" | "slate";
46
+ export type KakuroPieces = "ink" | "tiles";
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ import type { KakuroBoard, KakuroCheck, KakuroRun } from "./kakuro.types.ts";
2
+ export declare function isKakuroBoard(value: unknown): value is KakuroBoard;
3
+ export declare function kakuroRuns(board: KakuroBoard): KakuroRun[];
4
+ /** Checks exact run sums and distinct digits independently of a stored answer. */
5
+ export declare function checkKakuro(board: KakuroBoard, values: readonly number[]): KakuroCheck;
6
+ /** Allows unfinished runs while flagging duplicate digits and impossible remaining totals. */
7
+ export declare function progressKakuro(board: KakuroBoard, values: readonly number[]): KakuroCheck;
8
+ export declare function canReach(target: number, slots: number, used: readonly number[]): boolean;