@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,63 @@
|
|
|
1
|
+
export type YajilinDirection = "up" | "right" | "down" | "left";
|
|
2
|
+
export type YajilinClue = {
|
|
3
|
+
direction: YajilinDirection;
|
|
4
|
+
count: number;
|
|
5
|
+
};
|
|
6
|
+
export type YajilinBoard = {
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
clues: readonly (YajilinClue | null)[];
|
|
10
|
+
};
|
|
11
|
+
export type YajilinPuzzle = YajilinBoard & {
|
|
12
|
+
seed: number;
|
|
13
|
+
solution: {
|
|
14
|
+
shaded: readonly boolean[];
|
|
15
|
+
edges: readonly number[];
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
export type YajilinCheck = {
|
|
19
|
+
ok: boolean;
|
|
20
|
+
errors: readonly number[];
|
|
21
|
+
};
|
|
22
|
+
export type YajilinSolution = {
|
|
23
|
+
shaded: readonly boolean[];
|
|
24
|
+
edges: readonly number[];
|
|
25
|
+
};
|
|
26
|
+
export type YajilinSolve = {
|
|
27
|
+
count: number;
|
|
28
|
+
solution: YajilinSolution | null;
|
|
29
|
+
complete: boolean;
|
|
30
|
+
nodes: number;
|
|
31
|
+
};
|
|
32
|
+
export type YajilinGame = {
|
|
33
|
+
board: YajilinBoard;
|
|
34
|
+
shaded: readonly boolean[];
|
|
35
|
+
edges: readonly number[];
|
|
36
|
+
history: readonly {
|
|
37
|
+
shaded: readonly boolean[];
|
|
38
|
+
edges: readonly number[];
|
|
39
|
+
}[];
|
|
40
|
+
helped: boolean;
|
|
41
|
+
};
|
|
42
|
+
export type YajilinDrawOptions = {
|
|
43
|
+
shaded?: readonly boolean[];
|
|
44
|
+
edges?: readonly number[];
|
|
45
|
+
selected?: number;
|
|
46
|
+
errors?: readonly number[];
|
|
47
|
+
material?: "ivory" | "wood" | "slate";
|
|
48
|
+
pieces?: "plain" | "tiles";
|
|
49
|
+
language?: "en" | "ja";
|
|
50
|
+
};
|
|
51
|
+
export type YajilinMountOptions = YajilinDrawOptions & {
|
|
52
|
+
board: YajilinBoard;
|
|
53
|
+
progress?: string;
|
|
54
|
+
onChange?: (game: YajilinGame) => void;
|
|
55
|
+
onFinish?: (game: YajilinGame) => void;
|
|
56
|
+
};
|
|
57
|
+
export type YajilinMount = {
|
|
58
|
+
game: () => YajilinGame;
|
|
59
|
+
progress: () => string;
|
|
60
|
+
set: (options: YajilinDrawOptions) => void;
|
|
61
|
+
restart: () => void;
|
|
62
|
+
destroy: () => void;
|
|
63
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { YajilinBoard, YajilinCheck, YajilinDirection } from "./yajilin.types.ts";
|
|
2
|
+
export declare const yajilinEdgeCount: (board: YajilinBoard) => number;
|
|
3
|
+
export declare function isYajilinBoard(value: unknown): value is YajilinBoard;
|
|
4
|
+
export declare function yajilinNeighbors(board: YajilinBoard, cell: number): {
|
|
5
|
+
cell: number;
|
|
6
|
+
edge: number;
|
|
7
|
+
}[];
|
|
8
|
+
export declare function checkYajilin(board: YajilinBoard, shaded: readonly boolean[], lines: readonly number[]): YajilinCheck;
|
|
9
|
+
export declare function yajilinRay(board: YajilinBoard, cell: number, direction: YajilinDirection): number[];
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { YAJILIN_SIZES } from "./yajilin.constants.js";
|
|
2
|
+
const directionDelta = { up: [0, -1], right: [1, 0], down: [0, 1], left: [-1, 0] };
|
|
3
|
+
export const yajilinEdgeCount = (board) => board.height * (board.width - 1) + board.width * (board.height - 1);
|
|
4
|
+
export function isYajilinBoard(value) {
|
|
5
|
+
if (!value || typeof value !== "object")
|
|
6
|
+
return false;
|
|
7
|
+
const board = value;
|
|
8
|
+
if (!YAJILIN_SIZES.includes(board.width) || board.width !== board.height
|
|
9
|
+
|| !Array.isArray(board.clues) || board.clues.length !== board.width * board.height)
|
|
10
|
+
return false;
|
|
11
|
+
return board.clues.every(clue => clue === null || (!!clue && typeof clue === "object"
|
|
12
|
+
&& Object.hasOwn(directionDelta, clue.direction) && Number.isInteger(clue.count) && clue.count >= 0 && clue.count < board.width));
|
|
13
|
+
}
|
|
14
|
+
export function yajilinNeighbors(board, cell) {
|
|
15
|
+
const x = cell % board.width, y = Math.floor(cell / board.width), horizontal = board.height * (board.width - 1), result = [];
|
|
16
|
+
if (y > 0)
|
|
17
|
+
result.push({ cell: cell - board.width, edge: horizontal + (y - 1) * board.width + x });
|
|
18
|
+
if (x < board.width - 1)
|
|
19
|
+
result.push({ cell: cell + 1, edge: y * (board.width - 1) + x });
|
|
20
|
+
if (y < board.height - 1)
|
|
21
|
+
result.push({ cell: cell + board.width, edge: horizontal + y * board.width + x });
|
|
22
|
+
if (x > 0)
|
|
23
|
+
result.push({ cell: cell - 1, edge: y * (board.width - 1) + x - 1 });
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
function ray(board, cell, direction) {
|
|
27
|
+
const [dx, dy] = directionDelta[direction], cells = [];
|
|
28
|
+
let x = cell % board.width + dx, y = Math.floor(cell / board.width) + dy;
|
|
29
|
+
while (x >= 0 && x < board.width && y >= 0 && y < board.height) {
|
|
30
|
+
cells.push(y * board.width + x);
|
|
31
|
+
x += dx;
|
|
32
|
+
y += dy;
|
|
33
|
+
}
|
|
34
|
+
return cells;
|
|
35
|
+
}
|
|
36
|
+
export function checkYajilin(board, shaded, lines) {
|
|
37
|
+
if (!isYajilinBoard(board))
|
|
38
|
+
throw new RangeError("Invalid Yajilin board");
|
|
39
|
+
const size = board.width, total = size * size, edges = new Set(lines), errors = new Set();
|
|
40
|
+
if (shaded.length !== total || !shaded.every(value => typeof value === "boolean"))
|
|
41
|
+
return { ok: false, errors: [] };
|
|
42
|
+
if (edges.size !== lines.length || lines.some(edge => !Number.isInteger(edge) || edge < 0 || edge >= yajilinEdgeCount(board)))
|
|
43
|
+
return { ok: false, errors: [] };
|
|
44
|
+
for (let cell = 0; cell < total; cell += 1) {
|
|
45
|
+
const clue = board.clues[cell];
|
|
46
|
+
if (clue && shaded[cell])
|
|
47
|
+
errors.add(cell);
|
|
48
|
+
if (shaded[cell]) {
|
|
49
|
+
if (cell % size < size - 1 && shaded[cell + 1]) {
|
|
50
|
+
errors.add(cell);
|
|
51
|
+
errors.add(cell + 1);
|
|
52
|
+
}
|
|
53
|
+
if (cell + size < total && shaded[cell + size]) {
|
|
54
|
+
errors.add(cell);
|
|
55
|
+
errors.add(cell + size);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (clue && ray(board, cell, clue.direction).filter(next => shaded[next]).length !== clue.count)
|
|
59
|
+
errors.add(cell);
|
|
60
|
+
}
|
|
61
|
+
const degrees = board.clues.map((clue, cell) => {
|
|
62
|
+
const degree = yajilinNeighbors(board, cell).filter(next => edges.has(next.edge)).length;
|
|
63
|
+
if ((clue || shaded[cell]) && degree > 0)
|
|
64
|
+
errors.add(cell);
|
|
65
|
+
if (!clue && !shaded[cell] && degree !== 2)
|
|
66
|
+
errors.add(cell);
|
|
67
|
+
return degree;
|
|
68
|
+
});
|
|
69
|
+
if (edges.size) {
|
|
70
|
+
const first = degrees.findIndex(degree => degree > 0), reached = new Set([first]), stack = [first];
|
|
71
|
+
while (stack.length) {
|
|
72
|
+
const cell = stack.pop();
|
|
73
|
+
for (const next of yajilinNeighbors(board, cell))
|
|
74
|
+
if (edges.has(next.edge) && !reached.has(next.cell)) {
|
|
75
|
+
reached.add(next.cell);
|
|
76
|
+
stack.push(next.cell);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const loopCells = degrees.filter(degree => degree > 0).length;
|
|
80
|
+
if (reached.size !== loopCells || edges.size !== loopCells)
|
|
81
|
+
for (const cell of reached)
|
|
82
|
+
errors.add(cell);
|
|
83
|
+
}
|
|
84
|
+
const playable = board.clues.filter((clue, cell) => !clue && !shaded[cell]).length;
|
|
85
|
+
const ok = playable >= 4 && edges.size === playable && errors.size === 0
|
|
86
|
+
&& degrees.every((degree, cell) => board.clues[cell] || shaded[cell] ? degree === 0 : degree === 2);
|
|
87
|
+
return { ok, errors: [...errors] };
|
|
88
|
+
}
|
|
89
|
+
export function yajilinRay(board, cell, direction) {
|
|
90
|
+
return ray(board, cell, direction);
|
|
91
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { isYajilinBoard, yajilinNeighbors } from "./yajilinBoard.js";
|
|
2
|
+
import { YAJILIN_STRINGS } from "./yajilinStrings.js";
|
|
3
|
+
const arrows = { up: "↑", right: "→", down: "↓", left: "←" };
|
|
4
|
+
export function drawYajilin(board, options = {}) {
|
|
5
|
+
if (!isYajilinBoard(board))
|
|
6
|
+
throw new RangeError("Invalid Yajilin board");
|
|
7
|
+
const words = YAJILIN_STRINGS[options.language ?? "en"], size = 52, pad = 14;
|
|
8
|
+
const palette = { ivory: ["var(--kz-paper,#fbf8f1)", "var(--kz-ink,#1f2320)", "var(--kz-grid,#cfc6b2)"], wood: ["#e5cda6", "#493e2f", "#bca582"], slate: ["#262a27", "#ece8dc", "#454a44"] };
|
|
9
|
+
const [paper, ink, grid] = palette[options.material ?? "ivory"], edges = new Set(options.edges ?? []), parts = [];
|
|
10
|
+
for (let cell = 0; cell < board.clues.length; cell += 1) {
|
|
11
|
+
const x = pad + cell % board.width * size, y = pad + Math.floor(cell / board.width) * size, clue = board.clues[cell];
|
|
12
|
+
const bad = options.errors?.includes(cell), active = options.selected === cell;
|
|
13
|
+
parts.push(`<rect x="${x - size / 2}" y="${y - size / 2}" width="${size}" height="${size}" fill="${active ? ink : "transparent"}" fill-opacity="${active ? ".08" : "1"}" stroke="${grid}" stroke-width="1"/>`);
|
|
14
|
+
if (options.shaded?.[cell])
|
|
15
|
+
parts.push(`<rect x="${x - 17}" y="${y - 17}" width="34" height="34" rx="2" fill="${bad ? "var(--kz-bad,#b5452c)" : ink}"/>`);
|
|
16
|
+
if (clue) {
|
|
17
|
+
if (options.pieces === "tiles")
|
|
18
|
+
parts.push(`<rect x="${x - 19}" y="${y - 19}" width="38" height="38" rx="5" fill="${paper}" stroke="${grid}"/>`);
|
|
19
|
+
parts.push(`<text x="${x - 2}" y="${y + 1}" text-anchor="middle" dominant-baseline="middle" fill="${bad ? "var(--kz-bad,#b5452c)" : ink}" font-size="20" font-weight="700" font-family="system-ui,sans-serif">${arrows[clue.direction]}</text>`);
|
|
20
|
+
parts.push(`<text x="${x + 11}" y="${y + 14}" text-anchor="middle" dominant-baseline="middle" fill="${ink}" font-size="12" font-weight="700" font-family="system-ui,sans-serif">${clue.count}</text>`);
|
|
21
|
+
}
|
|
22
|
+
for (const next of yajilinNeighbors(board, cell))
|
|
23
|
+
if (cell < next.cell && edges.has(next.edge)) {
|
|
24
|
+
const nx = pad + next.cell % board.width * size, ny = pad + Math.floor(next.cell / board.width) * size;
|
|
25
|
+
parts.push(`<line x1="${x}" y1="${y}" x2="${nx}" y2="${ny}" stroke="${bad ? "var(--kz-bad,#b5452c)" : ink}" stroke-width="5" stroke-linecap="round"/>`);
|
|
26
|
+
}
|
|
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 * size}" cy="${pad + y * size}" r="2" fill="${grid}"/>`);
|
|
31
|
+
}
|
|
32
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${pad * 2 + (board.width - 1) * size} ${pad * 2 + (board.height - 1) * size}" role="img" aria-label="${words.title}" style="display:block;width:100%;height:auto;background:${paper}">${parts.join("")}</svg>`;
|
|
33
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { YajilinBoard, YajilinGame } from "./yajilin.types.ts";
|
|
2
|
+
export declare function newYajilin(board: YajilinBoard): YajilinGame;
|
|
3
|
+
export declare function toggleYajilinShade(game: YajilinGame, cell: number): YajilinGame;
|
|
4
|
+
export declare function toggleYajilinEdge(game: YajilinGame, edge: number): YajilinGame;
|
|
5
|
+
export declare function undoYajilin(game: YajilinGame): YajilinGame;
|
|
6
|
+
export declare function restartYajilin(game: YajilinGame): YajilinGame;
|
|
7
|
+
export declare function hintYajilin(game: YajilinGame): {
|
|
8
|
+
type: "shade";
|
|
9
|
+
cell: number;
|
|
10
|
+
} | {
|
|
11
|
+
type: "edge";
|
|
12
|
+
edge: number;
|
|
13
|
+
} | null;
|
|
14
|
+
export declare function encodeYajilin(game: YajilinGame): string;
|
|
15
|
+
export declare function decodeYajilin(code: string): YajilinGame | null;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { isYajilinBoard, yajilinEdgeCount } from "./yajilinBoard.js";
|
|
2
|
+
import { solveYajilin } from "./yajilinSolve.js";
|
|
3
|
+
export function newYajilin(board) {
|
|
4
|
+
if (!isYajilinBoard(board))
|
|
5
|
+
throw new RangeError("Invalid Yajilin board");
|
|
6
|
+
return { board: { width: board.width, height: board.height, clues: board.clues.map(clue => clue ? { ...clue } : null) }, shaded: Array(board.width * board.height).fill(false), edges: [], history: [], helped: false };
|
|
7
|
+
}
|
|
8
|
+
function snapshot(game) { return { shaded: [...game.shaded], edges: [...game.edges] }; }
|
|
9
|
+
export function toggleYajilinShade(game, cell) {
|
|
10
|
+
if (!Number.isInteger(cell) || cell < 0 || cell >= game.shaded.length || game.board.clues[cell])
|
|
11
|
+
return game;
|
|
12
|
+
const shaded = [...game.shaded];
|
|
13
|
+
shaded[cell] = !shaded[cell];
|
|
14
|
+
return { ...game, shaded, history: [...game.history, snapshot(game)] };
|
|
15
|
+
}
|
|
16
|
+
export function toggleYajilinEdge(game, edge) {
|
|
17
|
+
if (!Number.isInteger(edge) || edge < 0 || edge >= yajilinEdgeCount(game.board))
|
|
18
|
+
return game;
|
|
19
|
+
const edges = game.edges.includes(edge) ? game.edges.filter(value => value !== edge) : [...game.edges, edge].sort((a, b) => a - b);
|
|
20
|
+
return { ...game, edges, history: [...game.history, snapshot(game)] };
|
|
21
|
+
}
|
|
22
|
+
export function undoYajilin(game) {
|
|
23
|
+
const previous = game.history.at(-1);
|
|
24
|
+
return previous ? { ...game, shaded: [...previous.shaded], edges: [...previous.edges], history: game.history.slice(0, -1) } : game;
|
|
25
|
+
}
|
|
26
|
+
export function restartYajilin(game) { return { ...newYajilin(game.board), helped: false }; }
|
|
27
|
+
export function hintYajilin(game) {
|
|
28
|
+
const result = solveYajilin(game.board);
|
|
29
|
+
if (!result.complete || result.count !== 1)
|
|
30
|
+
return null;
|
|
31
|
+
const cell = result.solution.shaded.findIndex((value, index) => value !== game.shaded[index]);
|
|
32
|
+
if (cell >= 0)
|
|
33
|
+
return { type: "shade", cell };
|
|
34
|
+
const edge = result.solution.edges.find(value => !game.edges.includes(value));
|
|
35
|
+
return edge === undefined ? null : { type: "edge", edge };
|
|
36
|
+
}
|
|
37
|
+
export function encodeYajilin(game) {
|
|
38
|
+
return JSON.stringify({ version: 1, board: game.board, shaded: game.shaded, edges: game.edges, helped: game.helped });
|
|
39
|
+
}
|
|
40
|
+
export function decodeYajilin(code) {
|
|
41
|
+
try {
|
|
42
|
+
if (code.length > 40000)
|
|
43
|
+
return null;
|
|
44
|
+
const value = JSON.parse(code);
|
|
45
|
+
if (value.version !== 1 || !isYajilinBoard(value.board) || !Array.isArray(value.shaded)
|
|
46
|
+
|| value.shaded.length !== value.board.width * value.board.height || !value.shaded.every((cell) => typeof cell === "boolean")
|
|
47
|
+
|| !Array.isArray(value.edges) || !value.edges.every((edge) => Number.isInteger(edge))
|
|
48
|
+
|| typeof value.helped !== "boolean")
|
|
49
|
+
return null;
|
|
50
|
+
const edges = [...new Set(value.edges)].sort((a, b) => a - b);
|
|
51
|
+
if (edges.length !== value.edges.length || edges.some(edge => edge < 0 || edge >= yajilinEdgeCount(value.board)))
|
|
52
|
+
return null;
|
|
53
|
+
return { ...newYajilin(value.board), shaded: [...value.shaded], edges, helped: value.helped };
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { isKazuSeed, seededRandom } from "./random.js";
|
|
2
|
+
import { checkYajilin, isYajilinBoard, yajilinNeighbors } from "./yajilinBoard.js";
|
|
3
|
+
import { solveYajilin } from "./yajilinSolve.js";
|
|
4
|
+
const layouts = [
|
|
5
|
+
{
|
|
6
|
+
loop: [6, 7, 8, 13, 18, 17, 16, 11],
|
|
7
|
+
shaded: [1, 22, 5, 9, 24, 12, 20],
|
|
8
|
+
clues: [
|
|
9
|
+
[0, "left", 0],
|
|
10
|
+
[2, "up", 0],
|
|
11
|
+
[3, "up", 0],
|
|
12
|
+
[4, "down", 2],
|
|
13
|
+
[10, "up", 1],
|
|
14
|
+
[14, "down", 1],
|
|
15
|
+
[15, "left", 0],
|
|
16
|
+
[19, "up", 1],
|
|
17
|
+
[21, "down", 0],
|
|
18
|
+
[23, "up", 0],
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
loop: [6, 7, 8, 13, 18, 17, 16, 11],
|
|
23
|
+
shaded: [0, 21, 15, 2, 14, 4, 12],
|
|
24
|
+
clues: [
|
|
25
|
+
[1, "right", 2],
|
|
26
|
+
[3, "left", 2],
|
|
27
|
+
[5, "down", 1],
|
|
28
|
+
[9, "right", 0],
|
|
29
|
+
[10, "down", 1],
|
|
30
|
+
[19, "up", 2],
|
|
31
|
+
[20, "left", 0],
|
|
32
|
+
[22, "up", 2],
|
|
33
|
+
[23, "right", 0],
|
|
34
|
+
[24, "down", 0],
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
loop: [6, 7, 8, 13, 18, 17, 16, 11],
|
|
39
|
+
shaded: [24, 12, 22, 4, 15, 0, 2],
|
|
40
|
+
clues: [
|
|
41
|
+
[1, "right", 2],
|
|
42
|
+
[3, "down", 0],
|
|
43
|
+
[5, "down", 1],
|
|
44
|
+
[9, "right", 0],
|
|
45
|
+
[10, "left", 0],
|
|
46
|
+
[14, "up", 1],
|
|
47
|
+
[19, "left", 1],
|
|
48
|
+
[20, "left", 0],
|
|
49
|
+
[21, "down", 0],
|
|
50
|
+
[23, "right", 1],
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
loop: [6, 7, 8, 13, 18, 17, 16, 11],
|
|
55
|
+
shaded: [12, 9, 2, 21, 0, 19, 10],
|
|
56
|
+
clues: [
|
|
57
|
+
[1, "right", 1],
|
|
58
|
+
[3, "up", 0],
|
|
59
|
+
[4, "down", 2],
|
|
60
|
+
[5, "up", 1],
|
|
61
|
+
[14, "left", 2],
|
|
62
|
+
[15, "right", 1],
|
|
63
|
+
[20, "down", 0],
|
|
64
|
+
[22, "up", 2],
|
|
65
|
+
[23, "up", 0],
|
|
66
|
+
[24, "up", 2],
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
loop: [6, 7, 8, 13, 18, 17, 16, 11],
|
|
71
|
+
shaded: [20, 3, 22, 14, 1, 12, 24],
|
|
72
|
+
clues: [
|
|
73
|
+
[0, "left", 0],
|
|
74
|
+
[2, "down", 2],
|
|
75
|
+
[4, "up", 0],
|
|
76
|
+
[5, "right", 0],
|
|
77
|
+
[9, "down", 2],
|
|
78
|
+
[10, "up", 0],
|
|
79
|
+
[15, "right", 0],
|
|
80
|
+
[19, "up", 1],
|
|
81
|
+
[21, "right", 2],
|
|
82
|
+
[23, "up", 1],
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
loop: [6, 7, 8, 13, 18, 17, 16, 11],
|
|
87
|
+
shaded: [14, 3, 21, 15, 24, 0, 12],
|
|
88
|
+
clues: [
|
|
89
|
+
[1, "up", 0],
|
|
90
|
+
[2, "right", 1],
|
|
91
|
+
[4, "left", 2],
|
|
92
|
+
[5, "right", 0],
|
|
93
|
+
[9, "left", 0],
|
|
94
|
+
[10, "down", 1],
|
|
95
|
+
[19, "left", 1],
|
|
96
|
+
[20, "right", 2],
|
|
97
|
+
[22, "right", 1],
|
|
98
|
+
[23, "left", 1],
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
loop: [6, 7, 8, 13, 18, 17, 16, 11],
|
|
103
|
+
shaded: [15, 24, 9, 21, 5, 3, 1],
|
|
104
|
+
clues: [
|
|
105
|
+
[0, "left", 0],
|
|
106
|
+
[2, "right", 1],
|
|
107
|
+
[4, "right", 0],
|
|
108
|
+
[10, "up", 1],
|
|
109
|
+
[12, "down", 0],
|
|
110
|
+
[14, "right", 0],
|
|
111
|
+
[19, "left", 1],
|
|
112
|
+
[20, "down", 0],
|
|
113
|
+
[22, "up", 0],
|
|
114
|
+
[23, "down", 0],
|
|
115
|
+
],
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
loop: [6, 7, 8, 13, 18, 17, 16, 11],
|
|
119
|
+
shaded: [4, 2, 0, 10, 23, 21, 19],
|
|
120
|
+
clues: [
|
|
121
|
+
[1, "right", 2],
|
|
122
|
+
[3, "left", 2],
|
|
123
|
+
[5, "up", 1],
|
|
124
|
+
[9, "down", 1],
|
|
125
|
+
[12, "down", 0],
|
|
126
|
+
[14, "up", 1],
|
|
127
|
+
[15, "right", 1],
|
|
128
|
+
[20, "right", 2],
|
|
129
|
+
[22, "right", 1],
|
|
130
|
+
[24, "up", 2],
|
|
131
|
+
],
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
loop: [6, 7, 8, 13, 18, 23, 22, 21, 16, 11],
|
|
135
|
+
shaded: [10, 4, 2, 17, 0, 24],
|
|
136
|
+
clues: [
|
|
137
|
+
[1, "right", 2],
|
|
138
|
+
[3, "up", 0],
|
|
139
|
+
[5, "down", 1],
|
|
140
|
+
[9, "down", 1],
|
|
141
|
+
[12, "down", 1],
|
|
142
|
+
[14, "up", 1],
|
|
143
|
+
[15, "right", 1],
|
|
144
|
+
[19, "left", 1],
|
|
145
|
+
[20, "down", 0],
|
|
146
|
+
],
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
loop: [6, 7, 8, 13, 18, 23, 22, 21, 16, 11],
|
|
150
|
+
shaded: [3, 15, 19, 17, 1, 5],
|
|
151
|
+
clues: [
|
|
152
|
+
[0, "left", 0],
|
|
153
|
+
[2, "right", 1],
|
|
154
|
+
[4, "down", 1],
|
|
155
|
+
[9, "down", 1],
|
|
156
|
+
[10, "down", 1],
|
|
157
|
+
[12, "right", 0],
|
|
158
|
+
[14, "down", 1],
|
|
159
|
+
[20, "left", 0],
|
|
160
|
+
[24, "up", 1],
|
|
161
|
+
],
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
loop: [6, 7, 8, 13, 18, 23, 22, 21, 16, 11],
|
|
165
|
+
shaded: [2, 17, 4, 0, 20, 10],
|
|
166
|
+
clues: [
|
|
167
|
+
[1, "left", 1],
|
|
168
|
+
[3, "right", 1],
|
|
169
|
+
[5, "up", 1],
|
|
170
|
+
[9, "left", 0],
|
|
171
|
+
[12, "up", 1],
|
|
172
|
+
[14, "down", 0],
|
|
173
|
+
[15, "up", 2],
|
|
174
|
+
[19, "right", 0],
|
|
175
|
+
[24, "down", 0],
|
|
176
|
+
],
|
|
177
|
+
},
|
|
178
|
+
];
|
|
179
|
+
function transformCell(cell, turns, reflect) {
|
|
180
|
+
let x = cell % 5, y = Math.floor(cell / 5);
|
|
181
|
+
if (reflect)
|
|
182
|
+
x = 4 - x;
|
|
183
|
+
for (let turn = 0; turn < turns; turn += 1)
|
|
184
|
+
[x, y] = [4 - y, x];
|
|
185
|
+
return y * 5 + x;
|
|
186
|
+
}
|
|
187
|
+
function transformDirection(direction, turns, reflect) {
|
|
188
|
+
const vector = { up: [0, -1], right: [1, 0], down: [0, 1], left: [-1, 0] };
|
|
189
|
+
let [x, y] = vector[direction];
|
|
190
|
+
if (reflect)
|
|
191
|
+
x = -x;
|
|
192
|
+
for (let turn = 0; turn < turns; turn += 1)
|
|
193
|
+
[x, y] = [-y, x];
|
|
194
|
+
return (Object.entries(vector).find(([, value]) => value[0] === x && value[1] === y)?.[0] ?? "up");
|
|
195
|
+
}
|
|
196
|
+
function edgesFromLoop(board, loop) {
|
|
197
|
+
return loop.map((cell, index) => {
|
|
198
|
+
const next = loop[(index + 1) % loop.length];
|
|
199
|
+
return yajilinNeighbors(board, cell).find(item => item.cell === next).edge;
|
|
200
|
+
}).sort((a, b) => a - b);
|
|
201
|
+
}
|
|
202
|
+
/** Seeded original 5×5 families, accepted only after a completed uniqueness proof. */
|
|
203
|
+
export function generateYajilin(size = 5, seed = 1) {
|
|
204
|
+
if (size !== 5 || !isKazuSeed(seed))
|
|
205
|
+
throw new RangeError("Invalid Yajilin settings");
|
|
206
|
+
const random = seededRandom(seed), layout = layouts[Math.floor(random() * layouts.length)];
|
|
207
|
+
const turns = Math.floor(random() * 4), reflect = random() < 0.5;
|
|
208
|
+
const clues = Array(25).fill(null);
|
|
209
|
+
for (const [cell, direction, count] of layout.clues) {
|
|
210
|
+
const transformed = transformCell(cell, turns, reflect);
|
|
211
|
+
clues[transformed] = { direction: transformDirection(direction, turns, reflect), count };
|
|
212
|
+
}
|
|
213
|
+
const board = { width: 5, height: 5, clues };
|
|
214
|
+
const shaded = Array(25).fill(false);
|
|
215
|
+
for (const cell of layout.shaded)
|
|
216
|
+
shaded[transformCell(cell, turns, reflect)] = true;
|
|
217
|
+
const expectedEdges = edgesFromLoop(board, layout.loop.map(cell => transformCell(cell, turns, reflect)));
|
|
218
|
+
if (!isYajilinBoard(board) || !checkYajilin(board, shaded, expectedEdges).ok)
|
|
219
|
+
throw new Error("Invalid Yajilin layout");
|
|
220
|
+
const proof = solveYajilin(board);
|
|
221
|
+
if (!proof.complete || proof.count !== 1 || !proof.solution)
|
|
222
|
+
throw new Error("No uniquely proved Yajilin found within the generation budget; try another seed");
|
|
223
|
+
return { ...board, seed, solution: proof.solution };
|
|
224
|
+
}
|