@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.
- package/CHANGELOG.md +39 -0
- package/README.md +424 -10
- package/dist/akari-draw-entry.d.ts +2 -0
- package/dist/akari-draw-entry.js +1 -0
- package/dist/akari-entry.d.ts +13 -0
- package/dist/akari-entry.js +11 -0
- package/dist/akari-play-entry.d.ts +2 -0
- package/dist/akari-play-entry.js +1 -0
- package/dist/akari.constants.d.ts +7 -0
- package/dist/akari.constants.js +7 -0
- package/dist/akari.types.d.ts +52 -0
- package/dist/akari.types.js +1 -0
- package/dist/akariBoard.d.ts +11 -0
- package/dist/akariBoard.js +96 -0
- package/dist/akariDraw.d.ts +4 -0
- package/dist/akariDraw.js +38 -0
- package/dist/akariGame.d.ts +15 -0
- package/dist/akariGame.js +71 -0
- package/dist/akariGenerate.d.ts +10 -0
- package/dist/akariGenerate.js +101 -0
- package/dist/akariLogic.d.ts +10 -0
- package/dist/akariLogic.js +84 -0
- package/dist/akariMount.d.ts +3 -0
- package/dist/akariMount.js +214 -0
- package/dist/akariPlay.types.d.ts +25 -0
- package/dist/akariPlay.types.js +1 -0
- package/dist/akariRate.d.ts +6 -0
- package/dist/akariRate.js +34 -0
- package/dist/akariSolve.d.ts +6 -0
- package/dist/akariSolve.js +18 -0
- package/dist/akariStrings.d.ts +98 -0
- package/dist/akariStrings.js +53 -0
- package/dist/akariStyle.d.ts +1 -0
- package/dist/akariStyle.js +9 -0
- package/dist/akariTemplate.d.ts +7 -0
- package/dist/akariTemplate.js +78 -0
- package/dist/csp.d.ts +63 -0
- package/dist/csp.js +162 -0
- package/dist/fillomino-draw-entry.d.ts +2 -0
- package/dist/fillomino-draw-entry.js +1 -0
- package/dist/fillomino-entry.d.ts +9 -0
- package/dist/fillomino-entry.js +8 -0
- package/dist/fillomino-play-entry.d.ts +4 -0
- package/dist/fillomino-play-entry.js +3 -0
- package/dist/fillomino.constants.d.ts +9 -0
- package/dist/fillomino.constants.js +9 -0
- package/dist/fillomino.types.d.ts +47 -0
- package/dist/fillomino.types.js +1 -0
- package/dist/fillominoBoard.d.ts +6 -0
- package/dist/fillominoBoard.js +84 -0
- package/dist/fillominoBuild.d.ts +10 -0
- package/dist/fillominoBuild.js +78 -0
- package/dist/fillominoDraw.d.ts +3 -0
- package/dist/fillominoDraw.js +31 -0
- package/dist/fillominoGame.d.ts +13 -0
- package/dist/fillominoGame.js +65 -0
- package/dist/fillominoGenerate.d.ts +9 -0
- package/dist/fillominoGenerate.js +93 -0
- package/dist/fillominoLogic.d.ts +14 -0
- package/dist/fillominoLogic.js +177 -0
- package/dist/fillominoMount.d.ts +2 -0
- package/dist/fillominoMount.js +214 -0
- package/dist/fillominoPlay.types.d.ts +25 -0
- package/dist/fillominoPlay.types.js +1 -0
- package/dist/fillominoRate.d.ts +3 -0
- package/dist/fillominoRate.js +57 -0
- package/dist/fillominoSolve.d.ts +6 -0
- package/dist/fillominoSolve.js +38 -0
- package/dist/fillominoStrings.d.ts +102 -0
- package/dist/fillominoStrings.js +13 -0
- package/dist/fillominoStyle.d.ts +1 -0
- package/dist/fillominoStyle.js +13 -0
- package/dist/fillominoWorker.d.ts +1 -0
- package/dist/fillominoWorker.js +10 -0
- package/dist/heyawake-draw-entry.d.ts +2 -0
- package/dist/heyawake-draw-entry.js +1 -0
- package/dist/heyawake-entry.d.ts +13 -0
- package/dist/heyawake-entry.js +11 -0
- package/dist/heyawake-play-entry.d.ts +2 -0
- package/dist/heyawake-play-entry.js +1 -0
- package/dist/heyawake.constants.d.ts +5 -0
- package/dist/heyawake.constants.js +5 -0
- package/dist/heyawake.types.d.ts +35 -0
- package/dist/heyawake.types.js +1 -0
- package/dist/heyawakeBoard.d.ts +7 -0
- package/dist/heyawakeBoard.js +116 -0
- package/dist/heyawakeDraw.d.ts +3 -0
- package/dist/heyawakeDraw.js +27 -0
- package/dist/heyawakeGame.d.ts +12 -0
- package/dist/heyawakeGame.js +51 -0
- package/dist/heyawakeGenerate.d.ts +2 -0
- package/dist/heyawakeGenerate.js +114 -0
- package/dist/heyawakeMount.d.ts +2 -0
- package/dist/heyawakeMount.js +106 -0
- package/dist/heyawakePlay.types.d.ts +24 -0
- package/dist/heyawakePlay.types.js +1 -0
- package/dist/heyawakeSolve.d.ts +5 -0
- package/dist/heyawakeSolve.js +52 -0
- package/dist/heyawakeStrings.d.ts +19 -0
- package/dist/heyawakeStrings.js +7 -0
- package/dist/heyawakeStyle.d.ts +1 -0
- package/dist/heyawakeStyle.js +2 -0
- package/dist/heyawakeWorker.d.ts +1 -0
- package/dist/heyawakeWorker.js +10 -0
- package/dist/hitori-draw-entry.d.ts +2 -0
- package/dist/hitori-draw-entry.js +1 -0
- package/dist/hitori-entry.d.ts +7 -0
- package/dist/hitori-entry.js +7 -0
- package/dist/hitori-play-entry.d.ts +4 -0
- package/dist/hitori-play-entry.js +3 -0
- package/dist/hitori.constants.d.ts +8 -0
- package/dist/hitori.constants.js +8 -0
- package/dist/hitori.types.d.ts +37 -0
- package/dist/hitori.types.js +1 -0
- package/dist/hitoriBoard.d.ts +8 -0
- package/dist/hitoriBoard.js +73 -0
- package/dist/hitoriBuild.d.ts +12 -0
- package/dist/hitoriBuild.js +117 -0
- package/dist/hitoriDraw.d.ts +4 -0
- package/dist/hitoriDraw.js +25 -0
- package/dist/hitoriGame.d.ts +10 -0
- package/dist/hitoriGame.js +48 -0
- package/dist/hitoriGenerate.d.ts +10 -0
- package/dist/hitoriGenerate.js +65 -0
- package/dist/hitoriLogic.d.ts +12 -0
- package/dist/hitoriLogic.js +189 -0
- package/dist/hitoriMount.d.ts +3 -0
- package/dist/hitoriMount.js +111 -0
- package/dist/hitoriPlay.types.d.ts +25 -0
- package/dist/hitoriPlay.types.js +1 -0
- package/dist/hitoriRate.d.ts +3 -0
- package/dist/hitoriRate.js +32 -0
- package/dist/hitoriSolve.d.ts +9 -0
- package/dist/hitoriSolve.js +18 -0
- package/dist/hitoriStrings.d.ts +70 -0
- package/dist/hitoriStrings.js +17 -0
- package/dist/hitoriStyle.d.ts +1 -0
- package/dist/hitoriStyle.js +9 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +11 -0
- package/dist/juosan-draw-entry.d.ts +2 -0
- package/dist/juosan-draw-entry.js +1 -0
- package/dist/juosan-entry.d.ts +11 -0
- package/dist/juosan-entry.js +10 -0
- package/dist/juosan-play-entry.d.ts +4 -0
- package/dist/juosan-play-entry.js +3 -0
- package/dist/juosan.constants.d.ts +4 -0
- package/dist/juosan.constants.js +4 -0
- package/dist/juosan.types.d.ts +33 -0
- package/dist/juosan.types.js +1 -0
- package/dist/juosanBoard.d.ts +7 -0
- package/dist/juosanBoard.js +102 -0
- package/dist/juosanDraw.d.ts +4 -0
- package/dist/juosanDraw.js +49 -0
- package/dist/juosanGame.d.ts +20 -0
- package/dist/juosanGame.js +86 -0
- package/dist/juosanGenerate.d.ts +3 -0
- package/dist/juosanGenerate.js +31 -0
- package/dist/juosanMount.d.ts +3 -0
- package/dist/juosanMount.js +206 -0
- package/dist/juosanPlay.types.d.ts +24 -0
- package/dist/juosanPlay.types.js +1 -0
- package/dist/juosanSolve.d.ts +3 -0
- package/dist/juosanSolve.js +82 -0
- package/dist/juosanStrings.d.ts +77 -0
- package/dist/juosanStrings.js +43 -0
- package/dist/juosanStyle.d.ts +1 -0
- package/dist/juosanStyle.js +1 -0
- package/dist/kakuro-draw-entry.d.ts +2 -0
- package/dist/kakuro-draw-entry.js +1 -0
- package/dist/kakuro-entry.d.ts +13 -0
- package/dist/kakuro-entry.js +11 -0
- package/dist/kakuro-play-entry.d.ts +4 -0
- package/dist/kakuro-play-entry.js +3 -0
- package/dist/kakuro.constants.d.ts +11 -0
- package/dist/kakuro.constants.js +11 -0
- package/dist/kakuro.types.d.ts +65 -0
- package/dist/kakuro.types.js +1 -0
- package/dist/kakuroBoard.d.ts +8 -0
- package/dist/kakuroBoard.js +117 -0
- package/dist/kakuroBuild.d.ts +30 -0
- package/dist/kakuroBuild.js +337 -0
- package/dist/kakuroDraw.d.ts +10 -0
- package/dist/kakuroDraw.js +21 -0
- package/dist/kakuroGame.d.ts +12 -0
- package/dist/kakuroGame.js +74 -0
- package/dist/kakuroGenerate.d.ts +10 -0
- package/dist/kakuroGenerate.js +62 -0
- package/dist/kakuroLogic.d.ts +13 -0
- package/dist/kakuroLogic.js +153 -0
- package/dist/kakuroMount.d.ts +2 -0
- package/dist/kakuroMount.js +109 -0
- package/dist/kakuroPlay.types.d.ts +20 -0
- package/dist/kakuroPlay.types.js +1 -0
- package/dist/kakuroRate.d.ts +3 -0
- package/dist/kakuroRate.js +40 -0
- package/dist/kakuroSolve.d.ts +6 -0
- package/dist/kakuroSolve.js +33 -0
- package/dist/kakuroStrings.d.ts +82 -0
- package/dist/kakuroStrings.js +5 -0
- package/dist/kakuroStyle.d.ts +1 -0
- package/dist/kakuroStyle.js +1 -0
- package/dist/kakuroTemplate.d.ts +3 -0
- package/dist/kakuroTemplate.js +130 -0
- package/dist/masyu-draw-entry.d.ts +2 -0
- package/dist/masyu-draw-entry.js +1 -0
- package/dist/masyu-entry.d.ts +6 -0
- package/dist/masyu-entry.js +6 -0
- package/dist/masyu-play-entry.d.ts +4 -0
- package/dist/masyu-play-entry.js +3 -0
- package/dist/masyu.constants.d.ts +2 -0
- package/dist/masyu.constants.js +2 -0
- package/dist/masyu.types.d.ts +46 -0
- package/dist/masyu.types.js +1 -0
- package/dist/masyuBoard.d.ts +10 -0
- package/dist/masyuBoard.js +88 -0
- package/dist/masyuDraw.d.ts +2 -0
- package/dist/masyuDraw.js +33 -0
- package/dist/masyuGame.d.ts +9 -0
- package/dist/masyuGame.js +46 -0
- package/dist/masyuGenerate.d.ts +3 -0
- package/dist/masyuGenerate.js +56 -0
- package/dist/masyuMount.d.ts +2 -0
- package/dist/masyuMount.js +118 -0
- package/dist/masyuPlay.types.d.ts +1 -0
- package/dist/masyuPlay.types.js +1 -0
- package/dist/masyuSolve.d.ts +6 -0
- package/dist/masyuSolve.js +88 -0
- package/dist/masyuStrings.d.ts +28 -0
- package/dist/masyuStrings.js +4 -0
- package/dist/masyuStyle.d.ts +1 -0
- package/dist/masyuStyle.js +11 -0
- package/dist/nurikabe-draw-entry.d.ts +2 -0
- package/dist/nurikabe-draw-entry.js +1 -0
- package/dist/nurikabe-entry.d.ts +6 -0
- package/dist/nurikabe-entry.js +6 -0
- package/dist/nurikabe-play-entry.d.ts +4 -0
- package/dist/nurikabe-play-entry.js +3 -0
- package/dist/nurikabe.constants.d.ts +2 -0
- package/dist/nurikabe.constants.js +2 -0
- package/dist/nurikabe.types.d.ts +20 -0
- package/dist/nurikabe.types.js +1 -0
- package/dist/nurikabeBoard.d.ts +8 -0
- package/dist/nurikabeBoard.js +72 -0
- package/dist/nurikabeDraw.d.ts +4 -0
- package/dist/nurikabeDraw.js +15 -0
- package/dist/nurikabeGame.d.ts +8 -0
- package/dist/nurikabeGame.js +40 -0
- package/dist/nurikabeGenerate.d.ts +3 -0
- package/dist/nurikabeGenerate.js +31 -0
- package/dist/nurikabeMount.d.ts +3 -0
- package/dist/nurikabeMount.js +99 -0
- package/dist/nurikabePlay.types.d.ts +25 -0
- package/dist/nurikabePlay.types.js +1 -0
- package/dist/nurikabeSolve.d.ts +6 -0
- package/dist/nurikabeSolve.js +75 -0
- package/dist/nurikabeStrings.d.ts +70 -0
- package/dist/nurikabeStrings.js +5 -0
- package/dist/nurikabeStyle.d.ts +1 -0
- package/dist/nurikabeStyle.js +9 -0
- package/dist/ripple-draw-entry.d.ts +2 -0
- package/dist/ripple-draw-entry.js +1 -0
- package/dist/ripple-entry.d.ts +12 -0
- package/dist/ripple-entry.js +11 -0
- package/dist/ripple-play-entry.d.ts +4 -0
- package/dist/ripple-play-entry.js +3 -0
- package/dist/ripple.constants.d.ts +4 -0
- package/dist/ripple.constants.js +4 -0
- package/dist/ripple.types.d.ts +34 -0
- package/dist/ripple.types.js +1 -0
- package/dist/rippleBoard.d.ts +7 -0
- package/dist/rippleBoard.js +99 -0
- package/dist/rippleDraw.d.ts +3 -0
- package/dist/rippleDraw.js +32 -0
- package/dist/rippleGame.d.ts +13 -0
- package/dist/rippleGame.js +85 -0
- package/dist/rippleGenerate.d.ts +3 -0
- package/dist/rippleGenerate.js +57 -0
- package/dist/rippleMount.d.ts +2 -0
- package/dist/rippleMount.js +179 -0
- package/dist/ripplePlay.types.d.ts +23 -0
- package/dist/ripplePlay.types.js +1 -0
- package/dist/rippleSolve.d.ts +6 -0
- package/dist/rippleSolve.js +83 -0
- package/dist/rippleStrings.d.ts +86 -0
- package/dist/rippleStrings.js +5 -0
- package/dist/rippleStyle.d.ts +1 -0
- package/dist/rippleStyle.js +1 -0
- package/dist/shikaku-entry.d.ts +2 -0
- package/dist/shikaku-entry.js +2 -0
- package/dist/shikaku.constants.d.ts +5 -2
- package/dist/shikaku.constants.js +5 -2
- package/dist/shikaku.types.d.ts +18 -1
- package/dist/shikakuBuild.d.ts +21 -0
- package/dist/shikakuBuild.js +135 -0
- package/dist/shikakuGenerate.d.ts +6 -1
- package/dist/shikakuGenerate.js +50 -35
- package/dist/shikakuLogic.d.ts +13 -0
- package/dist/shikakuLogic.js +84 -0
- package/dist/shikakuPacks.d.ts +27 -0
- package/dist/shikakuPacks.js +26 -0
- package/dist/shikakuRate.d.ts +3 -0
- package/dist/shikakuRate.js +27 -0
- package/dist/shikakuSolve.d.ts +1 -1
- package/dist/shikakuSolve.js +23 -44
- package/dist/shikakuTemplate.d.ts +3 -0
- package/dist/shikakuTemplate.js +43 -0
- package/dist/slitherlink-draw-entry.d.ts +2 -0
- package/dist/slitherlink-draw-entry.js +1 -0
- package/dist/slitherlink-entry.d.ts +12 -0
- package/dist/slitherlink-entry.js +10 -0
- package/dist/slitherlink-play-entry.d.ts +4 -0
- package/dist/slitherlink-play-entry.js +3 -0
- package/dist/slitherlink.constants.d.ts +7 -0
- package/dist/slitherlink.constants.js +7 -0
- package/dist/slitherlink.types.d.ts +52 -0
- package/dist/slitherlink.types.js +1 -0
- package/dist/slitherlinkBoard.d.ts +12 -0
- package/dist/slitherlinkBoard.js +132 -0
- package/dist/slitherlinkDraw.d.ts +4 -0
- package/dist/slitherlinkDraw.js +51 -0
- package/dist/slitherlinkGame.d.ts +8 -0
- package/dist/slitherlinkGame.js +63 -0
- package/dist/slitherlinkGenerate.d.ts +10 -0
- package/dist/slitherlinkGenerate.js +182 -0
- package/dist/slitherlinkLogic.d.ts +10 -0
- package/dist/slitherlinkLogic.js +267 -0
- package/dist/slitherlinkMount.d.ts +3 -0
- package/dist/slitherlinkMount.js +267 -0
- package/dist/slitherlinkPlay.types.d.ts +23 -0
- package/dist/slitherlinkPlay.types.js +1 -0
- package/dist/slitherlinkRate.d.ts +3 -0
- package/dist/slitherlinkRate.js +26 -0
- package/dist/slitherlinkSolve.d.ts +6 -0
- package/dist/slitherlinkSolve.js +19 -0
- package/dist/slitherlinkStrings.d.ts +86 -0
- package/dist/slitherlinkStrings.js +47 -0
- package/dist/slitherlinkStyle.d.ts +1 -0
- package/dist/slitherlinkStyle.js +18 -0
- package/dist/slitherlinkTemplate.d.ts +3 -0
- package/dist/slitherlinkTemplate.js +101 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/yajilin-draw-entry.d.ts +2 -0
- package/dist/yajilin-draw-entry.js +1 -0
- package/dist/yajilin-entry.d.ts +6 -0
- package/dist/yajilin-entry.js +6 -0
- package/dist/yajilin-play-entry.d.ts +4 -0
- package/dist/yajilin-play-entry.js +3 -0
- package/dist/yajilin.constants.d.ts +2 -0
- package/dist/yajilin.constants.js +2 -0
- package/dist/yajilin.types.d.ts +63 -0
- package/dist/yajilin.types.js +1 -0
- package/dist/yajilinBoard.d.ts +9 -0
- package/dist/yajilinBoard.js +91 -0
- package/dist/yajilinDraw.d.ts +2 -0
- package/dist/yajilinDraw.js +33 -0
- package/dist/yajilinGame.d.ts +15 -0
- package/dist/yajilinGame.js +58 -0
- package/dist/yajilinGenerate.d.ts +3 -0
- package/dist/yajilinGenerate.js +224 -0
- package/dist/yajilinMount.d.ts +2 -0
- package/dist/yajilinMount.js +162 -0
- package/dist/yajilinPlay.types.d.ts +1 -0
- package/dist/yajilinPlay.types.js +1 -0
- package/dist/yajilinSolve.d.ts +6 -0
- package/dist/yajilinSolve.js +82 -0
- package/dist/yajilinStrings.d.ts +32 -0
- package/dist/yajilinStrings.js +4 -0
- package/dist/yajilinStyle.d.ts +1 -0
- package/dist/yajilinStyle.js +12 -0
- package/package.json +180 -3
|
@@ -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,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 @@
|
|
|
1
|
+
export { drawKakuro } from "./kakuroDraw.js";
|
|
@@ -0,0 +1,13 @@
|
|
|
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 "./kakuroRate.ts";
|
|
7
|
+
export * from "./kakuroGame.ts";
|
|
8
|
+
export { drawKakuro } from "./kakuroDraw.ts";
|
|
9
|
+
export type { KakuroDrawOptions } from "./kakuroDraw.ts";
|
|
10
|
+
export { mountKakuro } from "./kakuroMount.ts";
|
|
11
|
+
export { KAKURO_PLAY_STYLE } from "./kakuroStyle.ts";
|
|
12
|
+
export { KAKURO_STRINGS } from "./kakuroStrings.ts";
|
|
13
|
+
export type * from "./kakuroPlay.types.ts";
|
|
@@ -0,0 +1,11 @@
|
|
|
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 "./kakuroRate.js";
|
|
7
|
+
export * from "./kakuroGame.js";
|
|
8
|
+
export { drawKakuro } from "./kakuroDraw.js";
|
|
9
|
+
export { mountKakuro } from "./kakuroMount.js";
|
|
10
|
+
export { KAKURO_PLAY_STYLE } from "./kakuroStyle.js";
|
|
11
|
+
export { KAKURO_STRINGS } from "./kakuroStrings.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const KAKURO_MIN_SIDE = 3;
|
|
2
|
+
export declare const KAKURO_MAX_SIDE = 12;
|
|
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;
|
|
7
|
+
/** How hard a puzzle is made, by what a person must do to solve it; see `rateKakuro`. */
|
|
8
|
+
export declare const KAKURO_LEVELS: readonly ["easy", "medium", "hard", "extra-hard"];
|
|
9
|
+
/** The sides on offer, counting the row and column of totals; any side from 5 to `KAKURO_MAX_SIDE` can be made. */
|
|
10
|
+
export declare const KAKURO_SIZES: readonly [6, 8, 10, 12];
|
|
11
|
+
export declare const KAKURO_LEAST_GENERATED_SIDE = 5;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const KAKURO_MIN_SIDE = 3;
|
|
2
|
+
export const KAKURO_MAX_SIDE = 12;
|
|
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;
|
|
7
|
+
/** How hard a puzzle is made, by what a person must do to solve it; see `rateKakuro`. */
|
|
8
|
+
export const KAKURO_LEVELS = ["easy", "medium", "hard", "extra-hard"];
|
|
9
|
+
/** The sides on offer, counting the row and column of totals; any side from 5 to `KAKURO_MAX_SIDE` can be made. */
|
|
10
|
+
export const KAKURO_SIZES = [6, 8, 10, 12];
|
|
11
|
+
export const KAKURO_LEAST_GENERATED_SIDE = 5;
|
|
@@ -0,0 +1,65 @@
|
|
|
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 KakuroLevel = "easy" | "medium" | "hard" | "extra-hard";
|
|
20
|
+
export type KakuroPuzzle = KakuroBoard & {
|
|
21
|
+
seed: number;
|
|
22
|
+
level: KakuroLevel;
|
|
23
|
+
solution: readonly number[];
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* How hard a board is, measured by solving it. `depth` 0 means the rules solve it, 1 that somebody has to suppose a
|
|
27
|
+
* digit and watch it break, 2 that more than that is needed; `plain` says the single-run rules were enough (no
|
|
28
|
+
* digit that must appear had to be traced). `probes` is how many suppositions depth 1 needed. The rest describes the runs.
|
|
29
|
+
*/
|
|
30
|
+
export type KakuroRating = {
|
|
31
|
+
depth: 0 | 1 | 2;
|
|
32
|
+
plain: boolean;
|
|
33
|
+
probes: number;
|
|
34
|
+
whites: number;
|
|
35
|
+
runs: number;
|
|
36
|
+
longest: number;
|
|
37
|
+
/** Mean number of squares in a run. */
|
|
38
|
+
meanRun: number;
|
|
39
|
+
/** Runs whose total can be made in only one way, as a share of the runs. */
|
|
40
|
+
fixedShare: number;
|
|
41
|
+
};
|
|
42
|
+
export type KakuroCheck = {
|
|
43
|
+
ok: boolean;
|
|
44
|
+
complete: boolean;
|
|
45
|
+
errors: readonly number[];
|
|
46
|
+
};
|
|
47
|
+
export type KakuroSolve = {
|
|
48
|
+
count: number;
|
|
49
|
+
solution: readonly number[] | null;
|
|
50
|
+
complete: boolean;
|
|
51
|
+
nodes: number;
|
|
52
|
+
};
|
|
53
|
+
export type KakuroGame = {
|
|
54
|
+
board: KakuroBoard;
|
|
55
|
+
values: readonly number[];
|
|
56
|
+
notes: readonly (readonly number[])[];
|
|
57
|
+
history: readonly {
|
|
58
|
+
values: readonly number[];
|
|
59
|
+
notes: readonly (readonly number[])[];
|
|
60
|
+
}[];
|
|
61
|
+
helped: boolean;
|
|
62
|
+
};
|
|
63
|
+
export type KakuroLanguage = "en" | "ja";
|
|
64
|
+
export type KakuroMaterial = "ivory" | "wood" | "slate";
|
|
65
|
+
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;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { KAKURO_MAX_SIDE, KAKURO_MIN_SIDE } from "./kakuro.constants.js";
|
|
2
|
+
export function isKakuroBoard(value) {
|
|
3
|
+
if (!value || typeof value !== "object")
|
|
4
|
+
return false;
|
|
5
|
+
const board = value;
|
|
6
|
+
if (!Number.isInteger(board.width) || board.width < KAKURO_MIN_SIDE || board.width > KAKURO_MAX_SIDE
|
|
7
|
+
|| !Number.isInteger(board.height) || board.height < KAKURO_MIN_SIDE || board.height > KAKURO_MAX_SIDE
|
|
8
|
+
|| !Array.isArray(board.cells) || board.cells.length !== board.width * board.height
|
|
9
|
+
|| board.cells.some(cell => !validCell(cell)))
|
|
10
|
+
return false;
|
|
11
|
+
const runs = collectRuns(board);
|
|
12
|
+
if (!runs)
|
|
13
|
+
return false;
|
|
14
|
+
return runs.length > 0 && runs.every(run => {
|
|
15
|
+
const length = run.cells.length;
|
|
16
|
+
const min = length * (length + 1) / 2;
|
|
17
|
+
const max = length * (19 - length) / 2;
|
|
18
|
+
return length >= 2 && length <= 9 && run.sum >= min && run.sum <= max;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
function validCell(cell) {
|
|
22
|
+
if (!cell || typeof cell !== "object")
|
|
23
|
+
return false;
|
|
24
|
+
if (cell.kind === "white")
|
|
25
|
+
return true;
|
|
26
|
+
return cell.kind === "black" && [cell.across, cell.down].every(value => value === null
|
|
27
|
+
|| Number.isInteger(value) && value >= 3 && value <= 45);
|
|
28
|
+
}
|
|
29
|
+
export function kakuroRuns(board) {
|
|
30
|
+
if (!isKakuroBoard(board))
|
|
31
|
+
throw new RangeError("Invalid Kakuro board");
|
|
32
|
+
return collectRuns(board);
|
|
33
|
+
}
|
|
34
|
+
function collectRuns(board) {
|
|
35
|
+
const runs = [];
|
|
36
|
+
const acrossCount = Array(board.cells.length).fill(0), downCount = Array(board.cells.length).fill(0);
|
|
37
|
+
for (let cell = 0; cell < board.cells.length; cell += 1) {
|
|
38
|
+
const x = cell % board.width, y = Math.floor(cell / board.width), tile = board.cells[cell];
|
|
39
|
+
if (tile.kind === "white")
|
|
40
|
+
continue;
|
|
41
|
+
const rightWhite = x + 1 < board.width && board.cells[cell + 1]?.kind === "white";
|
|
42
|
+
const downWhite = y + 1 < board.height && board.cells[cell + board.width]?.kind === "white";
|
|
43
|
+
if ((tile.across !== null) !== rightWhite || (tile.down !== null) !== downWhite)
|
|
44
|
+
return null;
|
|
45
|
+
if (rightWhite) {
|
|
46
|
+
const cells = [];
|
|
47
|
+
for (let at = cell + 1; at < (y + 1) * board.width && board.cells[at]?.kind === "white"; at += 1)
|
|
48
|
+
cells.push(at);
|
|
49
|
+
if (cells.length < 2 || cells.length > 9)
|
|
50
|
+
return null;
|
|
51
|
+
cells.forEach(at => { acrossCount[at] += 1; });
|
|
52
|
+
runs.push({ direction: "across", clueCell: cell, cells, sum: tile.across });
|
|
53
|
+
}
|
|
54
|
+
if (downWhite) {
|
|
55
|
+
const cells = [];
|
|
56
|
+
for (let at = cell + board.width; at < board.cells.length && board.cells[at]?.kind === "white"; at += board.width)
|
|
57
|
+
cells.push(at);
|
|
58
|
+
if (cells.length < 2 || cells.length > 9)
|
|
59
|
+
return null;
|
|
60
|
+
cells.forEach(at => { downCount[at] += 1; });
|
|
61
|
+
runs.push({ direction: "down", clueCell: cell, cells, sum: tile.down });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
for (let cell = 0; cell < board.cells.length; cell += 1) {
|
|
65
|
+
if (board.cells[cell].kind === "white" && (acrossCount[cell] !== 1 || downCount[cell] !== 1))
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
return runs;
|
|
69
|
+
}
|
|
70
|
+
/** Checks exact run sums and distinct digits independently of a stored answer. */
|
|
71
|
+
export function checkKakuro(board, values) {
|
|
72
|
+
if (!isKakuroBoard(board))
|
|
73
|
+
throw new RangeError("Invalid Kakuro board");
|
|
74
|
+
if (values.length !== board.cells.length)
|
|
75
|
+
throw new RangeError("Invalid Kakuro values");
|
|
76
|
+
const errors = new Set(), runs = kakuroRuns(board);
|
|
77
|
+
values.forEach((value, cell) => {
|
|
78
|
+
if (board.cells[cell].kind === "black" ? value !== 0 : !Number.isInteger(value) || value < 0 || value > 9)
|
|
79
|
+
errors.add(cell);
|
|
80
|
+
});
|
|
81
|
+
for (const run of runs) {
|
|
82
|
+
const filled = run.cells.map(cell => values[cell]).filter(value => value > 0);
|
|
83
|
+
const duplicate = new Set(filled.filter((value, index) => filled.indexOf(value) !== index));
|
|
84
|
+
const sum = filled.reduce((total, value) => total + value, 0);
|
|
85
|
+
const missing = run.cells.length - filled.length;
|
|
86
|
+
if (duplicate.size || sum > run.sum || !canReach(run.sum - sum, missing, filled))
|
|
87
|
+
run.cells.forEach(cell => errors.add(cell));
|
|
88
|
+
if (missing === 0 && sum !== run.sum)
|
|
89
|
+
run.cells.forEach(cell => errors.add(cell));
|
|
90
|
+
}
|
|
91
|
+
const complete = board.cells.every((cell, index) => cell.kind === "black" || values[index] > 0);
|
|
92
|
+
return { ok: complete && errors.size === 0, complete, errors: [...errors].sort((a, b) => a - b) };
|
|
93
|
+
}
|
|
94
|
+
/** Allows unfinished runs while flagging duplicate digits and impossible remaining totals. */
|
|
95
|
+
export function progressKakuro(board, values) {
|
|
96
|
+
const result = checkKakuro(board, values);
|
|
97
|
+
return { ...result, ok: result.errors.length === 0 };
|
|
98
|
+
}
|
|
99
|
+
export function canReach(target, slots, used) {
|
|
100
|
+
if (slots === 0)
|
|
101
|
+
return target === 0;
|
|
102
|
+
if (target < 1 || slots > 9)
|
|
103
|
+
return false;
|
|
104
|
+
const mask = used.reduce((bits, value) => bits | (1 << value), 0);
|
|
105
|
+
const visit = (next, remaining, count) => {
|
|
106
|
+
if (count === 0)
|
|
107
|
+
return remaining === 0;
|
|
108
|
+
for (let digit = next; digit <= 9; digit += 1) {
|
|
109
|
+
if (mask & (1 << digit))
|
|
110
|
+
continue;
|
|
111
|
+
if (visit(digit + 1, remaining - digit, count - 1))
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
return false;
|
|
115
|
+
};
|
|
116
|
+
return visit(1, target, slots);
|
|
117
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { KakuroBoard, KakuroLevel } from "./kakuro.types.ts";
|
|
2
|
+
import type { Random } from "./random.ts";
|
|
3
|
+
/** How the black squares are scattered, and the longest run a level allows. */
|
|
4
|
+
export declare const SHAPE: Record<KakuroLevel, {
|
|
5
|
+
black: number;
|
|
6
|
+
longest: number;
|
|
7
|
+
}>;
|
|
8
|
+
/** Which squares are white: no run shorter than two, none longer than the level allows, and all of them one connected piece. */
|
|
9
|
+
export declare function kakuroPattern(side: number, level: KakuroLevel, random: Random): boolean[] | null;
|
|
10
|
+
/** How many different sets of digits can make `sum` out of `length` squares. */
|
|
11
|
+
export declare function kakuroWays(length: number, sum: number): number;
|
|
12
|
+
/** A random digit for every white square with no digit repeated in a run, or null when the search gives up. */
|
|
13
|
+
export declare function kakuroDigits(side: number, white: readonly boolean[], random: Random): number[] | null;
|
|
14
|
+
/**
|
|
15
|
+
* Changes digits one square at a time, keeping every run free of repeats, to make runs whose totals can be made in
|
|
16
|
+
* few ways: a run that says a lot about its digits lets the solver start from the rules alone. A change is kept when
|
|
17
|
+
* it does not raise the count of ways, summed over runs, which is `weight` raised to the number of ways.
|
|
18
|
+
*/
|
|
19
|
+
export declare function kakuroTighten(side: number, white: readonly boolean[], digits: number[], random: Random, rounds: number): void;
|
|
20
|
+
/** The totals of a pattern and its digits, as a board. */
|
|
21
|
+
export declare function kakuroBoardOf(side: number, white: readonly boolean[], digits: readonly number[]): KakuroBoard;
|
|
22
|
+
/**
|
|
23
|
+
* A board with exactly one answer, or null. While the board has other answers, each one is broken: a square the
|
|
24
|
+
* two answers disagree on is turned black (when every run stays two long and the squares stay one piece) or given
|
|
25
|
+
* another digit that still repeats nowhere in its runs, which changes two totals.
|
|
26
|
+
*/
|
|
27
|
+
export declare function candidateKakuro(side: number, level: KakuroLevel, random: Random): {
|
|
28
|
+
board: KakuroBoard;
|
|
29
|
+
digits: readonly number[];
|
|
30
|
+
} | null;
|