@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,2 @@
1
+ export const MASYU_SIZES = [5, 7];
2
+ export const MASYU_NODE_BUDGET = 250000;
@@ -0,0 +1,46 @@
1
+ export type MasyuBoard = {
2
+ width: number;
3
+ height: number;
4
+ pearls: readonly number[];
5
+ };
6
+ export type MasyuPuzzle = MasyuBoard & {
7
+ seed: number;
8
+ solution: readonly number[];
9
+ };
10
+ export type MasyuCheck = {
11
+ ok: boolean;
12
+ errors: readonly number[];
13
+ };
14
+ export type MasyuSolve = {
15
+ count: number;
16
+ solution: readonly number[] | null;
17
+ complete: boolean;
18
+ nodes: number;
19
+ };
20
+ export type MasyuGame = {
21
+ board: MasyuBoard;
22
+ edges: readonly number[];
23
+ history: readonly (readonly number[])[];
24
+ helped: boolean;
25
+ };
26
+ export type MasyuDrawOptions = {
27
+ edges?: readonly number[];
28
+ selected?: number;
29
+ errors?: readonly number[];
30
+ material?: "ivory" | "wood" | "slate";
31
+ pieces?: "plain" | "tiles";
32
+ language?: "en" | "ja";
33
+ };
34
+ export type MasyuMountOptions = MasyuDrawOptions & {
35
+ board: MasyuBoard;
36
+ progress?: string;
37
+ onChange?: (game: MasyuGame) => void;
38
+ onFinish?: (game: MasyuGame) => void;
39
+ };
40
+ export type MasyuMount = {
41
+ game: () => MasyuGame;
42
+ progress: () => string;
43
+ set: (options: MasyuDrawOptions) => void;
44
+ restart: () => void;
45
+ destroy: () => void;
46
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import type { MasyuBoard, MasyuCheck } from "./masyu.types.ts";
2
+ export declare const masyuCellCount: (board: MasyuBoard) => number;
3
+ export declare const masyuEdgeCount: (board: MasyuBoard) => number;
4
+ export declare function isMasyuBoard(value: unknown): value is MasyuBoard;
5
+ export declare function masyuNeighbors(board: MasyuBoard, cell: number): {
6
+ cell: number;
7
+ edge: number;
8
+ direction: number;
9
+ }[];
10
+ export declare function checkMasyu(board: MasyuBoard, lines: readonly number[]): MasyuCheck;
@@ -0,0 +1,88 @@
1
+ import { MASYU_SIZES } from "./masyu.constants.js";
2
+ export const masyuCellCount = (board) => board.width * board.height;
3
+ export const masyuEdgeCount = (board) => board.height * (board.width - 1) + board.width * (board.height - 1);
4
+ export function isMasyuBoard(value) {
5
+ if (!value || typeof value !== "object")
6
+ return false;
7
+ const board = value;
8
+ return MASYU_SIZES.includes(board.width) && board.width === board.height
9
+ && Array.isArray(board.pearls) && board.pearls.length === masyuCellCount(board)
10
+ && board.pearls.every(pearl => pearl === 0 || pearl === 1 || pearl === 2)
11
+ && board.pearls.some(Boolean);
12
+ }
13
+ export function masyuNeighbors(board, cell) {
14
+ const x = cell % board.width, y = Math.floor(cell / board.width), horizontal = board.height * (board.width - 1);
15
+ const result = [];
16
+ if (y > 0)
17
+ result.push({ cell: cell - board.width, edge: horizontal + (y - 1) * board.width + x, direction: 0 });
18
+ if (x < board.width - 1)
19
+ result.push({ cell: cell + 1, edge: y * (board.width - 1) + x, direction: 1 });
20
+ if (y < board.height - 1)
21
+ result.push({ cell: cell + board.width, edge: horizontal + y * board.width + x, direction: 2 });
22
+ if (x > 0)
23
+ result.push({ cell: cell - 1, edge: y * (board.width - 1) + x - 1, direction: 3 });
24
+ return result;
25
+ }
26
+ function pearlRule(board, edges, cell) {
27
+ const directions = masyuNeighbors(board, cell).filter(next => edges.has(next.edge)).map(next => next.direction);
28
+ if (directions.length !== 2)
29
+ return false;
30
+ const pearl = board.pearls[cell];
31
+ if (pearl === 1) {
32
+ if ((directions[0] + 2) % 4 !== directions[1])
33
+ return false;
34
+ return directions.some(direction => {
35
+ const next = masyuNeighbors(board, cell).find(item => item.direction === direction);
36
+ const nextDirections = masyuNeighbors(board, next.cell).filter(item => edges.has(item.edge)).map(item => item.direction);
37
+ return nextDirections.length === 2 && (nextDirections[0] + 2) % 4 !== nextDirections[1];
38
+ });
39
+ }
40
+ if (pearl === 2) {
41
+ if ((directions[0] + 2) % 4 === directions[1])
42
+ return false;
43
+ return directions.every(direction => {
44
+ const next = masyuNeighbors(board, cell).find(item => item.direction === direction);
45
+ const nextDirections = masyuNeighbors(board, next.cell).filter(item => edges.has(item.edge)).map(item => item.direction);
46
+ return nextDirections.length === 2 && (nextDirections[0] + 2) % 4 === nextDirections[1];
47
+ });
48
+ }
49
+ return true;
50
+ }
51
+ export function checkMasyu(board, lines) {
52
+ if (!isMasyuBoard(board))
53
+ throw new RangeError("Invalid Masyu board");
54
+ const edgeCount = masyuEdgeCount(board), edges = new Set(lines), errors = new Set();
55
+ if (edges.size !== lines.length || lines.some(edge => !Number.isInteger(edge) || edge < 0 || edge >= edgeCount)) {
56
+ return { ok: false, errors: [] };
57
+ }
58
+ const degrees = board.pearls.map((_, cell) => masyuNeighbors(board, cell).filter(next => edges.has(next.edge)).length);
59
+ for (let cell = 0; cell < degrees.length; cell += 1) {
60
+ if (degrees[cell] !== 0 && degrees[cell] !== 2 || (board.pearls[cell] > 0 && degrees[cell] !== 2))
61
+ errors.add(cell);
62
+ }
63
+ if (edges.size) {
64
+ const start = degrees.findIndex(degree => degree === 2), reached = new Set([start]), stack = [start];
65
+ while (stack.length) {
66
+ const cell = stack.pop();
67
+ for (const next of masyuNeighbors(board, cell))
68
+ if (edges.has(next.edge) && !reached.has(next.cell)) {
69
+ reached.add(next.cell);
70
+ stack.push(next.cell);
71
+ }
72
+ }
73
+ for (let cell = 0; cell < degrees.length; cell += 1)
74
+ if (degrees[cell] === 2 && !reached.has(cell))
75
+ errors.add(cell);
76
+ if (reached.size !== edges.size)
77
+ for (const cell of reached)
78
+ errors.add(cell);
79
+ }
80
+ for (let cell = 0; cell < degrees.length; cell += 1) {
81
+ if (degrees[cell] === 2 && !pearlRule(board, edges, cell))
82
+ errors.add(cell);
83
+ }
84
+ const complete = edges.size > 0 && degrees.every(degree => degree === 0 || degree === 2)
85
+ && degrees.every((degree, cell) => board.pearls[cell] === 0 || degree === 2)
86
+ && degrees.filter(degree => degree === 2).length === edges.size;
87
+ return { ok: complete && errors.size === 0, errors: [...errors] };
88
+ }
@@ -0,0 +1,2 @@
1
+ import type { MasyuBoard, MasyuDrawOptions } from "./masyu.types.ts";
2
+ export declare function drawMasyu(board: MasyuBoard, options?: MasyuDrawOptions): string;
@@ -0,0 +1,33 @@
1
+ import { isMasyuBoard, masyuNeighbors } from "./masyuBoard.js";
2
+ import { MASYU_STRINGS } from "./masyuStrings.js";
3
+ export function drawMasyu(board, options = {}) {
4
+ if (!isMasyuBoard(board))
5
+ throw new RangeError("Invalid Masyu board");
6
+ const words = MASYU_STRINGS[options.language ?? "en"], step = 52, pad = 14;
7
+ const palette = { ivory: ["var(--kz-paper,#fbf8f1)", "var(--kz-ink,#1f2320)", "var(--kz-grid,#cfc6b2)"], wood: ["#e5cda6", "#493e2f", "#bca582"], slate: ["#262a27", "#ece8dc", "#454a44"] };
8
+ const [paper, ink, grid] = palette[options.material ?? "ivory"];
9
+ const edges = new Set(options.edges ?? []), parts = [];
10
+ for (let cell = 0; cell < board.pearls.length; cell += 1) {
11
+ const x = pad + (cell % board.width) * step, y = pad + Math.floor(cell / board.width) * step;
12
+ const active = options.selected === cell;
13
+ parts.push(`<rect x="${x - step / 2}" y="${y - step / 2}" width="${step}" height="${step}" fill="${active ? ink : "transparent"}" fill-opacity="${active ? ".08" : "1"}"/>`);
14
+ for (const next of masyuNeighbors(board, cell))
15
+ if (cell < next.cell && edges.has(next.edge)) {
16
+ const nx = pad + (next.cell % board.width) * step, ny = pad + Math.floor(next.cell / board.width) * step;
17
+ parts.push(`<line x1="${x}" y1="${y}" x2="${nx}" y2="${ny}" stroke="${options.errors?.includes(cell) ? "var(--kz-bad,#b5452c)" : ink}" stroke-width="5" stroke-linecap="round"/>`);
18
+ }
19
+ }
20
+ for (let cell = 0; cell < board.pearls.length; cell += 1) {
21
+ const pearl = board.pearls[cell];
22
+ if (!pearl)
23
+ continue;
24
+ const x = pad + (cell % board.width) * step, y = pad + Math.floor(cell / board.width) * step;
25
+ const radius = options.pieces === "tiles" ? 12 : 10;
26
+ parts.push(`<circle cx="${x}" cy="${y}" r="${radius}" fill="${pearl === 1 ? paper : ink}" stroke="${ink}" stroke-width="3" aria-label="${words.pearl[pearl]}"/>`);
27
+ }
28
+ for (let y = 0; y < board.height; y += 1)
29
+ for (let x = 0; x < board.width; x += 1) {
30
+ parts.push(`<circle cx="${pad + x * step}" cy="${pad + y * step}" r="2" fill="${grid}"/>`);
31
+ }
32
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${pad * 2 + (board.width - 1) * step} ${pad * 2 + (board.height - 1) * step}" role="img" aria-label="${words.title}" style="display:block;width:100%;height:auto;background:${paper}">${parts.join("")}</svg>`;
33
+ }
@@ -0,0 +1,9 @@
1
+ import type { MasyuBoard, MasyuGame } from "./masyu.types.ts";
2
+ export declare function newMasyu(board: MasyuBoard): MasyuGame;
3
+ export declare function toggleMasyuEdge(game: MasyuGame, edge: number): MasyuGame;
4
+ export declare function undoMasyu(game: MasyuGame): MasyuGame;
5
+ export declare function restartMasyu(game: MasyuGame): MasyuGame;
6
+ export declare function hintMasyu(game: MasyuGame): number | null;
7
+ export declare function masyuFinished(game: MasyuGame): boolean;
8
+ export declare function encodeMasyu(game: MasyuGame): string;
9
+ export declare function decodeMasyu(code: string): MasyuGame | null;
@@ -0,0 +1,46 @@
1
+ import { checkMasyu, isMasyuBoard, masyuEdgeCount } from "./masyuBoard.js";
2
+ import { solveMasyu } from "./masyuSolve.js";
3
+ export function newMasyu(board) {
4
+ if (!isMasyuBoard(board))
5
+ throw new RangeError("Invalid Masyu board");
6
+ return { board: { width: board.width, height: board.height, pearls: [...board.pearls] }, edges: [], history: [], helped: false };
7
+ }
8
+ export function toggleMasyuEdge(game, edge) {
9
+ if (!Number.isInteger(edge) || edge < 0 || edge >= masyuEdgeCount(game.board))
10
+ return game;
11
+ const edges = game.edges.includes(edge) ? game.edges.filter(value => value !== edge) : [...game.edges, edge].sort((a, b) => a - b);
12
+ return { ...game, edges, history: [...game.history, [...game.edges]] };
13
+ }
14
+ export function undoMasyu(game) {
15
+ const previous = game.history.at(-1);
16
+ return previous ? { ...game, edges: [...previous], history: game.history.slice(0, -1) } : game;
17
+ }
18
+ export function restartMasyu(game) { return { ...newMasyu(game.board), helped: false }; }
19
+ export function hintMasyu(game) {
20
+ const result = solveMasyu(game.board);
21
+ if (!result.complete || result.count !== 1)
22
+ return null;
23
+ return result.solution.find(edge => !game.edges.includes(edge)) ?? null;
24
+ }
25
+ export function masyuFinished(game) { return checkMasyu(game.board, game.edges).ok; }
26
+ export function encodeMasyu(game) {
27
+ return JSON.stringify({ version: 1, board: game.board, edges: game.edges, helped: game.helped });
28
+ }
29
+ export function decodeMasyu(code) {
30
+ try {
31
+ if (code.length > 40000)
32
+ return null;
33
+ const value = JSON.parse(code);
34
+ if (value.version !== 1 || !isMasyuBoard(value.board) || !Array.isArray(value.edges)
35
+ || typeof value.helped !== "boolean" || !value.edges.every((edge) => Number.isInteger(edge)))
36
+ return null;
37
+ const rawEdges = value.edges;
38
+ const edges = [...new Set(rawEdges)].sort((a, b) => a - b);
39
+ if (edges.length !== rawEdges.length || edges.some(edge => edge < 0 || edge >= masyuEdgeCount(value.board)))
40
+ return null;
41
+ return { ...newMasyu(value.board), edges, helped: value.helped };
42
+ }
43
+ catch {
44
+ return null;
45
+ }
46
+ }
@@ -0,0 +1,3 @@
1
+ import type { MasyuPuzzle } from "./masyu.types.ts";
2
+ /** Seeded original loop layouts; every output is accepted only after a completed uniqueness proof. */
3
+ export declare function generateMasyu(size?: 5, seed?: number): MasyuPuzzle;
@@ -0,0 +1,56 @@
1
+ import { isKazuSeed, seededRandom } from "./random.js";
2
+ import { checkMasyu, isMasyuBoard, masyuNeighbors } from "./masyuBoard.js";
3
+ import { solveMasyu } from "./masyuSolve.js";
4
+ const loops = [
5
+ [12, 17, 18, 19, 14, 9, 8, 7],
6
+ [14, 9, 4, 3, 2, 1, 6, 11, 12, 13],
7
+ [21, 20, 15, 10, 5, 0, 1, 2, 7, 12, 17, 22],
8
+ [19, 24, 23, 22, 17, 12, 7, 8, 9, 14],
9
+ ];
10
+ function turnAt(path, index) {
11
+ const before = path[(index + path.length - 1) % path.length];
12
+ const cell = path[index];
13
+ const after = path[(index + 1) % path.length];
14
+ return Math.abs(cell - before) !== Math.abs(after - cell);
15
+ }
16
+ function makePearls(path) {
17
+ const pearls = Array(25).fill(0);
18
+ for (let index = 0; index < path.length; index += 1) {
19
+ if (turnAt(path, index))
20
+ pearls[path[index]] = 2;
21
+ else if (turnAt(path, (index + path.length - 1) % path.length)
22
+ || turnAt(path, (index + 1) % path.length))
23
+ pearls[path[index]] = 1;
24
+ }
25
+ return pearls;
26
+ }
27
+ function makeEdges(board, path) {
28
+ const edges = path.map((cell, index) => {
29
+ const nextCell = path[(index + 1) % path.length];
30
+ return masyuNeighbors(board, cell).find(next => next.cell === nextCell)?.edge;
31
+ });
32
+ return edges.every((edge) => edge !== undefined) ? edges.sort((a, b) => a - b) : null;
33
+ }
34
+ /** Seeded original loop layouts; every output is accepted only after a completed uniqueness proof. */
35
+ export function generateMasyu(size = 5, seed = 1) {
36
+ if (size !== 5 || !isKazuSeed(seed))
37
+ throw new RangeError("Invalid Masyu settings");
38
+ const random = seededRandom(seed), base = loops[Math.floor(random() * loops.length)];
39
+ const turns = Math.floor(random() * 4), reflect = random() < 0.5;
40
+ const path = base.map(cell => {
41
+ let x = cell % 5, y = Math.floor(cell / 5);
42
+ if (reflect)
43
+ x = 4 - x;
44
+ for (let turn = 0; turn < turns; turn += 1)
45
+ [x, y] = [4 - y, x];
46
+ return y * 5 + x;
47
+ });
48
+ const board = { width: 5, height: 5, pearls: makePearls(path) };
49
+ const expected = makeEdges(board, path);
50
+ if (!expected || !isMasyuBoard(board) || !checkMasyu(board, expected).ok)
51
+ throw new Error("Invalid Masyu layout");
52
+ const proof = solveMasyu(board);
53
+ if (!proof.complete || proof.count !== 1)
54
+ throw new Error("No uniquely proved Masyu found within the generation budget; try another seed");
55
+ return { ...board, seed, solution: proof.solution };
56
+ }
@@ -0,0 +1,2 @@
1
+ import type { MasyuMount, MasyuMountOptions } from "./masyu.types.ts";
2
+ export declare function mountMasyu(element: HTMLElement, initial: MasyuMountOptions): MasyuMount;
@@ -0,0 +1,118 @@
1
+ import { checkMasyu, masyuNeighbors } from "./masyuBoard.js";
2
+ import { decodeMasyu, encodeMasyu, hintMasyu, newMasyu, restartMasyu, toggleMasyuEdge, undoMasyu } from "./masyuGame.js";
3
+ import { drawMasyu } from "./masyuDraw.js";
4
+ import { MASYU_PLAY_STYLE } from "./masyuStyle.js";
5
+ import { MASYU_STRINGS } from "./masyuStrings.js";
6
+ export function mountMasyu(element, initial) {
7
+ const document = element.ownerDocument, style = document.createElement("style");
8
+ style.textContent = MASYU_PLAY_STYLE;
9
+ const root = document.createElement("section");
10
+ root.className = "kazu-masyu";
11
+ const help = document.createElement("p"), board = document.createElement("div");
12
+ board.className = "km-board";
13
+ const tools = document.createElement("div");
14
+ tools.className = "km-tools";
15
+ const status = document.createElement("p");
16
+ status.className = "km-status";
17
+ status.setAttribute("aria-live", "polite");
18
+ root.append(help, board, tools, status);
19
+ element.replaceChildren(style, root);
20
+ let options = { ...initial };
21
+ let game = initial.progress ? decodeMasyu(initial.progress) : null;
22
+ if (!game || game.board.width !== initial.board.width || game.board.height !== initial.board.height
23
+ || game.board.pearls.some((pearl, cell) => pearl !== initial.board.pearls[cell]))
24
+ game = newMasyu(initial.board);
25
+ let selected = 0, anchor = null, errors = [], message = "", dead = false;
26
+ const words = () => MASYU_STRINGS[options.language ?? "en"];
27
+ const copy = () => ({ ...game, board: { ...game.board, pearls: [...game.board.pearls] }, edges: [...game.edges], history: game.history.map(turn => [...turn]) });
28
+ const changed = () => { options.onChange?.(copy()); if (checkMasyu(game.board, game.edges).ok)
29
+ options.onFinish?.(copy()); };
30
+ const toggleBetween = (first, second) => {
31
+ const edge = masyuNeighbors(game.board, first).find(next => next.cell === second)?.edge;
32
+ if (edge === undefined)
33
+ return;
34
+ game = toggleMasyuEdge(game, edge);
35
+ anchor = second;
36
+ selected = second;
37
+ errors = [];
38
+ message = "";
39
+ render();
40
+ changed();
41
+ };
42
+ const render = () => {
43
+ if (dead)
44
+ return;
45
+ const w = words();
46
+ help.textContent = w.help;
47
+ board.innerHTML = drawMasyu(game.board, { ...options, edges: game.edges, selected, errors });
48
+ const controls = document.createElement("div");
49
+ controls.className = "km-cells";
50
+ controls.style.gridTemplateColumns = `repeat(${game.board.width}, 1fr)`;
51
+ controls.style.gridTemplateRows = `repeat(${game.board.height}, 1fr)`;
52
+ game.board.pearls.forEach((pearl, cell) => {
53
+ const button = document.createElement("button");
54
+ button.type = "button";
55
+ button.className = "km-cell";
56
+ button.dataset.cell = String(cell);
57
+ button.textContent = String(cell + 1);
58
+ button.setAttribute("aria-label", `${w.pearl[pearl]}, row ${Math.floor(cell / game.board.width) + 1}, column ${cell % game.board.width + 1}`);
59
+ button.setAttribute("aria-pressed", String(selected === cell));
60
+ button.tabIndex = selected === cell ? 0 : -1;
61
+ button.onclick = () => {
62
+ selected = cell;
63
+ if (anchor === null)
64
+ anchor = cell;
65
+ else if (anchor !== cell)
66
+ toggleBetween(anchor, cell);
67
+ render();
68
+ };
69
+ button.onkeydown = event => {
70
+ const x = cell % game.board.width, y = Math.floor(cell / game.board.width);
71
+ const moves = { ArrowUp: [0, -1], ArrowDown: [0, 1], ArrowLeft: [-1, 0], ArrowRight: [1, 0] };
72
+ if (event.key in moves) {
73
+ event.preventDefault();
74
+ const [dx, dy] = moves[event.key];
75
+ const nextX = x + dx, nextY = y + dy;
76
+ if (nextX >= 0 && nextX < game.board.width && nextY >= 0 && nextY < game.board.height) {
77
+ selected = nextY * game.board.width + nextX;
78
+ render();
79
+ board.querySelectorAll(".km-cell")[selected]?.focus();
80
+ }
81
+ }
82
+ else if (event.key === "Enter" || event.key === " ") {
83
+ event.preventDefault();
84
+ if (anchor === null)
85
+ anchor = cell;
86
+ else if (anchor !== cell)
87
+ toggleBetween(anchor, cell);
88
+ render();
89
+ board.querySelectorAll(".km-cell")[selected]?.focus();
90
+ }
91
+ };
92
+ controls.append(button);
93
+ });
94
+ board.append(controls);
95
+ tools.replaceChildren();
96
+ const add = (label, action) => { const button = document.createElement("button"); button.type = "button"; button.textContent = label; button.onclick = action; tools.append(button); };
97
+ add(w.undo, () => { game = undoMasyu(game); errors = []; render(); changed(); });
98
+ add(w.hint, () => { const edge = hintMasyu(game); if (edge === null)
99
+ message = w.bad;
100
+ else {
101
+ game = toggleMasyuEdge(game, edge);
102
+ game = { ...game, helped: true };
103
+ message = w.good;
104
+ changed();
105
+ } render(); });
106
+ add(w.check, () => { errors = checkMasyu(game.board, game.edges).errors; message = errors.length ? w.bad : w.good; render(); });
107
+ add(w.restart, () => { game = restartMasyu(game); anchor = null; errors = []; message = ""; render(); changed(); });
108
+ status.textContent = checkMasyu(game.board, game.edges).ok ? (game.helped ? w.helped : w.won) : message;
109
+ };
110
+ render();
111
+ return {
112
+ game: copy,
113
+ progress: () => encodeMasyu(game),
114
+ set: next => { options = { ...options, ...next }; render(); },
115
+ restart: () => { game = restartMasyu(game); anchor = null; render(); changed(); },
116
+ destroy: () => { dead = true; element.replaceChildren(); },
117
+ };
118
+ }
@@ -0,0 +1 @@
1
+ export type { MasyuDrawOptions as MasyuDrawOptions, MasyuMount as MasyuMount, MasyuMountOptions as MasyuMountOptions } from "./masyu.types.ts";
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ import type { MasyuBoard, MasyuSolve } from "./masyu.types.ts";
2
+ /** Counts distinct single loops with local pearl rules; budget stops never claim a proof. */
3
+ export declare function solveMasyu(board: MasyuBoard, options?: {
4
+ limit?: number;
5
+ nodes?: number;
6
+ }): MasyuSolve;
@@ -0,0 +1,88 @@
1
+ import { MASYU_NODE_BUDGET } from "./masyu.constants.js";
2
+ import { checkMasyu, isMasyuBoard, masyuEdgeCount, masyuNeighbors } from "./masyuBoard.js";
3
+ /** Counts distinct single loops with local pearl rules; budget stops never claim a proof. */
4
+ export function solveMasyu(board, options = {}) {
5
+ if (!isMasyuBoard(board))
6
+ throw new RangeError("Invalid Masyu board");
7
+ const limit = options.limit ?? 2, budget = options.nodes ?? MASYU_NODE_BUDGET;
8
+ if (!Number.isInteger(limit) || limit < 1 || !Number.isInteger(budget) || budget < 1)
9
+ throw new RangeError("Invalid search bounds");
10
+ const edgeState = Array(masyuEdgeCount(board)).fill(-1);
11
+ const configs = board.pearls.map((pearl, cell) => {
12
+ const validDirections = masyuNeighbors(board, cell).map(next => next.direction);
13
+ const choices = [];
14
+ for (let mask = 0; mask < 16; mask += 1) {
15
+ const bits = [0, 1, 2, 3].filter(direction => mask & (1 << direction));
16
+ if (bits.length !== 0 && bits.length !== 2)
17
+ continue;
18
+ if (bits.some(direction => !validDirections.includes(direction)))
19
+ continue;
20
+ if (pearl && bits.length !== 2)
21
+ continue;
22
+ const straight = bits.length === 2 && (bits[0] + 2) % 4 === bits[1];
23
+ if (pearl === 1 && !straight || pearl === 2 && straight)
24
+ continue;
25
+ choices.push(mask);
26
+ }
27
+ return choices;
28
+ });
29
+ let count = 0, nodes = 0, complete = true, answer = null;
30
+ const assigned = Array(board.pearls.length).fill(false);
31
+ const visit = (depth) => {
32
+ if (++nodes > budget) {
33
+ complete = false;
34
+ return;
35
+ }
36
+ if (depth === board.pearls.length) {
37
+ const edges = edgeState.flatMap((value, edge) => value === 1 ? [edge] : []);
38
+ if (!checkMasyu(board, edges).ok)
39
+ return;
40
+ count += 1;
41
+ answer ?? (answer = edges);
42
+ if (count >= limit)
43
+ complete = false;
44
+ return;
45
+ }
46
+ let chosen = -1, available = null;
47
+ for (let cell = 0; cell < board.pearls.length; cell += 1) {
48
+ if (assigned[cell])
49
+ continue;
50
+ const neighbors = masyuNeighbors(board, cell);
51
+ const candidates = configs[cell].filter(mask => neighbors.every(next => edgeState[next.edge] < 0 || edgeState[next.edge] === (mask & (1 << next.direction) ? 1 : 0)));
52
+ if (!candidates.length)
53
+ return;
54
+ if (!available || candidates.length < available.length) {
55
+ chosen = cell;
56
+ available = candidates;
57
+ if (candidates.length === 1)
58
+ break;
59
+ }
60
+ }
61
+ const neighbors = masyuNeighbors(board, chosen);
62
+ assigned[chosen] = true;
63
+ for (const mask of available ?? []) {
64
+ const changes = [];
65
+ let valid = true;
66
+ for (const next of neighbors) {
67
+ const value = mask & (1 << next.direction) ? 1 : 0;
68
+ if (edgeState[next.edge] >= 0 && edgeState[next.edge] !== value) {
69
+ valid = false;
70
+ break;
71
+ }
72
+ if (edgeState[next.edge] < 0) {
73
+ edgeState[next.edge] = value;
74
+ changes.push(next.edge);
75
+ }
76
+ }
77
+ if (valid)
78
+ visit(depth + 1);
79
+ for (const edge of changes)
80
+ edgeState[edge] = -1;
81
+ if (!complete)
82
+ return;
83
+ }
84
+ assigned[chosen] = false;
85
+ };
86
+ visit(0);
87
+ return { count, solution: answer, complete, nodes };
88
+ }
@@ -0,0 +1,28 @@
1
+ export declare const MASYU_STRINGS: {
2
+ readonly en: {
3
+ readonly title: "Masyu";
4
+ readonly help: "Draw one loop through every pearl. White pearls go straight and turn at a neighboring cell; black pearls turn and go straight through the neighboring cell on each side.";
5
+ readonly undo: "Undo";
6
+ readonly hint: "Hint";
7
+ readonly check: "Check";
8
+ readonly restart: "Restart";
9
+ readonly won: "Solved!";
10
+ readonly helped: "Solved with help.";
11
+ readonly good: "The loop is still open. Keep going.";
12
+ readonly bad: "Check the marked line ends and pearls.";
13
+ readonly pearl: readonly ["empty", "white pearl", "black pearl"];
14
+ };
15
+ readonly ja: {
16
+ readonly title: "ましゅ";
17
+ readonly help: "すべての真珠を通る一つの輪を描きます。白真珠では直進して隣のマスで曲がり、黒真珠では曲がって両隣のマスで直進します。";
18
+ readonly undo: "戻す";
19
+ readonly hint: "ヒント";
20
+ readonly check: "確認";
21
+ readonly restart: "やり直す";
22
+ readonly won: "完成!";
23
+ readonly helped: "ヒントを使って完成。";
24
+ readonly good: "輪はまだ閉じていません。続けましょう。";
25
+ readonly bad: "線の端や真珠の周りを確認してください。";
26
+ readonly pearl: readonly ["なし", "白真珠", "黒真珠"];
27
+ };
28
+ };
@@ -0,0 +1,4 @@
1
+ export const MASYU_STRINGS = {
2
+ en: { title: "Masyu", help: "Draw one loop through every pearl. White pearls go straight and turn at a neighboring cell; black pearls turn and go straight through the neighboring cell on each side.", undo: "Undo", hint: "Hint", check: "Check", restart: "Restart", won: "Solved!", helped: "Solved with help.", good: "The loop is still open. Keep going.", bad: "Check the marked line ends and pearls.", pearl: ["empty", "white pearl", "black pearl"] },
3
+ ja: { title: "ましゅ", help: "すべての真珠を通る一つの輪を描きます。白真珠では直進して隣のマスで曲がり、黒真珠では曲がって両隣のマスで直進します。", undo: "戻す", hint: "ヒント", check: "確認", restart: "やり直す", won: "完成!", helped: "ヒントを使って完成。", good: "輪はまだ閉じていません。続けましょう。", bad: "線の端や真珠の周りを確認してください。", pearl: ["なし", "白真珠", "黒真珠"] },
4
+ };
@@ -0,0 +1 @@
1
+ export declare const MASYU_PLAY_STYLE = "\n.kazu-masyu { color:var(--kz-ink,#1f2320); font:16px/1.4 system-ui,sans-serif; }\n.kazu-masyu .km-board { position:relative; max-width:440px; margin:auto; }\n.kazu-masyu svg { border:1px solid var(--kz-frame,#a98954); border-radius:4px; }\n.kazu-masyu .km-cells { position:absolute; inset:6%; display:grid; }\n.kazu-masyu .km-cell { border:0; background:transparent; border-radius:50%; color:transparent; cursor:pointer; }\n.kazu-masyu .km-cell:focus-visible { outline:3px solid var(--kz-focus,#c57d2d); outline-offset:-5px; }\n.kazu-masyu .km-tools { display:flex; flex-wrap:wrap; gap:.5rem; justify-content:center; margin:.8rem 0; }\n.kazu-masyu button { font:inherit; padding:.45rem .8rem; }\n.kazu-masyu .km-status { min-height:1.5em; text-align:center; }\n";
@@ -0,0 +1,11 @@
1
+ export const MASYU_PLAY_STYLE = `
2
+ .kazu-masyu { color:var(--kz-ink,#1f2320); font:16px/1.4 system-ui,sans-serif; }
3
+ .kazu-masyu .km-board { position:relative; max-width:440px; margin:auto; }
4
+ .kazu-masyu svg { border:1px solid var(--kz-frame,#a98954); border-radius:4px; }
5
+ .kazu-masyu .km-cells { position:absolute; inset:6%; display:grid; }
6
+ .kazu-masyu .km-cell { border:0; background:transparent; border-radius:50%; color:transparent; cursor:pointer; }
7
+ .kazu-masyu .km-cell:focus-visible { outline:3px solid var(--kz-focus,#c57d2d); outline-offset:-5px; }
8
+ .kazu-masyu .km-tools { display:flex; flex-wrap:wrap; gap:.5rem; justify-content:center; margin:.8rem 0; }
9
+ .kazu-masyu button { font:inherit; padding:.45rem .8rem; }
10
+ .kazu-masyu .km-status { min-height:1.5em; text-align:center; }
11
+ `;
@@ -0,0 +1,2 @@
1
+ export { drawNurikabe } from "./nurikabeDraw.ts";
2
+ export type { NurikabeDrawOptions } from "./nurikabePlay.types.ts";
@@ -0,0 +1 @@
1
+ export { drawNurikabe } from "./nurikabeDraw.js";
@@ -0,0 +1,6 @@
1
+ export * from "./nurikabe.types.ts";
2
+ export * from "./nurikabe.constants.ts";
3
+ export * from "./nurikabeBoard.ts";
4
+ export * from "./nurikabeSolve.ts";
5
+ export * from "./nurikabeGenerate.ts";
6
+ export * from "./nurikabeGame.ts";
@@ -0,0 +1,6 @@
1
+ export * from "./nurikabe.types.js";
2
+ export * from "./nurikabe.constants.js";
3
+ export * from "./nurikabeBoard.js";
4
+ export * from "./nurikabeSolve.js";
5
+ export * from "./nurikabeGenerate.js";
6
+ export * from "./nurikabeGame.js";
@@ -0,0 +1,4 @@
1
+ export { mountNurikabe } from "./nurikabeMount.ts";
2
+ export { NURIKABE_PLAY_STYLE } from "./nurikabeStyle.ts";
3
+ export { NURIKABE_STRINGS } from "./nurikabeStrings.ts";
4
+ export type * from "./nurikabePlay.types.ts";
@@ -0,0 +1,3 @@
1
+ export { mountNurikabe } from "./nurikabeMount.js";
2
+ export { NURIKABE_PLAY_STYLE } from "./nurikabeStyle.js";
3
+ export { NURIKABE_STRINGS } from "./nurikabeStrings.js";
@@ -0,0 +1,2 @@
1
+ export declare const NURIKABE_MAX_NODES = 250000;
2
+ export declare const NURIKABE_SIZES: readonly [5];
@@ -0,0 +1,2 @@
1
+ export const NURIKABE_MAX_NODES = 250000;
2
+ export const NURIKABE_SIZES = [5];