@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,337 @@
1
+ import { isKakuroBoard } from "./kakuroBoard.js";
2
+ import { kakuroModel } from "./kakuroLogic.js";
3
+ import { countCsp, logicCsp, openSlots } from "./csp.js";
4
+ import { shuffled } from "./random.js";
5
+ /** How the black squares are scattered, and the longest run a level allows. */
6
+ export const SHAPE = {
7
+ easy: { black: .34, longest: 4 },
8
+ medium: { black: .3, longest: 5 },
9
+ hard: { black: .26, longest: 7 },
10
+ "extra-hard": { black: .22, longest: 9 },
11
+ };
12
+ /** Which squares are white: no run shorter than two, none longer than the level allows, and all of them one connected piece. */
13
+ export function kakuroPattern(side, level, random) {
14
+ for (let attempt = 0; attempt < 60; attempt += 1) {
15
+ const white = laidRows(side, level, random);
16
+ if (white && oneStrip(side, white))
17
+ return white;
18
+ }
19
+ return null;
20
+ }
21
+ /**
22
+ * Lays the squares out row by row, each row left to right, so that a run is never one square or longer than the
23
+ * level allows: a column whose run is one square long must go on, one at the limit must stop, and a row's own
24
+ * runs are kept the same way. A square is white with the level's chance, except where a rule forces it.
25
+ */
26
+ function laidRows(side, level, random) {
27
+ const { black, longest } = SHAPE[level];
28
+ const white = Array(side * side).fill(false), above = Array(side).fill(0);
29
+ const chance = 1 - black * (.85 + random() * .3);
30
+ for (let row = 1; row < side; row += 1) {
31
+ const last = row === side - 1, chosen = Array(side).fill(false);
32
+ let steps = 0;
33
+ const place = (col, run) => {
34
+ if (++steps > 4000)
35
+ return false;
36
+ if (col === side)
37
+ return run !== 1;
38
+ const mustWhite = above[col] === 1;
39
+ const mustBlack = above[col] === longest || last && above[col] === 0;
40
+ const options = [];
41
+ if (mustWhite && mustBlack)
42
+ return false;
43
+ if (mustWhite)
44
+ options.push(true);
45
+ else if (mustBlack)
46
+ options.push(false);
47
+ else
48
+ options.push(...(random() < chance ? [true, false] : [false, true]));
49
+ for (const isWhite of options) {
50
+ if (isWhite && run + 1 > longest)
51
+ continue;
52
+ if (!isWhite && run === 1)
53
+ continue;
54
+ chosen[col] = isWhite;
55
+ if (place(col + 1, isWhite ? run + 1 : 0))
56
+ return true;
57
+ }
58
+ return false;
59
+ };
60
+ if (!place(1, 0))
61
+ return null;
62
+ for (let col = 1; col < side; col += 1) {
63
+ white[row * side + col] = chosen[col];
64
+ above[col] = chosen[col] ? above[col] + 1 : 0;
65
+ }
66
+ }
67
+ return white;
68
+ }
69
+ /** How many different sets of digits can make `sum` out of `length` squares. */
70
+ export function kakuroWays(length, sum) {
71
+ let ways = 0;
72
+ for (let mask = 1; mask < 512; mask += 1) {
73
+ let n = 0, total = 0;
74
+ for (let d = 1; d <= 9; d += 1)
75
+ if (mask & 1 << d - 1) {
76
+ n += 1;
77
+ total += d;
78
+ }
79
+ if (n === length && total === sum)
80
+ ways += 1;
81
+ }
82
+ return ways;
83
+ }
84
+ const WAYS = Array.from({ length: 10 }, (_, length) => Array.from({ length: 46 }, (_, sum) => length < 2 ? 0 : kakuroWays(length, sum)));
85
+ /** A random digit for every white square with no digit repeated in a run, or null when the search gives up. */
86
+ export function kakuroDigits(side, white, random) {
87
+ const digits = Array(white.length).fill(0);
88
+ let steps = 0;
89
+ const fill = (at) => {
90
+ while (at < white.length && !white[at])
91
+ at += 1;
92
+ if (at === white.length)
93
+ return true;
94
+ if (++steps > 40000)
95
+ return false;
96
+ const used = new Set();
97
+ for (let left = at - 1; left >= 0 && white[left] && (left + 1) % side !== 0; left -= 1)
98
+ used.add(digits[left]);
99
+ for (let up = at - side; up >= 0 && white[up]; up -= side)
100
+ used.add(digits[up]);
101
+ for (const digit of shuffled([1, 2, 3, 4, 5, 6, 7, 8, 9].filter(d => !used.has(d)), random)) {
102
+ digits[at] = digit;
103
+ if (fill(at + 1))
104
+ return true;
105
+ }
106
+ digits[at] = 0;
107
+ return false;
108
+ };
109
+ return fill(0) ? digits : null;
110
+ }
111
+ /**
112
+ * Changes digits one square at a time, keeping every run free of repeats, to make runs whose totals can be made in
113
+ * few ways: a run that says a lot about its digits lets the solver start from the rules alone. A change is kept when
114
+ * it does not raise the count of ways, summed over runs, which is `weight` raised to the number of ways.
115
+ */
116
+ export function kakuroTighten(side, white, digits, random, rounds) {
117
+ const runsAt = (cell) => {
118
+ const across = [cell], down = [cell];
119
+ for (let at = cell - 1; at >= 0 && white[at] && (at + 1) % side !== 0; at -= 1)
120
+ across.push(at);
121
+ for (let at = cell + 1; at < white.length && white[at] && at % side !== 0; at += 1)
122
+ across.push(at);
123
+ for (let at = cell - side; at >= 0 && white[at]; at -= side)
124
+ down.push(at);
125
+ for (let at = cell + side; at < white.length && white[at]; at += side)
126
+ down.push(at);
127
+ return { across, down };
128
+ };
129
+ const ways = (run, swap) => {
130
+ let sum = 0;
131
+ for (const at of run)
132
+ sum += swap && at === swap.cell ? swap.digit : digits[at];
133
+ return WAYS[run.length][sum];
134
+ };
135
+ const cells = white.flatMap((w, i) => w ? [i] : []);
136
+ for (let round = 0; round < rounds; round += 1) {
137
+ const cell = cells[Math.floor(random() * cells.length)];
138
+ const { across, down } = runsAt(cell);
139
+ const used = new Set();
140
+ for (const at of [...across, ...down])
141
+ if (at !== cell)
142
+ used.add(digits[at]);
143
+ const options = [1, 2, 3, 4, 5, 6, 7, 8, 9].filter(d => !used.has(d) && d !== digits[cell]);
144
+ if (!options.length)
145
+ continue;
146
+ const digit = options[Math.floor(random() * options.length)];
147
+ const before = ways(across) + ways(down), after = ways(across, { cell, digit }) + ways(down, { cell, digit });
148
+ if (after <= before)
149
+ digits[cell] = digit;
150
+ }
151
+ }
152
+ /** The totals of a pattern and its digits, as a board. */
153
+ export function kakuroBoardOf(side, white, digits) {
154
+ const cells = white.map(w => w ? { kind: "white" } : { kind: "black", across: null, down: null });
155
+ for (let cell = 0; cell < white.length; cell += 1) {
156
+ if (white[cell])
157
+ continue;
158
+ const black = cells[cell];
159
+ if (cell % side < side - 1 && white[cell + 1]) {
160
+ let sum = 0;
161
+ for (let at = cell + 1; white[at]; at += 1) {
162
+ sum += digits[at];
163
+ if ((at + 1) % side === 0)
164
+ break;
165
+ }
166
+ black.across = sum;
167
+ }
168
+ if (cell + side < white.length && white[cell + side]) {
169
+ let sum = 0;
170
+ for (let at = cell + side; at < white.length && white[at]; at += side)
171
+ sum += digits[at];
172
+ black.down = sum;
173
+ }
174
+ }
175
+ return { width: side, height: side, cells };
176
+ }
177
+ /** Whether the white squares are one connected piece. */
178
+ function oneStrip(side, white) {
179
+ const first = white.indexOf(true);
180
+ if (first < 0)
181
+ return false;
182
+ const seen = new Set([first]), stack = [first];
183
+ while (stack.length) {
184
+ const at = stack.pop();
185
+ for (const next of [at - 1, at + 1, at - side, at + side]) {
186
+ if (next < 0 || next >= white.length || !white[next] || seen.has(next) || (next === at + 1 && next % side === 0))
187
+ continue;
188
+ seen.add(next);
189
+ stack.push(next);
190
+ }
191
+ }
192
+ return seen.size === white.filter(Boolean).length;
193
+ }
194
+ /** The squares in the same row or column strip as `cell` that have the given digit, to see what digits it may take. */
195
+ function digitsAround(side, white, digits, cell) {
196
+ const used = new Set();
197
+ for (let at = cell - 1; at >= 0 && white[at] && (at + 1) % side !== 0; at -= 1)
198
+ used.add(digits[at]);
199
+ for (let at = cell + 1; at < white.length && white[at] && at % side !== 0; at += 1)
200
+ used.add(digits[at]);
201
+ for (let at = cell - side; at >= 0 && white[at]; at -= side)
202
+ used.add(digits[at]);
203
+ for (let at = cell + side; at < white.length && white[at]; at += side)
204
+ used.add(digits[at]);
205
+ return used;
206
+ }
207
+ /**
208
+ * A board with exactly one answer, or null. While the board has other answers, each one is broken: a square the
209
+ * two answers disagree on is turned black (when every run stays two long and the squares stay one piece) or given
210
+ * another digit that still repeats nowhere in its runs, which changes two totals.
211
+ */
212
+ export function candidateKakuro(side, level, random) {
213
+ // Easy and medium boards are also eased until the rules they promise are enough to solve them.
214
+ const eased = level === "easy" ? 0 : level === "medium" ? 1 : -1;
215
+ const white = kakuroPattern(side, level, random);
216
+ if (!white)
217
+ return null;
218
+ const digits = kakuroDigits(side, white, random);
219
+ if (!digits)
220
+ return null;
221
+ if (level === "easy" || level === "medium")
222
+ kakuroTighten(side, white, digits, random, side * side * (level === "easy" ? 60 : 25));
223
+ const valid = () => {
224
+ for (let cell = 0; cell < white.length; cell += 1) {
225
+ if (!white[cell])
226
+ continue;
227
+ let across = 1, down = 1;
228
+ for (let at = cell - 1; at >= 0 && white[at] && (at + 1) % side !== 0; at -= 1)
229
+ across += 1;
230
+ for (let at = cell + 1; at < white.length && white[at] && at % side !== 0; at += 1)
231
+ across += 1;
232
+ for (let at = cell - side; at >= 0 && white[at]; at -= side)
233
+ down += 1;
234
+ for (let at = cell + side; at < white.length && white[at]; at += side)
235
+ down += 1;
236
+ if (across < 2 || down < 2)
237
+ return false;
238
+ }
239
+ return oneStrip(side, white);
240
+ };
241
+ for (let repair = 0; repair < 150; repair += 1) {
242
+ const board = kakuroBoardOf(side, white, digits);
243
+ if (!isKakuroBoard(board))
244
+ return null;
245
+ const model = kakuroModel(board, 1, random);
246
+ let found = countCsp(model.csp, openSlots(model.csp), 8, 3000);
247
+ for (let restart = 0; restart < 5 && found.exhausted; restart += 1)
248
+ found = countCsp(model.csp, openSlots(model.csp), 8, 3000);
249
+ if (found.exhausted) {
250
+ // Too many answers to even count: darken any square that can be darkened and look again.
251
+ const any = shuffled(white.flatMap((w, cell) => w ? [cell] : []), random).find(cell => { white[cell] = false; if (valid())
252
+ return true; white[cell] = true; return false; });
253
+ if (any === undefined)
254
+ return null;
255
+ continue;
256
+ }
257
+ if (found.count === 1 && eased < 0)
258
+ return { board, digits };
259
+ if (found.count === 1) {
260
+ const rules = kakuroModel(board, eased);
261
+ const run = logicCsp(rules.csp, openSlots(rules.csp), 0);
262
+ if (run.solved)
263
+ return { board, digits };
264
+ if (white.filter(Boolean).length < (side - 1) * (side - 1) * .42)
265
+ return null;
266
+ const open = rules.whites.filter((_, i) => { let n = 0; for (let k = 0; k < 9; k += 1)
267
+ n += run.alive[9 * i + k]; return n > 1; });
268
+ // Squares still open, then the others that share a run with one.
269
+ const near = new Set(open);
270
+ for (const cell of open) {
271
+ for (let at = cell - 1; at >= 0 && white[at] && (at + 1) % side !== 0; at -= 1)
272
+ near.add(at);
273
+ for (let at = cell + 1; at < white.length && white[at] && at % side !== 0; at += 1)
274
+ near.add(at);
275
+ for (let at = cell - side; at >= 0 && white[at]; at -= side)
276
+ near.add(at);
277
+ for (let at = cell + side; at < white.length && white[at]; at += side)
278
+ near.add(at);
279
+ }
280
+ const stuck = [...shuffled(open, random), ...shuffled([...near].filter(cell => !open.includes(cell)), random)];
281
+ let eased_ = false;
282
+ for (const cell of stuck) {
283
+ white[cell] = false;
284
+ if (valid()) {
285
+ eased_ = true;
286
+ break;
287
+ }
288
+ white[cell] = true;
289
+ }
290
+ if (!eased_) {
291
+ const any = shuffled(white.flatMap((w, cell) => w ? [cell] : []), random).find(cell => { white[cell] = false; if (valid())
292
+ return true; white[cell] = true; return false; });
293
+ if (any === undefined)
294
+ return null;
295
+ }
296
+ continue;
297
+ }
298
+ const digitOf = (answer, cell) => { const index = model.index[cell]; let d = 0; for (let k = 0; k < 9; k += 1)
299
+ if (answer[9 * index + k])
300
+ d = k + 1; return d; };
301
+ let moved = false;
302
+ for (const answer of found.solutions) {
303
+ const differing = shuffled(white.flatMap((w, cell) => w && digitOf(answer, cell) !== digits[cell] ? [cell] : []), random);
304
+ if (!differing.length)
305
+ continue;
306
+ const blacken = () => {
307
+ for (const cell of differing) {
308
+ if (!white[cell])
309
+ continue;
310
+ white[cell] = false;
311
+ if (valid())
312
+ return true;
313
+ white[cell] = true;
314
+ }
315
+ return false;
316
+ };
317
+ const change = () => {
318
+ for (const cell of differing) {
319
+ if (!white[cell])
320
+ continue;
321
+ const used = digitsAround(side, white, digits, cell);
322
+ const options = [1, 2, 3, 4, 5, 6, 7, 8, 9].filter(d => !used.has(d));
323
+ if (!options.length)
324
+ continue;
325
+ digits[cell] = options[Math.floor(random() * options.length)];
326
+ return true;
327
+ }
328
+ return false;
329
+ };
330
+ if (random() < .5 ? blacken() || change() : change() || blacken())
331
+ moved = true;
332
+ }
333
+ if (!moved)
334
+ return null;
335
+ }
336
+ return null;
337
+ }
@@ -0,0 +1,10 @@
1
+ import type { KakuroBoard, KakuroMaterial, KakuroPieces } from "./kakuro.types.ts";
2
+ export type KakuroDrawOptions = {
3
+ values?: readonly number[];
4
+ notes?: readonly (readonly number[])[];
5
+ selected?: number;
6
+ material?: KakuroMaterial;
7
+ pieces?: KakuroPieces;
8
+ errors?: readonly number[];
9
+ };
10
+ export declare function drawKakuro(board: KakuroBoard, options?: KakuroDrawOptions): string;
@@ -0,0 +1,21 @@
1
+ import { isKakuroBoard } from "./kakuroBoard.js";
2
+ export function drawKakuro(board, options = {}) {
3
+ if (!isKakuroBoard(board))
4
+ throw new RangeError("Invalid Kakuro board");
5
+ const size = 56, paper = options.material === "slate" ? "#262a27" : options.material === "wood" ? "#e5cda6" : "var(--kz-paper,#fbf8f1)";
6
+ const ink = options.material === "slate" ? "#ece8dc" : options.material === "wood" ? "#493e2f" : "var(--kz-ink,#1f2320)";
7
+ const grid = options.material === "slate" ? "#454a44" : "var(--kz-grid,#cfc6b2)";
8
+ const cells = board.cells.map((tile, cell) => {
9
+ const x = cell % board.width * size, y = Math.floor(cell / board.width) * size;
10
+ const white = tile.kind === "white", value = options.values?.[cell] ?? 0;
11
+ const mark = options.errors?.includes(cell) ? "var(--kz-bad,#b5452c)" : ink;
12
+ const bg = white ? options.selected === cell ? "var(--kz-selected,#e9dfca)" : paper : "#333630";
13
+ const clue = white ? "" : `<path d="M${x},${y + size} L${x + size},${y} M${x + 6},${y + size - 6} L${x + size - 6},${y + 6}" stroke="#858174"/><text x="${x + size - 7}" y="${y + 16}" text-anchor="end" fill="#fff" font-size="15" aria-label="Across ${tile.across ?? ""}">${tile.across ?? ""}</text><text x="${x + 7}" y="${y + size - 7}" fill="#fff" font-size="15" aria-label="Down ${tile.down ?? ""}">${tile.down ?? ""}</text>`;
14
+ const notes = !white || value ? "" : (options.notes?.[cell] ?? []).map((n, index) => `<text x="${x + 8 + index % 3 * 15}" y="${y + 17 + Math.floor(index / 3) * 14}" fill="${ink}" font-size="10">${n}</text>`).join("");
15
+ const piece = value && options.pieces === "tiles"
16
+ ? `<rect x="${x + 10}" y="${y + 8}" width="36" height="40" rx="8" fill="${paper}" stroke="${grid}"/>`
17
+ : "";
18
+ return `<g data-kakuro-cell="${cell}"><rect x="${x}" y="${y}" width="${size}" height="${size}" fill="${bg}" stroke="${grid}"/><title>${white ? `Cell ${Math.floor(cell / board.width) + 1}, ${cell % board.width + 1}` : `Across ${tile.across ?? "—"}; down ${tile.down ?? "—"}`}</title>${clue}${piece}${value ? `<text x="${x + size / 2}" y="${y + size / 2 + 7}" text-anchor="middle" fill="${mark}" font-size="27" font-weight="700">${value}</text>` : notes}</g>`;
19
+ }).join("");
20
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${board.width * size} ${board.height * size}" role="img" aria-label="Kakuro crossword sum grid" style="display:block;width:100%;height:auto;background:${paper}">${cells}<rect width="${board.width * size}" height="${board.height * size}" fill="none" stroke="var(--kz-frame,#a98954)" stroke-width="3"/></svg>`;
21
+ }
@@ -0,0 +1,12 @@
1
+ import type { KakuroBoard, KakuroGame } from "./kakuro.types.ts";
2
+ export declare function newKakuro(board: KakuroBoard): KakuroGame;
3
+ export declare function enterKakuro(game: KakuroGame, cell: number, digit: number, pencil?: boolean): KakuroGame;
4
+ export declare function undoKakuro(game: KakuroGame): KakuroGame;
5
+ export declare function restartKakuro(game: KakuroGame): KakuroGame;
6
+ export declare function finishedKakuro(game: KakuroGame): boolean;
7
+ export declare function hintKakuro(game: KakuroGame): {
8
+ cell: number;
9
+ digit: number;
10
+ } | null;
11
+ export declare function encodeKakuro(game: KakuroGame): string;
12
+ export declare function decodeKakuro(code: string): KakuroGame | null;
@@ -0,0 +1,74 @@
1
+ import { checkKakuro, isKakuroBoard } from "./kakuroBoard.js";
2
+ import { solveKakuro } from "./kakuroSolve.js";
3
+ export function newKakuro(board) {
4
+ if (!isKakuroBoard(board))
5
+ throw new RangeError("Invalid Kakuro board");
6
+ return { board: { width: board.width, height: board.height, cells: board.cells.map(cell => ({ ...cell })) },
7
+ values: Array(board.cells.length).fill(0), notes: board.cells.map(() => []), history: [], helped: false };
8
+ }
9
+ export function enterKakuro(game, cell, digit, pencil = false) {
10
+ if (!Number.isInteger(cell) || cell < 0 || cell >= game.values.length || game.board.cells[cell].kind !== "white"
11
+ || !Number.isInteger(digit) || digit < 0 || digit > 9)
12
+ return game;
13
+ const values = [...game.values], notes = game.notes.map(mark => [...mark]);
14
+ if (pencil) {
15
+ if (!digit)
16
+ return game;
17
+ const index = notes[cell].indexOf(digit);
18
+ if (index < 0)
19
+ notes[cell].push(digit);
20
+ else
21
+ notes[cell].splice(index, 1);
22
+ notes[cell].sort((a, b) => a - b);
23
+ }
24
+ else {
25
+ values[cell] = digit;
26
+ notes[cell] = [];
27
+ }
28
+ return { ...game, values, notes, history: [...game.history, snapshot(game)] };
29
+ }
30
+ export function undoKakuro(game) {
31
+ const previous = game.history.at(-1);
32
+ return previous ? { ...game, ...previous, history: game.history.slice(0, -1) } : game;
33
+ }
34
+ export function restartKakuro(game) { return newKakuro(game.board); }
35
+ export function finishedKakuro(game) { return checkKakuro(game.board, game.values).ok; }
36
+ export function hintKakuro(game) {
37
+ const proof = solveKakuro(game.board, game.values, { limit: 2 });
38
+ if (!proof.complete || proof.count !== 1 || !proof.solution)
39
+ return null;
40
+ const cell = game.board.cells.findIndex((tile, at) => tile.kind === "white" && game.values[at] === 0);
41
+ return cell < 0 ? null : { cell, digit: proof.solution[cell] };
42
+ }
43
+ export function encodeKakuro(game) {
44
+ return JSON.stringify({ version: 1, board: game.board, values: game.values, notes: game.notes, helped: game.helped });
45
+ }
46
+ export function decodeKakuro(code) {
47
+ try {
48
+ if (code.length > 40000)
49
+ return null;
50
+ const value = JSON.parse(code);
51
+ if (value.version !== 1 || !isKakuroBoard(value.board) || typeof value.helped !== "boolean"
52
+ || !Array.isArray(value.values) || value.values.length !== value.board.cells.length
53
+ || !Array.isArray(value.notes) || value.notes.length !== value.board.cells.length)
54
+ return null;
55
+ let game = newKakuro(value.board);
56
+ for (let cell = 0; cell < value.values.length; cell += 1) {
57
+ const digit = value.values[cell];
58
+ if (!Number.isInteger(digit) || digit < 0 || digit > 9 || value.board.cells[cell].kind === "black" && digit !== 0)
59
+ return null;
60
+ if (digit)
61
+ game = enterKakuro(game, cell, digit);
62
+ const marks = value.notes[cell];
63
+ if (!Array.isArray(marks) || marks.some(n => !Number.isInteger(n) || n < 1 || n > 9) || new Set(marks).size !== marks.length)
64
+ return null;
65
+ for (const mark of marks)
66
+ game = enterKakuro(game, cell, mark, true);
67
+ }
68
+ return { ...game, history: [], helped: value.helped };
69
+ }
70
+ catch {
71
+ return null;
72
+ }
73
+ }
74
+ function snapshot(game) { return { values: [...game.values], notes: game.notes.map(mark => [...mark]) }; }
@@ -0,0 +1,10 @@
1
+ import type { KakuroLevel, KakuroPuzzle } from "./kakuro.types.ts";
2
+ /**
3
+ * Makes a seeded board of `size` squares a side (counting the row and column of totals) and proves it has one
4
+ * answer. A random pattern of black squares sets the runs, random digits fill them, and digits are changed until
5
+ * the answer is single. The board is then rated by solving it: easy by the single-run rules, medium once a digit
6
+ * that must appear has to be traced, hard by supposing, and extra-hard the most-supposing of several boards.
7
+ * If no board of the level is found within the attempts the next level down is tried, and the first generator
8
+ * (a 10×10 only) is the last resort, so a seed never throws.
9
+ */
10
+ export declare function generateKakuro(seed?: number, level?: KakuroLevel, size?: number): KakuroPuzzle;
@@ -0,0 +1,62 @@
1
+ import { KAKURO_LEAST_GENERATED_SIDE, KAKURO_LEVELS, KAKURO_MAX_SIDE } from "./kakuro.constants.js";
2
+ import { checkKakuro } from "./kakuroBoard.js";
3
+ import { candidateKakuro } from "./kakuroBuild.js";
4
+ import { kakuroModel } from "./kakuroLogic.js";
5
+ import { solveKakuro } from "./kakuroSolve.js";
6
+ import { templateKakuro } from "./kakuroTemplate.js";
7
+ import { logicCsp, openSlots } from "./csp.js";
8
+ import { isKazuSeed, seededRandom } from "./random.js";
9
+ const MOST_ATTEMPTS = 400;
10
+ /**
11
+ * Makes a seeded board of `size` squares a side (counting the row and column of totals) and proves it has one
12
+ * answer. A random pattern of black squares sets the runs, random digits fill them, and digits are changed until
13
+ * the answer is single. The board is then rated by solving it: easy by the single-run rules, medium once a digit
14
+ * that must appear has to be traced, hard by supposing, and extra-hard the most-supposing of several boards.
15
+ * If no board of the level is found within the attempts the next level down is tried, and the first generator
16
+ * (a 10×10 only) is the last resort, so a seed never throws.
17
+ */
18
+ export function generateKakuro(seed = 1, level = "medium", size = 10) {
19
+ if (!isKazuSeed(seed) || !KAKURO_LEVELS.includes(level) || !Number.isInteger(size)
20
+ || size < KAKURO_LEAST_GENERATED_SIDE || size > KAKURO_MAX_SIDE)
21
+ throw new RangeError("Invalid Kakuro settings");
22
+ const random = seededRandom(seed);
23
+ for (let aim = KAKURO_LEVELS.indexOf(level); aim >= 0; aim -= 1) {
24
+ const aimed = KAKURO_LEVELS[aim];
25
+ const wanted = aimed === "extra-hard" ? 3 : 1;
26
+ let best = null, found = 0;
27
+ for (let attempt = 0; attempt < MOST_ATTEMPTS && found < wanted; attempt += 1) {
28
+ const built = candidateKakuro(size, aimed, random);
29
+ if (!built)
30
+ continue;
31
+ const score = scored(built.board, aimed);
32
+ if (score === null)
33
+ continue;
34
+ found += 1;
35
+ if (!best || score > best.score)
36
+ best = { board: built.board, score };
37
+ }
38
+ if (best) {
39
+ const proof = solveKakuro(best.board);
40
+ if (proof.complete && proof.count === 1 && proof.solution && checkKakuro(best.board, proof.solution).ok) {
41
+ return { ...best.board, seed, level, solution: proof.solution };
42
+ }
43
+ }
44
+ }
45
+ if (size === 10)
46
+ return { ...templateKakuro(seed), level };
47
+ throw new Error("No uniquely solvable Kakuro board was found within the generation budget; try another seed");
48
+ }
49
+ /** Whether a board suits a level, and how well: higher is harder. */
50
+ function scored(board, level) {
51
+ const weak = kakuroModel(board, 0), strong = kakuroModel(board, 1);
52
+ if (logicCsp(weak.csp, openSlots(weak.csp), 0).solved)
53
+ return level === "easy" ? 0 : null;
54
+ if (level === "easy")
55
+ return null;
56
+ if (logicCsp(strong.csp, openSlots(strong.csp), 0).solved)
57
+ return level === "medium" ? 0 : null;
58
+ if (level === "medium")
59
+ return null;
60
+ const probing = logicCsp(strong.csp, openSlots(strong.csp), 1);
61
+ return probing.solved ? probing.probes : level === "extra-hard" ? 1000 : null;
62
+ }
@@ -0,0 +1,13 @@
1
+ import type { Csp } from "./csp.ts";
2
+ import type { KakuroBoard } from "./kakuro.types.ts";
3
+ /**
4
+ * Kakuro as variables: one per white square, nine slots for the digits 1 to 9. A run's pruning keeps only the digit
5
+ * sets that add up to its total and fit what its squares can still be, so a square keeps only digits some such
6
+ * set uses; a digit that every such set uses must appear somewhere (`strength` 1), and a digit that only one square
7
+ * can take is that square's.
8
+ */
9
+ export declare function kakuroModel(board: KakuroBoard, strength?: 0 | 1, random?: () => number): {
10
+ csp: Csp;
11
+ whites: readonly number[];
12
+ index: Int32Array;
13
+ };