@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,86 @@
|
|
|
1
|
+
import type { RippleLanguage } from "./ripple.types.ts";
|
|
2
|
+
export declare const RIPPLE_STRINGS: {
|
|
3
|
+
readonly en: {
|
|
4
|
+
readonly title: "Ripple Effect";
|
|
5
|
+
readonly help: "Fill each room with 1 through its size. Equal numbers in a row or column need at least that many cells between them.";
|
|
6
|
+
readonly undo: "Undo";
|
|
7
|
+
readonly check: "Check";
|
|
8
|
+
readonly hint: "Hint";
|
|
9
|
+
readonly restart: "Restart";
|
|
10
|
+
readonly pencil: "Pencil";
|
|
11
|
+
readonly won: "Every room and ripple rule is correct.";
|
|
12
|
+
readonly wrong: "Some entries break a room or ripple rule.";
|
|
13
|
+
readonly checked: "Fill the rooms and check the spacing.";
|
|
14
|
+
readonly noHint: "No single proved answer is available yet.";
|
|
15
|
+
readonly hintWhy: "This value follows from the room and spacing rules.";
|
|
16
|
+
readonly view: "Just the board";
|
|
17
|
+
readonly close: "Close";
|
|
18
|
+
readonly cell: "Row {row}, column {column}";
|
|
19
|
+
readonly clue: "clue {value}";
|
|
20
|
+
readonly empty: "empty";
|
|
21
|
+
readonly entry: "{value}";
|
|
22
|
+
readonly note: "notes {values}";
|
|
23
|
+
};
|
|
24
|
+
readonly ja: {
|
|
25
|
+
readonly title: "波及効果";
|
|
26
|
+
readonly help: "部屋には1から部屋の大きさまでの数字を一つずつ入れます。同じ数字を同じ行・列に置くときは、その数字以上のマスを間に空けます。";
|
|
27
|
+
readonly undo: "戻す";
|
|
28
|
+
readonly check: "確認";
|
|
29
|
+
readonly hint: "ヒント";
|
|
30
|
+
readonly restart: "やり直す";
|
|
31
|
+
readonly pencil: "メモ";
|
|
32
|
+
readonly won: "部屋と間隔のルールをすべて満たしました。";
|
|
33
|
+
readonly wrong: "部屋か数字の間隔に誤りがあります。";
|
|
34
|
+
readonly checked: "部屋を埋めて間隔を確認してください。";
|
|
35
|
+
readonly noHint: "確定できるヒントはまだありません。";
|
|
36
|
+
readonly hintWhy: "部屋と間隔のルールから確定する数字です。";
|
|
37
|
+
readonly view: "盤だけ";
|
|
38
|
+
readonly close: "閉じる";
|
|
39
|
+
readonly cell: "{row}行{column}列";
|
|
40
|
+
readonly clue: "手がかり {value}";
|
|
41
|
+
readonly empty: "空き";
|
|
42
|
+
readonly entry: "{value}";
|
|
43
|
+
readonly note: "メモ {values}";
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
export declare function rippleWords(language?: RippleLanguage): {
|
|
47
|
+
readonly title: "Ripple Effect";
|
|
48
|
+
readonly help: "Fill each room with 1 through its size. Equal numbers in a row or column need at least that many cells between them.";
|
|
49
|
+
readonly undo: "Undo";
|
|
50
|
+
readonly check: "Check";
|
|
51
|
+
readonly hint: "Hint";
|
|
52
|
+
readonly restart: "Restart";
|
|
53
|
+
readonly pencil: "Pencil";
|
|
54
|
+
readonly won: "Every room and ripple rule is correct.";
|
|
55
|
+
readonly wrong: "Some entries break a room or ripple rule.";
|
|
56
|
+
readonly checked: "Fill the rooms and check the spacing.";
|
|
57
|
+
readonly noHint: "No single proved answer is available yet.";
|
|
58
|
+
readonly hintWhy: "This value follows from the room and spacing rules.";
|
|
59
|
+
readonly view: "Just the board";
|
|
60
|
+
readonly close: "Close";
|
|
61
|
+
readonly cell: "Row {row}, column {column}";
|
|
62
|
+
readonly clue: "clue {value}";
|
|
63
|
+
readonly empty: "empty";
|
|
64
|
+
readonly entry: "{value}";
|
|
65
|
+
readonly note: "notes {values}";
|
|
66
|
+
} | {
|
|
67
|
+
readonly title: "波及効果";
|
|
68
|
+
readonly help: "部屋には1から部屋の大きさまでの数字を一つずつ入れます。同じ数字を同じ行・列に置くときは、その数字以上のマスを間に空けます。";
|
|
69
|
+
readonly undo: "戻す";
|
|
70
|
+
readonly check: "確認";
|
|
71
|
+
readonly hint: "ヒント";
|
|
72
|
+
readonly restart: "やり直す";
|
|
73
|
+
readonly pencil: "メモ";
|
|
74
|
+
readonly won: "部屋と間隔のルールをすべて満たしました。";
|
|
75
|
+
readonly wrong: "部屋か数字の間隔に誤りがあります。";
|
|
76
|
+
readonly checked: "部屋を埋めて間隔を確認してください。";
|
|
77
|
+
readonly noHint: "確定できるヒントはまだありません。";
|
|
78
|
+
readonly hintWhy: "部屋と間隔のルールから確定する数字です。";
|
|
79
|
+
readonly view: "盤だけ";
|
|
80
|
+
readonly close: "閉じる";
|
|
81
|
+
readonly cell: "{row}行{column}列";
|
|
82
|
+
readonly clue: "手がかり {value}";
|
|
83
|
+
readonly empty: "空き";
|
|
84
|
+
readonly entry: "{value}";
|
|
85
|
+
readonly note: "メモ {values}";
|
|
86
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export const RIPPLE_STRINGS = {
|
|
2
|
+
en: { title: "Ripple Effect", help: "Fill each room with 1 through its size. Equal numbers in a row or column need at least that many cells between them.", undo: "Undo", check: "Check", hint: "Hint", restart: "Restart", pencil: "Pencil", won: "Every room and ripple rule is correct.", wrong: "Some entries break a room or ripple rule.", checked: "Fill the rooms and check the spacing.", noHint: "No single proved answer is available yet.", hintWhy: "This value follows from the room and spacing rules.", view: "Just the board", close: "Close", cell: "Row {row}, column {column}", clue: "clue {value}", empty: "empty", entry: "{value}", note: "notes {values}" },
|
|
3
|
+
ja: { title: "波及効果", help: "部屋には1から部屋の大きさまでの数字を一つずつ入れます。同じ数字を同じ行・列に置くときは、その数字以上のマスを間に空けます。", undo: "戻す", check: "確認", hint: "ヒント", restart: "やり直す", pencil: "メモ", won: "部屋と間隔のルールをすべて満たしました。", wrong: "部屋か数字の間隔に誤りがあります。", checked: "部屋を埋めて間隔を確認してください。", noHint: "確定できるヒントはまだありません。", hintWhy: "部屋と間隔のルールから確定する数字です。", view: "盤だけ", close: "閉じる", cell: "{row}行{column}列", clue: "手がかり {value}", empty: "空き", entry: "{value}", note: "メモ {values}" },
|
|
4
|
+
};
|
|
5
|
+
export function rippleWords(language = "en") { return RIPPLE_STRINGS[language]; }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const RIPPLE_PLAY_STYLE = ".rp-root{color:var(--kz-ink,#242621);font:inherit}.rp-board{display:grid;grid-template-columns:repeat(var(--rp-width),minmax(36px,1fr));max-width:620px;margin:auto;touch-action:manipulation}.rp-cell{position:relative;aspect-ratio:1;border:1px solid var(--rp-line);background:var(--rp-surface);color:var(--rp-ink);font:500 clamp(16px,4vw,25px) system-ui;cursor:pointer;padding:0}.rp-cell[data-clue=true]{font-weight:700}.rp-cell[data-error=true]{box-shadow:inset 0 0 0 3px #bd493f}.rp-cell[data-selected=true]{background:#dce9c9;color:#1f3021}.rp-cell:focus-visible,.rp-key:focus-visible,.rp-tool:focus-visible{outline:3px solid #466f41;outline-offset:2px;z-index:1}.rp-room-right{border-right:3px solid var(--rp-ink)}.rp-room-bottom{border-bottom:3px solid var(--rp-ink)}.rp-notes{position:absolute;inset:2px;display:grid;grid-template-columns:repeat(3,1fr);font-size:10px;line-height:1;align-items:center}.rp-controls,.rp-numpad{display:flex;gap:7px;justify-content:center;flex-wrap:wrap;margin:12px auto}.rp-tool,.rp-key{font:inherit;min-height:44px;min-width:44px;border:1px solid var(--rp-line);border-radius:10px;background:var(--rp-surface);color:var(--rp-ink);cursor:pointer}.rp-tool:disabled{opacity:.5}.rp-status{min-height:1.5em;text-align:center}.rp-help{text-align:center;font-size:.9em}.rp-dialog{border:0;border-radius:16px;padding:18px;max-width:min(94vw,720px);background:#f7f3e9}.rp-close{margin-bottom:10px}.rp-tiles .rp-cell[data-clue=true]{box-shadow:inset 0 0 0 4px #c2a875}.rp-palette{display:grid;gap:8px}.rp-root[data-material=wood]{--rp-surface:#e0bf88;--rp-line:#907044;--rp-ink:#392d1f}.rp-root[data-material=slate]{--rp-surface:#364348;--rp-line:#839397;--rp-ink:#f1eee5}.rp-root[data-material=ivory]{--rp-surface:#faf7ef;--rp-line:#cfc6b2;--rp-ink:#252824}";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const RIPPLE_PLAY_STYLE = `.rp-root{color:var(--kz-ink,#242621);font:inherit}.rp-board{display:grid;grid-template-columns:repeat(var(--rp-width),minmax(36px,1fr));max-width:620px;margin:auto;touch-action:manipulation}.rp-cell{position:relative;aspect-ratio:1;border:1px solid var(--rp-line);background:var(--rp-surface);color:var(--rp-ink);font:500 clamp(16px,4vw,25px) system-ui;cursor:pointer;padding:0}.rp-cell[data-clue=true]{font-weight:700}.rp-cell[data-error=true]{box-shadow:inset 0 0 0 3px #bd493f}.rp-cell[data-selected=true]{background:#dce9c9;color:#1f3021}.rp-cell:focus-visible,.rp-key:focus-visible,.rp-tool:focus-visible{outline:3px solid #466f41;outline-offset:2px;z-index:1}.rp-room-right{border-right:3px solid var(--rp-ink)}.rp-room-bottom{border-bottom:3px solid var(--rp-ink)}.rp-notes{position:absolute;inset:2px;display:grid;grid-template-columns:repeat(3,1fr);font-size:10px;line-height:1;align-items:center}.rp-controls,.rp-numpad{display:flex;gap:7px;justify-content:center;flex-wrap:wrap;margin:12px auto}.rp-tool,.rp-key{font:inherit;min-height:44px;min-width:44px;border:1px solid var(--rp-line);border-radius:10px;background:var(--rp-surface);color:var(--rp-ink);cursor:pointer}.rp-tool:disabled{opacity:.5}.rp-status{min-height:1.5em;text-align:center}.rp-help{text-align:center;font-size:.9em}.rp-dialog{border:0;border-radius:16px;padding:18px;max-width:min(94vw,720px);background:#f7f3e9}.rp-close{margin-bottom:10px}.rp-tiles .rp-cell[data-clue=true]{box-shadow:inset 0 0 0 4px #c2a875}.rp-palette{display:grid;gap:8px}.rp-root[data-material=wood]{--rp-surface:#e0bf88;--rp-line:#907044;--rp-ink:#392d1f}.rp-root[data-material=slate]{--rp-surface:#364348;--rp-line:#839397;--rp-ink:#f1eee5}.rp-root[data-material=ivory]{--rp-surface:#faf7ef;--rp-line:#cfc6b2;--rp-ink:#252824}`;
|
package/dist/shikaku-entry.d.ts
CHANGED
package/dist/shikaku-entry.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/** Bounds keep custom boards and searches finite. */
|
|
2
2
|
export declare const SHIKAKU_MOST_SIDE = 16;
|
|
3
3
|
export declare const SHIKAKU_MOST_NODES = 100000;
|
|
4
|
-
export declare const SHIKAKU_MOST_ATTEMPTS =
|
|
5
|
-
|
|
4
|
+
export declare const SHIKAKU_MOST_ATTEMPTS = 1500;
|
|
5
|
+
/** How hard a puzzle is made, by what a person must do to solve it; see `rateShikaku`. */
|
|
6
|
+
export declare const SHIKAKU_LEVELS: readonly ["easy", "medium", "hard", "extra-hard"];
|
|
7
|
+
/** The sides on offer for a square board; any side from 2 to `SHIKAKU_MOST_SIDE` can be made. */
|
|
8
|
+
export declare const SHIKAKU_SIZES: readonly [5, 7, 10, 14];
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/** Bounds keep custom boards and searches finite. */
|
|
2
2
|
export const SHIKAKU_MOST_SIDE = 16;
|
|
3
3
|
export const SHIKAKU_MOST_NODES = 100000;
|
|
4
|
-
export const SHIKAKU_MOST_ATTEMPTS =
|
|
5
|
-
|
|
4
|
+
export const SHIKAKU_MOST_ATTEMPTS = 1500;
|
|
5
|
+
/** How hard a puzzle is made, by what a person must do to solve it; see `rateShikaku`. */
|
|
6
|
+
export const SHIKAKU_LEVELS = ["easy", "medium", "hard", "extra-hard"];
|
|
7
|
+
/** The sides on offer for a square board; any side from 2 to `SHIKAKU_MOST_SIDE` can be made. */
|
|
8
|
+
export const SHIKAKU_SIZES = [5, 7, 10, 14];
|
package/dist/shikaku.types.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export type ShikakuBoard = {
|
|
|
11
11
|
height: number;
|
|
12
12
|
clues: readonly number[];
|
|
13
13
|
};
|
|
14
|
-
export type ShikakuLevel = "easy" | "medium" | "hard";
|
|
14
|
+
export type ShikakuLevel = "easy" | "medium" | "hard" | "extra-hard";
|
|
15
15
|
export type ShikakuPuzzle = ShikakuBoard & {
|
|
16
16
|
seed: number;
|
|
17
17
|
level: ShikakuLevel;
|
|
@@ -34,3 +34,20 @@ export type ShikakuGame = {
|
|
|
34
34
|
history: readonly (readonly ShikakuRectangle[])[];
|
|
35
35
|
helped: boolean;
|
|
36
36
|
};
|
|
37
|
+
/**
|
|
38
|
+
* How hard a board is, measured by solving it. `depth` 0 means the rules solve it, 1 that somebody has to suppose a
|
|
39
|
+
* rectangle and watch it break, 2 that more than that is needed. `rules` is how many of the three rules a depth-0
|
|
40
|
+
* solve needed: 1 for a number with one fitting rectangle (a settled rectangle clears its squares), 2 adding that a
|
|
41
|
+
* square only one rectangle can cover is covered by it, 3 adding that a square only one number can reach makes that
|
|
42
|
+
* number's rectangle cover it. `probes` is the suppositions depth 1 needed.
|
|
43
|
+
*/
|
|
44
|
+
export type ShikakuRating = {
|
|
45
|
+
depth: 0 | 1 | 2;
|
|
46
|
+
rules: 1 | 2 | 3;
|
|
47
|
+
probes: number;
|
|
48
|
+
rectangles: number;
|
|
49
|
+
meanArea: number;
|
|
50
|
+
largest: number;
|
|
51
|
+
/** Mean number of rectangles a number could be at the start. */
|
|
52
|
+
ambiguity: number;
|
|
53
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ShikakuBoard, ShikakuLevel, ShikakuRectangle } from "./shikaku.types.ts";
|
|
2
|
+
import type { Random } from "./random.ts";
|
|
3
|
+
/** The smallest and largest rectangle a level cuts the board into. */
|
|
4
|
+
export declare const SHIKAKU_AREAS: Record<ShikakuLevel, {
|
|
5
|
+
least: number;
|
|
6
|
+
most: number;
|
|
7
|
+
}>;
|
|
8
|
+
/**
|
|
9
|
+
* Cuts a board into rectangles by packing: the square with the fewest free neighbours is covered next, by a random
|
|
10
|
+
* rectangle of a random allowed area that fits the free squares round it. Unlike cutting the board in two over and
|
|
11
|
+
* over, this makes pinwheels and rectangles that interlock.
|
|
12
|
+
*/
|
|
13
|
+
export declare function packRectangles(width: number, height: number, level: ShikakuLevel, random: Random): ShikakuRectangle[];
|
|
14
|
+
/**
|
|
15
|
+
* A board with exactly one answer, or null. Each rectangle's number sits in a random square of it; while there is
|
|
16
|
+
* another answer, the numbers of the rectangles the two answers disagree on move to new squares.
|
|
17
|
+
*/
|
|
18
|
+
export declare function candidateShikaku(width: number, height: number, level: ShikakuLevel, random: Random): {
|
|
19
|
+
board: ShikakuBoard;
|
|
20
|
+
solution: readonly ShikakuRectangle[];
|
|
21
|
+
} | null;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { shikakuModel } from "./shikakuLogic.js";
|
|
2
|
+
import { countCsp, openSlots } from "./csp.js";
|
|
3
|
+
import { shuffled } from "./random.js";
|
|
4
|
+
/** The smallest and largest rectangle a level cuts the board into. */
|
|
5
|
+
export const SHIKAKU_AREAS = {
|
|
6
|
+
easy: { least: 2, most: 6 },
|
|
7
|
+
medium: { least: 3, most: 12 },
|
|
8
|
+
hard: { least: 3, most: 9 },
|
|
9
|
+
"extra-hard": { least: 4, most: 9 },
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Cuts a board into rectangles by packing: the square with the fewest free neighbours is covered next, by a random
|
|
13
|
+
* rectangle of a random allowed area that fits the free squares round it. Unlike cutting the board in two over and
|
|
14
|
+
* over, this makes pinwheels and rectangles that interlock.
|
|
15
|
+
*/
|
|
16
|
+
export function packRectangles(width, height, level, random) {
|
|
17
|
+
const free = new Uint8Array(width * height).fill(1);
|
|
18
|
+
const { least, most: wanted } = SHIKAKU_AREAS[level];
|
|
19
|
+
const most = Math.max(least, Math.min(wanted, Math.round(width * height * .3)));
|
|
20
|
+
const freeAt = (x, y) => x >= 0 && y >= 0 && x < width && y < height && free[y * width + x] === 1;
|
|
21
|
+
const rectangles = [];
|
|
22
|
+
for (;;) {
|
|
23
|
+
let anchor = -1, fewest = 9, ties = 0;
|
|
24
|
+
for (let cell = 0; cell < free.length; cell += 1) {
|
|
25
|
+
if (!free[cell])
|
|
26
|
+
continue;
|
|
27
|
+
const x = cell % width, y = Math.floor(cell / width);
|
|
28
|
+
const around = +freeAt(x - 1, y) + +freeAt(x + 1, y) + +freeAt(x, y - 1) + +freeAt(x, y + 1);
|
|
29
|
+
if (around < fewest) {
|
|
30
|
+
fewest = around;
|
|
31
|
+
anchor = cell;
|
|
32
|
+
ties = 1;
|
|
33
|
+
}
|
|
34
|
+
else if (around === fewest && random() * ++ties < 1)
|
|
35
|
+
anchor = cell;
|
|
36
|
+
}
|
|
37
|
+
if (anchor < 0)
|
|
38
|
+
return rectangles;
|
|
39
|
+
const ax = anchor % width, ay = Math.floor(anchor / width);
|
|
40
|
+
const fits = [];
|
|
41
|
+
for (let w = 1; w <= Math.min(width, most); w += 1)
|
|
42
|
+
for (let h = 1; w * h <= most; h += 1) {
|
|
43
|
+
for (let ox = 0; ox < w; ox += 1)
|
|
44
|
+
for (let oy = 0; oy < h; oy += 1) {
|
|
45
|
+
const x = ax - ox, y = ay - oy;
|
|
46
|
+
let all = x >= 0 && y >= 0 && x + w <= width && y + h <= height;
|
|
47
|
+
for (let k = 0; all && k < w * h; k += 1)
|
|
48
|
+
if (!free[(y + Math.floor(k / w)) * width + x + k % w])
|
|
49
|
+
all = false;
|
|
50
|
+
if (all)
|
|
51
|
+
fits.push({ x, y, width: w, height: h });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Prefer rectangles that leave no square with nowhere to go, then rectangles of a decent size.
|
|
55
|
+
const leavesIsland = (r) => {
|
|
56
|
+
const inside = (x, y) => x >= r.x && x < r.x + r.width && y >= r.y && y < r.y + r.height;
|
|
57
|
+
for (let x = r.x - 1; x <= r.x + r.width; x += 1)
|
|
58
|
+
for (let y = r.y - 1; y <= r.y + r.height; y += 1) {
|
|
59
|
+
if (inside(x, y) || !freeAt(x, y))
|
|
60
|
+
continue;
|
|
61
|
+
const exits = [[x - 1, y], [x + 1, y], [x, y - 1], [x, y + 1]].filter(([nx, ny]) => freeAt(nx, ny) && !inside(nx, ny)).length;
|
|
62
|
+
if (!exits)
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
};
|
|
67
|
+
const sound = fits.filter(r => !leavesIsland(r));
|
|
68
|
+
const roomy = (sound.length ? sound : fits).filter(r => r.width * r.height >= least);
|
|
69
|
+
const pool = roomy.length ? roomy : sound.length ? sound : fits;
|
|
70
|
+
const areas = [...new Set(pool.map(r => r.width * r.height))];
|
|
71
|
+
const area = areas[Math.floor(random() * areas.length)];
|
|
72
|
+
const same = pool.filter(r => r.width * r.height === area);
|
|
73
|
+
const pick = same[Math.floor(random() * same.length)];
|
|
74
|
+
rectangles.push(pick);
|
|
75
|
+
for (let k = 0; k < pick.width * pick.height; k += 1)
|
|
76
|
+
free[(pick.y + Math.floor(k / pick.width)) * width + pick.x + k % pick.width] = 0;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* A board with exactly one answer, or null. Each rectangle's number sits in a random square of it; while there is
|
|
81
|
+
* another answer, the numbers of the rectangles the two answers disagree on move to new squares.
|
|
82
|
+
*/
|
|
83
|
+
export function candidateShikaku(width, height, level, random) {
|
|
84
|
+
const solution = packRectangles(width, height, level, random);
|
|
85
|
+
if (solution.length < 2 || solution.some(r => r.width * r.height === 1))
|
|
86
|
+
return null;
|
|
87
|
+
const cellsOf = (r) => Array.from({ length: r.width * r.height }, (_, i) => (r.y + Math.floor(i / r.width)) * width + r.x + i % r.width);
|
|
88
|
+
// Where a number sits decides how many rectangles it could be: easy boards put it where it has the fewest, the others anywhere.
|
|
89
|
+
const freedom = (r, cell) => {
|
|
90
|
+
const area = r.width * r.height;
|
|
91
|
+
let count = 0;
|
|
92
|
+
for (let w = 1; w <= area && w <= width; w += 1) {
|
|
93
|
+
if (area % w || area / w > height)
|
|
94
|
+
continue;
|
|
95
|
+
const h = area / w;
|
|
96
|
+
count += (Math.min(cell % width, width - w) - Math.max(0, cell % width - w + 1) + 1) * (Math.min(Math.floor(cell / width), height - h) - Math.max(0, Math.floor(cell / width) - h + 1) + 1);
|
|
97
|
+
}
|
|
98
|
+
return count;
|
|
99
|
+
};
|
|
100
|
+
const chooseSpot = (r, not) => {
|
|
101
|
+
const cells = shuffled(cellsOf(r).filter(c => c !== not), random);
|
|
102
|
+
if (!cells.length)
|
|
103
|
+
return not;
|
|
104
|
+
if (level !== "easy")
|
|
105
|
+
return cells[0];
|
|
106
|
+
const ranked = [...cells].sort((a, b) => freedom(r, a) - freedom(r, b));
|
|
107
|
+
return ranked[Math.floor(random() * Math.min(2, ranked.length))];
|
|
108
|
+
};
|
|
109
|
+
const spot = solution.map(r => chooseSpot(r));
|
|
110
|
+
for (let repair = 0; repair < 40; repair += 1) {
|
|
111
|
+
const clues = Array(width * height).fill(0);
|
|
112
|
+
solution.forEach((r, i) => { clues[spot[i]] = r.width * r.height; });
|
|
113
|
+
const board = { width, height, clues };
|
|
114
|
+
const model = shikakuModel(board);
|
|
115
|
+
const found = countCsp(model.csp, openSlots(model.csp), 2, 20000);
|
|
116
|
+
if (found.exhausted)
|
|
117
|
+
return null;
|
|
118
|
+
if (found.count === 1)
|
|
119
|
+
return { board, solution };
|
|
120
|
+
const alt = found.solutions[1];
|
|
121
|
+
let moved = false;
|
|
122
|
+
solution.forEach((r, i) => {
|
|
123
|
+
const slot = model.rectangles.findIndex((c, s) => model.owner[s] === model.clues.indexOf(spot[i]) && c.x === r.x && c.y === r.y && c.width === r.width && c.height === r.height);
|
|
124
|
+
if (slot >= 0 && alt[slot])
|
|
125
|
+
return;
|
|
126
|
+
if (cellsOf(r).length < 2)
|
|
127
|
+
return;
|
|
128
|
+
spot[i] = shuffled(cellsOf(r).filter(c => c !== spot[i]), random)[0];
|
|
129
|
+
moved = true;
|
|
130
|
+
});
|
|
131
|
+
if (!moved)
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
import type { ShikakuLevel, ShikakuPuzzle } from "./shikaku.types.ts";
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Seeded rectangles packed into the board, one number each, with an independently counted, unique answer. The board
|
|
4
|
+
* is rated by solving it: easy by the first two rules (a number with one fitting rectangle, a square one rectangle can
|
|
5
|
+
* cover), medium once the third rule is needed too, hard by supposing, extra-hard the most-supposing of several boards. If no board of the level is found within the attempts
|
|
6
|
+
* the next level down is tried, and the first generator is the last resort.
|
|
7
|
+
*/
|
|
3
8
|
export declare function generateShikaku(width?: number, height?: number, level?: ShikakuLevel, seed?: number): ShikakuPuzzle;
|
package/dist/shikakuGenerate.js
CHANGED
|
@@ -1,43 +1,58 @@
|
|
|
1
|
-
import { SHIKAKU_LEVELS, SHIKAKU_MOST_ATTEMPTS } from "./shikaku.constants.js";
|
|
2
|
-
import {
|
|
1
|
+
import { SHIKAKU_LEVELS, SHIKAKU_MOST_ATTEMPTS, SHIKAKU_MOST_SIDE } from "./shikaku.constants.js";
|
|
2
|
+
import { checkShikaku } from "./shikakuBoard.js";
|
|
3
|
+
import { candidateShikaku } from "./shikakuBuild.js";
|
|
4
|
+
import { shikakuModel } from "./shikakuLogic.js";
|
|
3
5
|
import { solveShikaku } from "./shikakuSolve.js";
|
|
6
|
+
import { templateShikaku } from "./shikakuTemplate.js";
|
|
7
|
+
import { logicCsp, openSlots } from "./csp.js";
|
|
4
8
|
import { isKazuSeed, seededRandom } from "./random.js";
|
|
5
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Seeded rectangles packed into the board, one number each, with an independently counted, unique answer. The board
|
|
11
|
+
* is rated by solving it: easy by the first two rules (a number with one fitting rectangle, a square one rectangle can
|
|
12
|
+
* cover), medium once the third rule is needed too, hard by supposing, extra-hard the most-supposing of several boards. If no board of the level is found within the attempts
|
|
13
|
+
* the next level down is tried, and the first generator is the last resort.
|
|
14
|
+
*/
|
|
6
15
|
export function generateShikaku(width = 7, height = width, level = "medium", seed = 1) {
|
|
7
|
-
if (![width, height].every(n => Number.isInteger(n) && n >= 2 && n <=
|
|
8
|
-
|
|
9
|
-
const empty = { width, height, clues: Array(width * height).fill(0) };
|
|
10
|
-
if (!isKazuSeed(seed) || !SHIKAKU_LEVELS.includes(level)
|
|
11
|
-
|| !isShikakuBoard({ ...empty, clues: [width * height, ...empty.clues.slice(1)] }))
|
|
16
|
+
if (![width, height].every(n => Number.isInteger(n) && n >= 2 && n <= SHIKAKU_MOST_SIDE)
|
|
17
|
+
|| !isKazuSeed(seed) || !SHIKAKU_LEVELS.includes(level))
|
|
12
18
|
throw new RangeError("Invalid Shikaku settings");
|
|
13
|
-
const random = seededRandom(seed)
|
|
14
|
-
for (let
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
if (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
19
|
+
const random = seededRandom(seed);
|
|
20
|
+
for (let aim = SHIKAKU_LEVELS.indexOf(level); aim >= 0; aim -= 1) {
|
|
21
|
+
const aimed = SHIKAKU_LEVELS[aim];
|
|
22
|
+
const wanted = aimed === "extra-hard" ? 3 : 1;
|
|
23
|
+
let best = null, found = 0;
|
|
24
|
+
for (let attempt = 0; attempt < SHIKAKU_MOST_ATTEMPTS && found < wanted; attempt += 1) {
|
|
25
|
+
const built = candidateShikaku(width, height, aimed, random);
|
|
26
|
+
if (!built)
|
|
27
|
+
continue;
|
|
28
|
+
const score = scored(built.board, aimed);
|
|
29
|
+
if (score === null)
|
|
30
|
+
continue;
|
|
31
|
+
found += 1;
|
|
32
|
+
if (!best || score > best.score)
|
|
33
|
+
best = { board: built.board, score };
|
|
34
|
+
}
|
|
35
|
+
if (best) {
|
|
36
|
+
const proof = solveShikaku(best.board);
|
|
37
|
+
if (proof.complete && proof.count === 1 && proof.solution && checkShikaku(best.board, proof.solution).ok) {
|
|
38
|
+
return { ...best.board, seed, level, solution: proof.solution };
|
|
30
39
|
}
|
|
31
|
-
};
|
|
32
|
-
split({ x: 0, y: 0, width, height });
|
|
33
|
-
const clues = [...empty.clues];
|
|
34
|
-
for (const r of solution) {
|
|
35
|
-
const cells = shikakuCells(empty, r);
|
|
36
|
-
clues[cells[Math.floor(random() * cells.length)]] = cells.length;
|
|
37
40
|
}
|
|
38
|
-
const board = { width, height, clues }, counted = solveShikaku(board);
|
|
39
|
-
if (counted.complete && counted.count === 1)
|
|
40
|
-
return { ...board, seed, level, solution: counted.solution };
|
|
41
41
|
}
|
|
42
|
-
|
|
42
|
+
return { ...templateShikaku(width, height, level === "extra-hard" ? "hard" : level, seed), level };
|
|
43
|
+
}
|
|
44
|
+
/** Whether a board suits a level, and how well: higher is harder. */
|
|
45
|
+
function scored(board, level) {
|
|
46
|
+
const models = [shikakuModel(board, 1), shikakuModel(board, 2)];
|
|
47
|
+
const solves = (index, depth) => logicCsp(models[index].csp, openSlots(models[index].csp), depth);
|
|
48
|
+
if (solves(0, 0).solved)
|
|
49
|
+
return level === "easy" ? 0 : null;
|
|
50
|
+
if (level === "easy")
|
|
51
|
+
return null;
|
|
52
|
+
if (solves(1, 0).solved)
|
|
53
|
+
return level === "medium" ? 0 : null;
|
|
54
|
+
if (level === "medium")
|
|
55
|
+
return null;
|
|
56
|
+
const probing = solves(1, 1);
|
|
57
|
+
return probing.solved ? probing.probes : level === "extra-hard" ? 1000 : null;
|
|
43
58
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Csp } from "./csp.ts";
|
|
2
|
+
import type { ShikakuBoard, ShikakuRectangle } from "./shikaku.types.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Shikaku as variables: one per numbered square, its slots the rectangles it could be the one number of. A rectangle
|
|
5
|
+
* that is settled keeps every other rectangle off its squares. With `strength` 1 a square only one rectangle can
|
|
6
|
+
* still cover is covered by it, and with 2 a square only one number can still reach forces that number's rectangle to cover it.
|
|
7
|
+
*/
|
|
8
|
+
export declare function shikakuModel(board: ShikakuBoard, strength?: 0 | 1 | 2): {
|
|
9
|
+
csp: Csp;
|
|
10
|
+
clues: readonly number[];
|
|
11
|
+
rectangles: readonly ShikakuRectangle[];
|
|
12
|
+
owner: Int32Array;
|
|
13
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { shikakuCandidates, shikakuCells } from "./shikakuBoard.js";
|
|
2
|
+
/**
|
|
3
|
+
* Shikaku as variables: one per numbered square, its slots the rectangles it could be the one number of. A rectangle
|
|
4
|
+
* that is settled keeps every other rectangle off its squares. With `strength` 1 a square only one rectangle can
|
|
5
|
+
* still cover is covered by it, and with 2 a square only one number can still reach forces that number's rectangle to cover it.
|
|
6
|
+
*/
|
|
7
|
+
export function shikakuModel(board, strength = 2) {
|
|
8
|
+
const clues = board.clues.flatMap((n, cell) => n ? [cell] : []);
|
|
9
|
+
const rectangles = [], owner = [], covers = [], starts = [0];
|
|
10
|
+
clues.forEach((cell, variable) => {
|
|
11
|
+
for (const rectangle of shikakuCandidates(board, cell)) {
|
|
12
|
+
rectangles.push(rectangle);
|
|
13
|
+
owner.push(variable);
|
|
14
|
+
covers.push(Int32Array.from(shikakuCells(board, rectangle)));
|
|
15
|
+
}
|
|
16
|
+
starts.push(rectangles.length);
|
|
17
|
+
});
|
|
18
|
+
const through = Array.from({ length: board.clues.length }, () => []);
|
|
19
|
+
covers.forEach((cells, slot) => cells.forEach(cell => through[cell].push(slot)));
|
|
20
|
+
const propagate = (alive) => {
|
|
21
|
+
let changed = true;
|
|
22
|
+
while (changed) {
|
|
23
|
+
changed = false;
|
|
24
|
+
for (let v = 0; v < clues.length; v += 1) {
|
|
25
|
+
let left = 0, only = -1;
|
|
26
|
+
for (let s = starts[v]; s < starts[v + 1]; s += 1)
|
|
27
|
+
if (alive[s]) {
|
|
28
|
+
left += 1;
|
|
29
|
+
only = s;
|
|
30
|
+
}
|
|
31
|
+
if (!left)
|
|
32
|
+
return false;
|
|
33
|
+
if (left !== 1)
|
|
34
|
+
continue;
|
|
35
|
+
const cells = covers[only];
|
|
36
|
+
for (let k = 0; k < cells.length; k += 1) {
|
|
37
|
+
const others = through[cells[k]];
|
|
38
|
+
for (let j = 0; j < others.length; j += 1) {
|
|
39
|
+
const t = others[j];
|
|
40
|
+
if (t !== only && alive[t] && owner[t] !== v) {
|
|
41
|
+
alive[t] = 0;
|
|
42
|
+
changed = true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (strength < 1)
|
|
48
|
+
continue;
|
|
49
|
+
for (let cell = 0; cell < through.length; cell += 1) {
|
|
50
|
+
const slots = through[cell];
|
|
51
|
+
let count = 0, only = -1, single = true, first = -1;
|
|
52
|
+
for (let k = 0; k < slots.length; k += 1) {
|
|
53
|
+
const s = slots[k];
|
|
54
|
+
if (!alive[s])
|
|
55
|
+
continue;
|
|
56
|
+
count += 1;
|
|
57
|
+
only = s;
|
|
58
|
+
if (first < 0)
|
|
59
|
+
first = owner[s];
|
|
60
|
+
else if (owner[s] !== first)
|
|
61
|
+
single = false;
|
|
62
|
+
}
|
|
63
|
+
if (!count)
|
|
64
|
+
return false;
|
|
65
|
+
if (count === 1) {
|
|
66
|
+
for (let s = starts[owner[only]]; s < starts[owner[only] + 1]; s += 1)
|
|
67
|
+
if (s !== only && alive[s]) {
|
|
68
|
+
alive[s] = 0;
|
|
69
|
+
changed = true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else if (single && strength > 1) {
|
|
73
|
+
for (let s = starts[first]; s < starts[first + 1]; s += 1)
|
|
74
|
+
if (alive[s] && !covers[s].includes(cell)) {
|
|
75
|
+
alive[s] = 0;
|
|
76
|
+
changed = true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
82
|
+
};
|
|
83
|
+
return { csp: { starts: Int32Array.from(starts), propagate }, clues, rectangles, owner: Int32Array.from(owner) };
|
|
84
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ShikakuLevel, ShikakuPuzzle } from "./shikaku.types.ts";
|
|
2
|
+
export type ShikakuPackKey = "square" | "wide" | "tall";
|
|
3
|
+
export type ShikakuChallenge = {
|
|
4
|
+
pack: ShikakuPackKey;
|
|
5
|
+
packTitle: string;
|
|
6
|
+
name: string;
|
|
7
|
+
level: ShikakuLevel;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
seed: number;
|
|
11
|
+
};
|
|
12
|
+
export type ShikakuChallengePuzzle = ShikakuChallenge & {
|
|
13
|
+
puzzle: ShikakuPuzzle;
|
|
14
|
+
};
|
|
15
|
+
/** Three short, named challenge routes using the regular seeded unique-puzzle generator. */
|
|
16
|
+
export declare const SHIKAKU_CHALLENGE_PACKS: Readonly<Record<ShikakuPackKey, {
|
|
17
|
+
title: string;
|
|
18
|
+
description: string;
|
|
19
|
+
width: number;
|
|
20
|
+
height: number;
|
|
21
|
+
challenges: readonly {
|
|
22
|
+
name: string;
|
|
23
|
+
level: ShikakuLevel;
|
|
24
|
+
seed: number;
|
|
25
|
+
}[];
|
|
26
|
+
}>>;
|
|
27
|
+
export declare function generateShikakuChallenge(pack: ShikakuPackKey, challenge?: number): ShikakuChallengePuzzle;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { generateShikaku } from "./shikakuGenerate.js";
|
|
2
|
+
/** Three short, named challenge routes using the regular seeded unique-puzzle generator. */
|
|
3
|
+
export const SHIKAKU_CHALLENGE_PACKS = {
|
|
4
|
+
square: {
|
|
5
|
+
title: "The Courtyard", description: "A compact square route.", width: 7, height: 7,
|
|
6
|
+
challenges: [{ name: "First Cut", level: "easy", seed: 1 }, { name: "Crossing", level: "medium", seed: 2 }, { name: "Mosaic", level: "hard", seed: 3 }],
|
|
7
|
+
},
|
|
8
|
+
wide: {
|
|
9
|
+
title: "The Long Table", description: "Three puzzles across a wide board.", width: 10, height: 6,
|
|
10
|
+
challenges: [{ name: "Harbour", level: "easy", seed: 1 }, { name: "Causeway", level: "medium", seed: 2 }, { name: "Headland", level: "hard", seed: 3 }],
|
|
11
|
+
},
|
|
12
|
+
tall: {
|
|
13
|
+
title: "The Narrow Garden", description: "A tall route with long edges.", width: 6, height: 10,
|
|
14
|
+
challenges: [{ name: "Gate", level: "easy", seed: 1 }, { name: "Pavilion", level: "medium", seed: 2 }, { name: "Lantern Walk", level: "hard", seed: 3 }],
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
export function generateShikakuChallenge(pack, challenge = 1) {
|
|
18
|
+
const definition = SHIKAKU_CHALLENGE_PACKS[pack], entry = definition?.challenges[challenge - 1];
|
|
19
|
+
if (!definition || !entry)
|
|
20
|
+
throw new RangeError("Unknown Shikaku challenge");
|
|
21
|
+
return {
|
|
22
|
+
pack, packTitle: definition.title, name: entry.name, level: entry.level,
|
|
23
|
+
width: definition.width, height: definition.height, seed: entry.seed,
|
|
24
|
+
puzzle: generateShikaku(definition.width, definition.height, entry.level, entry.seed),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { isShikakuBoard } from "./shikakuBoard.js";
|
|
2
|
+
import { shikakuModel } from "./shikakuLogic.js";
|
|
3
|
+
import { logicCsp, openSlots, solveCsp } from "./csp.js";
|
|
4
|
+
/** Rates a board with one answer by solving it with the first rule, with the first two, with all three, then by supposing. */
|
|
5
|
+
export function rateShikaku(board) {
|
|
6
|
+
if (!isShikakuBoard(board))
|
|
7
|
+
throw new RangeError("Invalid Shikaku board");
|
|
8
|
+
const first = shikakuModel(board, 0), second = shikakuModel(board, 1), strong = shikakuModel(board, 2);
|
|
9
|
+
const proof = solveCsp(strong.csp, openSlots(strong.csp), 2, 100000);
|
|
10
|
+
if (proof.count !== 1 || proof.exhausted)
|
|
11
|
+
throw new RangeError("Shikaku rating needs a board with one answer");
|
|
12
|
+
const one = logicCsp(first.csp, openSlots(first.csp), 0);
|
|
13
|
+
const two = one.solved ? one : logicCsp(second.csp, openSlots(second.csp), 0);
|
|
14
|
+
const rules = two.solved ? two : logicCsp(strong.csp, openSlots(strong.csp), 0);
|
|
15
|
+
const probing = rules.solved ? rules : logicCsp(strong.csp, openSlots(strong.csp), 1);
|
|
16
|
+
const areas = strong.clues.map(cell => board.clues[cell]);
|
|
17
|
+
const options = strong.clues.map((_, v) => strong.csp.starts[v + 1] - strong.csp.starts[v]);
|
|
18
|
+
return {
|
|
19
|
+
depth: rules.solved ? 0 : probing.solved ? 1 : 2,
|
|
20
|
+
rules: one.solved ? 1 : two.solved ? 2 : 3,
|
|
21
|
+
probes: rules.solved ? 0 : probing.probes,
|
|
22
|
+
rectangles: areas.length,
|
|
23
|
+
meanArea: board.clues.length / areas.length,
|
|
24
|
+
largest: Math.max(...areas),
|
|
25
|
+
ambiguity: options.reduce((a, b) => a + b, 0) / options.length,
|
|
26
|
+
};
|
|
27
|
+
}
|
package/dist/shikakuSolve.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ShikakuBoard, ShikakuRectangle, ShikakuSolve } from "./shikaku.types.ts";
|
|
2
|
-
/** Exact cover over
|
|
2
|
+
/** Exact cover over squares. A bounded search reports incompleteness instead of claiming uniqueness. */
|
|
3
3
|
export declare function solveShikaku(board: ShikakuBoard, placed?: readonly ShikakuRectangle[], options?: {
|
|
4
4
|
limit?: number;
|
|
5
5
|
nodes?: number;
|