@fgv/ts-sudoku-lib 5.0.1-0 → 5.0.1-2
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/debug_killer.js +50 -0
- package/debug_test.js +88 -0
- package/dist/ts-sudoku-lib.d.ts +1505 -42
- package/lib/index.d.ts +3 -2
- package/lib/index.js +5 -3
- package/lib/packlets/collections/collections.d.ts +15 -8
- package/lib/packlets/collections/collections.js +41 -11
- package/lib/packlets/collections/data/puzzles.json +28 -33
- package/lib/packlets/common/common.d.ts +15 -0
- package/lib/packlets/common/common.js +25 -0
- package/lib/packlets/common/converters.d.ts +0 -6
- package/lib/packlets/common/converters.js +17 -22
- package/lib/packlets/common/ids.d.ts +8 -1
- package/lib/packlets/common/ids.js +83 -10
- package/lib/packlets/common/index.d.ts +3 -2
- package/lib/packlets/common/index.js +5 -2
- package/lib/packlets/common/logging.d.ts +7 -0
- package/lib/packlets/common/logging.js +35 -0
- package/lib/packlets/common/puzzle.d.ts +23 -13
- package/lib/packlets/common/puzzle.js +51 -25
- package/lib/packlets/common/puzzleDefinitions.d.ts +127 -0
- package/lib/packlets/common/puzzleDefinitions.js +251 -0
- package/lib/packlets/common/puzzleSession.d.ts +8 -0
- package/lib/packlets/common/puzzleSession.js +12 -0
- package/lib/packlets/common/puzzleState.js +3 -0
- package/lib/packlets/files/converters.d.ts +27 -0
- package/lib/packlets/files/converters.js +71 -0
- package/lib/packlets/{file/converters.d.ts → files/filesystem.d.ts} +3 -8
- package/lib/packlets/{file/converters.js → files/filesystem.js} +5 -14
- package/lib/packlets/files/index.d.ts +5 -0
- package/lib/packlets/{file → files}/index.js +3 -1
- package/lib/packlets/files/model.d.ts +32 -0
- package/lib/packlets/hints/baseHintProvider.d.ts +103 -0
- package/lib/packlets/hints/baseHintProvider.js +203 -0
- package/lib/packlets/hints/explanations.d.ts +105 -0
- package/lib/packlets/hints/explanations.js +276 -0
- package/lib/packlets/hints/hiddenSingles.d.ts +81 -0
- package/lib/packlets/hints/hiddenSingles.js +237 -0
- package/lib/packlets/hints/hintRegistry.d.ts +95 -0
- package/lib/packlets/hints/hintRegistry.js +226 -0
- package/lib/packlets/hints/hints.d.ts +144 -0
- package/lib/packlets/hints/hints.js +316 -0
- package/lib/packlets/hints/index.d.ts +10 -0
- package/lib/packlets/hints/index.js +55 -0
- package/lib/packlets/hints/interfaces.d.ts +121 -0
- package/lib/packlets/{file/model.js → hints/interfaces.js} +1 -1
- package/lib/packlets/hints/nakedSingles.d.ts +57 -0
- package/lib/packlets/hints/nakedSingles.js +202 -0
- package/lib/packlets/hints/puzzleSessionHints.d.ts +306 -0
- package/lib/packlets/hints/puzzleSessionHints.js +499 -0
- package/lib/packlets/hints/types.d.ts +102 -0
- package/lib/packlets/hints/types.js +46 -0
- package/lib/packlets/puzzles/anyPuzzle.d.ts +3 -3
- package/lib/packlets/puzzles/anyPuzzle.js +1 -1
- package/lib/packlets/puzzles/index.d.ts +4 -2
- package/lib/packlets/puzzles/index.js +20 -3
- package/lib/packlets/puzzles/internal/combinationCache.d.ts +29 -0
- package/lib/packlets/puzzles/internal/combinationCache.js +79 -0
- package/lib/packlets/puzzles/internal/combinationGenerator.d.ts +34 -0
- package/lib/packlets/puzzles/internal/combinationGenerator.js +146 -0
- package/lib/packlets/puzzles/internal/possibilityAnalyzer.d.ts +25 -0
- package/lib/packlets/puzzles/internal/possibilityAnalyzer.js +125 -0
- package/lib/packlets/puzzles/killerCombinations.d.ts +90 -0
- package/lib/packlets/puzzles/killerCombinations.js +226 -0
- package/lib/packlets/puzzles/killerCombinationsTypes.d.ts +17 -0
- package/lib/packlets/puzzles/killerCombinationsTypes.js +26 -0
- package/lib/packlets/puzzles/killerSudokuPuzzle.d.ts +2 -2
- package/lib/packlets/puzzles/killerSudokuPuzzle.js +15 -11
- package/lib/packlets/puzzles/sudokuPuzzle.d.ts +2 -2
- package/lib/packlets/puzzles/sudokuXPuzzle.d.ts +2 -2
- package/lib/packlets/puzzles/sudokuXPuzzle.js +8 -4
- package/package.json +5 -5
- package/sample-12x12-puzzle.js +72 -0
- package/sample-4x4-puzzles.js +68 -0
- package/sample-6x6-puzzle.js +61 -0
- package/temp_12x12_cells.txt +12 -0
- package/temp_correct.txt +12 -0
- package/test-grid-sizes.js +132 -0
- package/lib/packlets/common/model.d.ts +0 -15
- package/lib/packlets/file/index.d.ts +0 -4
- package/lib/packlets/file/model.d.ts +0 -9
- /package/lib/packlets/{common → files}/model.js +0 -0
package/lib/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as Files from './packlets/files';
|
|
2
|
+
import * as Hints from './packlets/hints';
|
|
2
3
|
import * as Puzzles from './packlets/puzzles';
|
|
3
4
|
export * from './packlets/collections';
|
|
4
5
|
export * from './packlets/common';
|
|
5
|
-
export {
|
|
6
|
+
export { Files, Hints, Puzzles };
|
|
6
7
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/index.js
CHANGED
|
@@ -59,9 +59,11 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
59
59
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
60
60
|
};
|
|
61
61
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
62
|
-
exports.Puzzles = exports.
|
|
63
|
-
const
|
|
64
|
-
exports.
|
|
62
|
+
exports.Puzzles = exports.Hints = exports.Files = void 0;
|
|
63
|
+
const Files = __importStar(require("./packlets/files"));
|
|
64
|
+
exports.Files = Files;
|
|
65
|
+
const Hints = __importStar(require("./packlets/hints"));
|
|
66
|
+
exports.Hints = Hints;
|
|
65
67
|
const Puzzles = __importStar(require("./packlets/puzzles"));
|
|
66
68
|
exports.Puzzles = Puzzles;
|
|
67
69
|
__exportStar(require("./packlets/collections"), exports);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as Files from '../files';
|
|
2
2
|
import { Result } from '@fgv/ts-utils';
|
|
3
|
-
import {
|
|
3
|
+
import { PuzzleSession } from '../common';
|
|
4
|
+
import { FileTree } from '@fgv/ts-json-base';
|
|
4
5
|
/**
|
|
5
6
|
* A collection of puzzles of various types.
|
|
6
7
|
* @public
|
|
@@ -9,23 +10,23 @@ export declare class PuzzleCollection {
|
|
|
9
10
|
/**
|
|
10
11
|
* All puzzles in the collection.
|
|
11
12
|
*/
|
|
12
|
-
readonly puzzles: readonly
|
|
13
|
+
readonly puzzles: readonly Files.Model.IPuzzleFileData[];
|
|
13
14
|
private readonly _byId;
|
|
14
15
|
private constructor();
|
|
15
16
|
/**
|
|
16
|
-
* Creates a new puzzle from a loaded {@link
|
|
17
|
-
* @param from - The {@link
|
|
17
|
+
* Creates a new puzzle from a loaded {@link Files.Model.IPuzzlesFile | PuzzlesFile}
|
|
18
|
+
* @param from - The {@link Files.Model.IPuzzlesFile | puzzles file} to be loaded.
|
|
18
19
|
* @returns `Success` with the resulting {@link PuzzleCollection | PuzzleCollection}
|
|
19
20
|
* or `Failure` with details if an error occurs.
|
|
20
21
|
*/
|
|
21
|
-
static create(from:
|
|
22
|
+
static create(from: Files.Model.IPuzzlesFile): Result<PuzzleCollection>;
|
|
22
23
|
/**
|
|
23
24
|
* Creates a new puzzle from a JSON file.
|
|
24
25
|
* @param path - path to the JSON file to be loaded.
|
|
25
26
|
* @returns `Success` with the resulting {@link PuzzleCollection | PuzzleCollection}
|
|
26
27
|
* or `Failure` with details if an error occurs.
|
|
27
28
|
*/
|
|
28
|
-
static load(
|
|
29
|
+
static load(file: FileTree.IFileTreeFileItem): Result<PuzzleCollection>;
|
|
29
30
|
/**
|
|
30
31
|
* Gets a puzzle by id from this collection.
|
|
31
32
|
* @param id - The string ID of the puzzle to be returned.
|
|
@@ -33,13 +34,19 @@ export declare class PuzzleCollection {
|
|
|
33
34
|
* `Failure` with details if an error occurs.
|
|
34
35
|
*/
|
|
35
36
|
getPuzzle(id: string): Result<PuzzleSession>;
|
|
37
|
+
/**
|
|
38
|
+
* Converts IPuzzleFileData to IPuzzleDefinition format.
|
|
39
|
+
* @param fileData - The puzzle file data to convert
|
|
40
|
+
* @returns Result containing the puzzle definition
|
|
41
|
+
*/
|
|
42
|
+
private _convertToDefinition;
|
|
36
43
|
/**
|
|
37
44
|
* Gets a puzzle by id from this collection.
|
|
38
45
|
* @param id - The string ID of the puzzle to be returned.
|
|
39
46
|
* @returns `Success` with the requested {@link PuzzleSession | puzzle}, or
|
|
40
47
|
* `Failure` with details if an error occurs.
|
|
41
48
|
*/
|
|
42
|
-
getDescription(id: string): Result<
|
|
49
|
+
getDescription(id: string): Result<Files.Model.IPuzzleFileData>;
|
|
43
50
|
}
|
|
44
51
|
/**
|
|
45
52
|
* Get well-known {@link PuzzleCollection | puzzle collections}.
|
|
@@ -60,7 +60,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
60
60
|
};
|
|
61
61
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
62
62
|
exports.PuzzleCollections = exports.PuzzleCollection = void 0;
|
|
63
|
-
const
|
|
63
|
+
const Files = __importStar(require("../files"));
|
|
64
64
|
const Puzzles = __importStar(require("../puzzles"));
|
|
65
65
|
const ts_utils_1 = require("@fgv/ts-utils");
|
|
66
66
|
const common_1 = require("../common");
|
|
@@ -74,11 +74,11 @@ class PuzzleCollection {
|
|
|
74
74
|
this.puzzles = puzzles.puzzles;
|
|
75
75
|
this._byId = new Map(this.puzzles
|
|
76
76
|
.map((p) => [p.id, p])
|
|
77
|
-
.filter((p) => p !== undefined));
|
|
77
|
+
.filter((p) => p[0] !== undefined));
|
|
78
78
|
}
|
|
79
79
|
/**
|
|
80
|
-
* Creates a new puzzle from a loaded {@link
|
|
81
|
-
* @param from - The {@link
|
|
80
|
+
* Creates a new puzzle from a loaded {@link Files.Model.IPuzzlesFile | PuzzlesFile}
|
|
81
|
+
* @param from - The {@link Files.Model.IPuzzlesFile | puzzles file} to be loaded.
|
|
82
82
|
* @returns `Success` with the resulting {@link PuzzleCollection | PuzzleCollection}
|
|
83
83
|
* or `Failure` with details if an error occurs.
|
|
84
84
|
*/
|
|
@@ -91,8 +91,8 @@ class PuzzleCollection {
|
|
|
91
91
|
* @returns `Success` with the resulting {@link PuzzleCollection | PuzzleCollection}
|
|
92
92
|
* or `Failure` with details if an error occurs.
|
|
93
93
|
*/
|
|
94
|
-
static load(
|
|
95
|
-
return
|
|
94
|
+
static load(file) {
|
|
95
|
+
return Files.Converters.loadPuzzlesFile(file).onSuccess(PuzzleCollection.create);
|
|
96
96
|
}
|
|
97
97
|
/**
|
|
98
98
|
* Gets a puzzle by id from this collection.
|
|
@@ -103,12 +103,41 @@ class PuzzleCollection {
|
|
|
103
103
|
getPuzzle(id) {
|
|
104
104
|
return this.getDescription(id)
|
|
105
105
|
.onSuccess((desc) => {
|
|
106
|
-
return
|
|
106
|
+
return this._convertToDefinition(desc);
|
|
107
|
+
})
|
|
108
|
+
.onSuccess((def) => {
|
|
109
|
+
return Puzzles.Any.create(def);
|
|
107
110
|
})
|
|
108
111
|
.onSuccess((puzzle) => {
|
|
109
112
|
return common_1.PuzzleSession.create(puzzle);
|
|
110
113
|
});
|
|
111
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Converts IPuzzleFileData to IPuzzleDefinition format.
|
|
117
|
+
* @param fileData - The puzzle file data to convert
|
|
118
|
+
* @returns Result containing the puzzle definition
|
|
119
|
+
*/
|
|
120
|
+
_convertToDefinition(fileData) {
|
|
121
|
+
// Validate that dimensions match cell count
|
|
122
|
+
const config = fileData.dimensions;
|
|
123
|
+
const expectedCells = config.cageWidthInCells *
|
|
124
|
+
config.cageHeightInCells *
|
|
125
|
+
config.boardWidthInCages *
|
|
126
|
+
config.boardHeightInCages;
|
|
127
|
+
// For killer sudoku, cells includes cage definitions after '|' separator
|
|
128
|
+
const gridCells = fileData.type === 'killer-sudoku' ? fileData.cells.split('|')[0] : fileData.cells;
|
|
129
|
+
const actualCells = gridCells.length;
|
|
130
|
+
if (actualCells !== expectedCells) {
|
|
131
|
+
return (0, ts_utils_1.fail)(`Dimensions mismatch: dimensions specify ${expectedCells} cells (${config.cageWidthInCells}x${config.cageHeightInCells} cages in ${config.boardWidthInCages}x${config.boardHeightInCages} layout) but cells string contains ${actualCells} cells`);
|
|
132
|
+
}
|
|
133
|
+
return common_1.PuzzleDefinitionFactory.create(config, {
|
|
134
|
+
id: fileData.id,
|
|
135
|
+
description: fileData.description,
|
|
136
|
+
type: fileData.type,
|
|
137
|
+
level: fileData.level,
|
|
138
|
+
cells: fileData.cells
|
|
139
|
+
});
|
|
140
|
+
}
|
|
112
141
|
/**
|
|
113
142
|
* Gets a puzzle by id from this collection.
|
|
114
143
|
* @param id - The string ID of the puzzle to be returned.
|
|
@@ -116,11 +145,12 @@ class PuzzleCollection {
|
|
|
116
145
|
* `Failure` with details if an error occurs.
|
|
117
146
|
*/
|
|
118
147
|
getDescription(id) {
|
|
119
|
-
const
|
|
120
|
-
|
|
148
|
+
const fileData = this._byId.get(id);
|
|
149
|
+
/* c8 ignore next 3 - tested but coverage fails intermittently */
|
|
150
|
+
if (!fileData) {
|
|
121
151
|
return (0, ts_utils_1.fail)(`Puzzle "${id}" not found`);
|
|
122
152
|
}
|
|
123
|
-
return (0, ts_utils_1.succeed)(
|
|
153
|
+
return (0, ts_utils_1.succeed)(fileData);
|
|
124
154
|
}
|
|
125
155
|
}
|
|
126
156
|
exports.PuzzleCollection = PuzzleCollection;
|
|
@@ -134,7 +164,7 @@ class PuzzleCollections {
|
|
|
134
164
|
*/
|
|
135
165
|
static get default() {
|
|
136
166
|
if (!PuzzleCollections._default) {
|
|
137
|
-
PuzzleCollections._default =
|
|
167
|
+
PuzzleCollections._default = Files.Converters.puzzlesFile
|
|
138
168
|
.convert(puzzles_json_1.default)
|
|
139
169
|
.onSuccess(PuzzleCollection.create)
|
|
140
170
|
.orThrow();
|
|
@@ -5,57 +5,52 @@
|
|
|
5
5
|
"description": "hidden pair sample",
|
|
6
6
|
"type": "sudoku",
|
|
7
7
|
"level": 1,
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
"7.8...3.1",
|
|
16
|
-
".513.87.2",
|
|
17
|
-
"..75.261.",
|
|
18
|
-
"..54.32.8",
|
|
19
|
-
"........."
|
|
20
|
-
]
|
|
8
|
+
"cells": ".........9.46.7....768.41..3.97.1.8.7.8...3.1.513.87.2..75.261...54.32.8.........",
|
|
9
|
+
"dimensions": {
|
|
10
|
+
"cageWidthInCells": 3,
|
|
11
|
+
"cageHeightInCells": 3,
|
|
12
|
+
"boardWidthInCages": 3,
|
|
13
|
+
"boardHeightInCages": 3
|
|
14
|
+
}
|
|
21
15
|
},
|
|
22
16
|
{
|
|
23
17
|
"id": "almost-done",
|
|
24
18
|
"description": "nearly complete puzzle missing only 1 in cell A1",
|
|
25
19
|
"type": "sudoku",
|
|
26
20
|
"level": 1,
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
|
|
21
|
+
"cells": ".46728539837549126259316748685297413312684957794153862428965371571832694963471285",
|
|
22
|
+
"dimensions": {
|
|
23
|
+
"cageWidthInCells": 3,
|
|
24
|
+
"cageHeightInCells": 3,
|
|
25
|
+
"boardWidthInCages": 3,
|
|
26
|
+
"boardHeightInCages": 3
|
|
27
|
+
}
|
|
30
28
|
},
|
|
31
29
|
{
|
|
32
30
|
"id": "sudoku-x-example",
|
|
33
31
|
"description": "sample x-sudoku puzzle",
|
|
34
32
|
"type": "sudoku-x",
|
|
35
33
|
"level": 1,
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
|
|
34
|
+
"cells": "4.....13....6.1.....7..29...76.....2....3..9.9.1....577...1.6..3...5.7...4......1",
|
|
35
|
+
"dimensions": {
|
|
36
|
+
"cageWidthInCells": 3,
|
|
37
|
+
"cageHeightInCells": 3,
|
|
38
|
+
"boardWidthInCages": 3,
|
|
39
|
+
"boardHeightInCages": 3
|
|
40
|
+
}
|
|
39
41
|
},
|
|
40
42
|
{
|
|
41
43
|
"id": "killer-insane",
|
|
42
44
|
"description": "Insane (3208329) example from djape.net",
|
|
43
45
|
"type": "killer-sudoku",
|
|
44
46
|
"level": 100,
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
|
|
52
|
-
"HOPPMQQNR",
|
|
53
|
-
"OOSTMUVWR",
|
|
54
|
-
"SSSTTUVWR",
|
|
55
|
-
"XYTTTZZab",
|
|
56
|
-
"XYYYcccab",
|
|
57
|
-
"|A11,B09,C09,D20,E16,F17,G30,H17,I13,J09,K11,L16,M16,N11,O16,P07,Q11,R10,S14,T39,U08,V17,W16,X06,Y26,Z06,a09,b09,c11"
|
|
58
|
-
]
|
|
47
|
+
"cells": "ABCCCDDDEABFFGGGDEHIJKMGLLLHIJKMGLNNHOPPMQQNROOSTMUVWRSSSTTUVWRXYTTTZZabXYYYcccab|A11,B09,C09,D20,E16,F17,G30,H17,I13,J09,K11,L16,M16,N11,O16,P07,Q11,R10,S14,T39,U08,V17,W16,X06,Y26,Z06,a09,b09,c11",
|
|
48
|
+
"dimensions": {
|
|
49
|
+
"cageWidthInCells": 3,
|
|
50
|
+
"cageHeightInCells": 3,
|
|
51
|
+
"boardWidthInCages": 3,
|
|
52
|
+
"boardHeightInCages": 3
|
|
53
|
+
}
|
|
59
54
|
}
|
|
60
55
|
]
|
|
61
56
|
}
|
|
@@ -59,6 +59,21 @@ export declare const allPuzzleTypes: PuzzleType[];
|
|
|
59
59
|
* {@link ICell | cells}.
|
|
60
60
|
* @public
|
|
61
61
|
*/
|
|
62
|
+
/**
|
|
63
|
+
* Calculate the minimum and maximum possible totals for a cage of given size
|
|
64
|
+
* @param cageSize - Number of cells in the cage
|
|
65
|
+
* @param maxValue - Maximum value allowed in a cell (e.g., 9 for 9x9, 16 for 16x16)
|
|
66
|
+
* @returns Object with min and max possible totals
|
|
67
|
+
* @public
|
|
68
|
+
*/
|
|
69
|
+
export declare function getCageTotalBounds(cageSize: number, maxValue: number): {
|
|
70
|
+
min: number;
|
|
71
|
+
max: number;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Legacy array for backward compatibility - supports standard 9x9 Sudoku
|
|
75
|
+
* @public
|
|
76
|
+
*/
|
|
62
77
|
export declare const totalsByCageSize: readonly {
|
|
63
78
|
min: number;
|
|
64
79
|
max: number;
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
*/
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
26
|
exports.totalsByCageSize = exports.allPuzzleTypes = void 0;
|
|
27
|
+
exports.getCageTotalBounds = getCageTotalBounds;
|
|
27
28
|
/**
|
|
28
29
|
* All supported public types.
|
|
29
30
|
* @public
|
|
@@ -34,6 +35,30 @@ exports.allPuzzleTypes = ['killer-sudoku', 'sudoku', 'sudoku-x'];
|
|
|
34
35
|
* {@link ICell | cells}.
|
|
35
36
|
* @public
|
|
36
37
|
*/
|
|
38
|
+
/**
|
|
39
|
+
* Calculate the minimum and maximum possible totals for a cage of given size
|
|
40
|
+
* @param cageSize - Number of cells in the cage
|
|
41
|
+
* @param maxValue - Maximum value allowed in a cell (e.g., 9 for 9x9, 16 for 16x16)
|
|
42
|
+
* @returns Object with min and max possible totals
|
|
43
|
+
* @public
|
|
44
|
+
*/
|
|
45
|
+
function getCageTotalBounds(cageSize, maxValue) {
|
|
46
|
+
/* c8 ignore next 3 - functional code tested but coverage intermittently missed */
|
|
47
|
+
if (cageSize <= 0) {
|
|
48
|
+
return { min: 0, max: 0 };
|
|
49
|
+
}
|
|
50
|
+
// Minimum is sum of first cageSize numbers (1, 2, 3, ...)
|
|
51
|
+
const min = (cageSize * (cageSize + 1)) / 2;
|
|
52
|
+
// Maximum is sum of last cageSize numbers (maxValue, maxValue-1, ...)
|
|
53
|
+
// But cageSize cannot exceed maxValue
|
|
54
|
+
const effectiveCageSize = Math.min(cageSize, maxValue);
|
|
55
|
+
const max = effectiveCageSize * maxValue - (effectiveCageSize * (effectiveCageSize - 1)) / 2;
|
|
56
|
+
return { min, max };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Legacy array for backward compatibility - supports standard 9x9 Sudoku
|
|
60
|
+
* @public
|
|
61
|
+
*/
|
|
37
62
|
exports.totalsByCageSize = [
|
|
38
63
|
{ min: 0, max: 0 },
|
|
39
64
|
{ min: 1, max: 9 },
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Converter } from '@fgv/ts-utils';
|
|
2
2
|
import { CageId, CellId, PuzzleType } from './common';
|
|
3
|
-
import { IPuzzleDescription } from './model';
|
|
4
3
|
/**
|
|
5
4
|
* Converts an arbitrary value to a {@link CageId | CageId}.
|
|
6
5
|
* @public
|
|
@@ -16,9 +15,4 @@ export declare const cellId: Converter<CellId>;
|
|
|
16
15
|
* @public
|
|
17
16
|
*/
|
|
18
17
|
export declare const puzzleType: Converter<PuzzleType, ReadonlyArray<PuzzleType>>;
|
|
19
|
-
/**
|
|
20
|
-
* Converts an arbitrary object to a {@link IPuzzleDescription | IPuzzleDescription}.
|
|
21
|
-
* @public
|
|
22
|
-
*/
|
|
23
|
-
export declare const puzzleDescription: Converter<IPuzzleDescription>;
|
|
24
18
|
//# sourceMappingURL=converters.d.ts.map
|
|
@@ -23,22 +23,32 @@
|
|
|
23
23
|
* SOFTWARE.
|
|
24
24
|
*/
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.
|
|
26
|
+
exports.puzzleType = exports.cellId = exports.cageId = void 0;
|
|
27
27
|
const ts_utils_1 = require("@fgv/ts-utils");
|
|
28
28
|
const common_1 = require("./common");
|
|
29
|
-
|
|
29
|
+
// Updated to support larger grids:
|
|
30
|
+
// - Row IDs: R followed by A-Z or AA-ZZ for rows
|
|
31
|
+
// - Column IDs: C followed by 1-99+ for columns
|
|
32
|
+
// - Section IDs: S followed by row letter(s) and column number(s)
|
|
33
|
+
// - X diagonals: X1, X2
|
|
34
|
+
// - Killer cages: K followed by letter(s)
|
|
35
|
+
const cageIdRegExp = /^(R[A-Z]{1,2}$)|(C[0-9]{1,3}$)|(S[A-Z]{1,2}[0-9]{1,3}$)|(X[1-2]$)|(K[A-Za-z]+$)/;
|
|
30
36
|
function validateCageId(from) {
|
|
31
37
|
if (cageIdRegExp.test(from)) {
|
|
32
38
|
return (0, ts_utils_1.succeed)(from);
|
|
33
39
|
}
|
|
34
|
-
return (0, ts_utils_1.fail)(`malformed cage ID "${from}"
|
|
40
|
+
return (0, ts_utils_1.fail)(`malformed cage ID "${from}"`);
|
|
35
41
|
}
|
|
36
|
-
|
|
42
|
+
// Updated to support larger grids:
|
|
43
|
+
// - Rows: A-Z for 0-25, then AA-ZZ for 26+
|
|
44
|
+
// - Columns: 1-999 for any size grid
|
|
45
|
+
const cellIdRegExp = /^[A-Z]{1,2}[0-9]{1,3}$/;
|
|
37
46
|
function validateCellId(from) {
|
|
38
|
-
|
|
39
|
-
|
|
47
|
+
// Check basic format
|
|
48
|
+
if (!cellIdRegExp.test(from)) {
|
|
49
|
+
return (0, ts_utils_1.fail)(`malformed cell ID "${from}" (expected row letter(s) followed by column number)`);
|
|
40
50
|
}
|
|
41
|
-
return (0, ts_utils_1.
|
|
51
|
+
return (0, ts_utils_1.succeed)(from);
|
|
42
52
|
}
|
|
43
53
|
/**
|
|
44
54
|
* Converts an arbitrary value to a {@link CageId | CageId}.
|
|
@@ -55,19 +65,4 @@ exports.cellId = ts_utils_1.Converters.string.map(validateCellId);
|
|
|
55
65
|
* @public
|
|
56
66
|
*/
|
|
57
67
|
exports.puzzleType = ts_utils_1.Converters.enumeratedValue(common_1.allPuzzleTypes);
|
|
58
|
-
/**
|
|
59
|
-
* Converts an arbitrary object to a {@link IPuzzleDescription | IPuzzleDescription}.
|
|
60
|
-
* @public
|
|
61
|
-
*/
|
|
62
|
-
exports.puzzleDescription = ts_utils_1.Converters.strictObject({
|
|
63
|
-
id: ts_utils_1.Converters.string,
|
|
64
|
-
description: ts_utils_1.Converters.string,
|
|
65
|
-
type: exports.puzzleType,
|
|
66
|
-
level: ts_utils_1.Converters.number,
|
|
67
|
-
rows: ts_utils_1.Converters.number,
|
|
68
|
-
cols: ts_utils_1.Converters.number,
|
|
69
|
-
cells: ts_utils_1.Converters.oneOf([ts_utils_1.Converters.string, ts_utils_1.Converters.stringArray.map((s) => (0, ts_utils_1.succeed)(s.join('')))])
|
|
70
|
-
}, {
|
|
71
|
-
optionalFields: ['id']
|
|
72
|
-
});
|
|
73
68
|
//# sourceMappingURL=converters.js.map
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { Result } from '@fgv/ts-utils';
|
|
2
2
|
import { CageId, CellId, IRowColumn } from './common';
|
|
3
3
|
import { ICage, ICell } from './public';
|
|
4
|
+
/**
|
|
5
|
+
* Parse a cell ID string back to row/column coordinates
|
|
6
|
+
* @param cellId - Cell ID string (e.g., "A1", "A01", "AB15")
|
|
7
|
+
* @returns Row and column indices (0-based) or undefined if invalid
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
export declare function parseCellId(cellId: string): IRowColumn | undefined;
|
|
4
11
|
/**
|
|
5
12
|
* @public
|
|
6
13
|
*/
|
|
@@ -9,7 +16,7 @@ export declare class Ids {
|
|
|
9
16
|
static cellId(spec: string | IRowColumn | ICell): Result<CellId>;
|
|
10
17
|
static rowCageId(row: number): CageId;
|
|
11
18
|
static columnCageId(col: number): CageId;
|
|
12
|
-
static sectionCageId(row: number, col: number): CageId;
|
|
19
|
+
static sectionCageId(row: number, col: number, cageHeight?: number, cageWidth?: number): CageId;
|
|
13
20
|
static cellIds(firstRow: number, numRows: number, firstCol: number, numCols: number): Result<CellId[]>;
|
|
14
21
|
}
|
|
15
22
|
//# sourceMappingURL=ids.d.ts.map
|
|
@@ -57,10 +57,74 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
57
57
|
})();
|
|
58
58
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
59
59
|
exports.Ids = void 0;
|
|
60
|
+
exports.parseCellId = parseCellId;
|
|
60
61
|
const ts_utils_1 = require("@fgv/ts-utils");
|
|
61
62
|
const Converters = __importStar(require("./converters"));
|
|
62
63
|
const firstRowIdChar = 'A'.charCodeAt(0);
|
|
63
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Convert a column index (0-based) to column letters (A-Z, AA-AZ, BA-BZ, etc.)
|
|
66
|
+
* @param col - 0-based column index
|
|
67
|
+
* @returns Column identifier string
|
|
68
|
+
*/
|
|
69
|
+
function columnToLetters(col) {
|
|
70
|
+
if (col < 26) {
|
|
71
|
+
// Single letter: A-Z
|
|
72
|
+
return String.fromCharCode(firstRowIdChar + col);
|
|
73
|
+
/* c8 ignore next 8 - functional code tested but coverage intermittently missed */
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
// Double letters: AA, AB, ..., ZZ
|
|
77
|
+
const firstLetter = Math.floor(col / 26) - 1;
|
|
78
|
+
const secondLetter = col % 26;
|
|
79
|
+
return (String.fromCharCode(firstRowIdChar + firstLetter) + String.fromCharCode(firstRowIdChar + secondLetter));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Convert a row index (0-based) to row number string (1-based with zero-padding)
|
|
84
|
+
* @param row - 0-based row index
|
|
85
|
+
* @param maxRow - Maximum row index (optional, for determining padding width)
|
|
86
|
+
* @returns Row number string with zero-padding
|
|
87
|
+
*/
|
|
88
|
+
function rowToNumber(row, maxRow) {
|
|
89
|
+
const rowNum = row + 1;
|
|
90
|
+
/* c8 ignore next 5 - functional code tested but coverage intermittently missed */
|
|
91
|
+
if (maxRow !== undefined) {
|
|
92
|
+
const maxRowNum = maxRow + 1;
|
|
93
|
+
const width = maxRowNum.toString().length;
|
|
94
|
+
return rowNum.toString().padStart(width, '0');
|
|
95
|
+
}
|
|
96
|
+
// Default: pad to 2 digits for grids up to 99x99
|
|
97
|
+
return rowNum.toString().padStart(2, '0');
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Parse a cell ID string back to row/column coordinates
|
|
101
|
+
* @param cellId - Cell ID string (e.g., "A1", "A01", "AB15")
|
|
102
|
+
* @returns Row and column indices (0-based) or undefined if invalid
|
|
103
|
+
* @public
|
|
104
|
+
*/
|
|
105
|
+
function parseCellId(cellId) {
|
|
106
|
+
const match = cellId.match(/^([A-Z]{1,2})([0-9]{1,3})$/);
|
|
107
|
+
/* c8 ignore next 3 - functional code tested but coverage intermittently missed */
|
|
108
|
+
if (!match) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
const rowLetters = match[1];
|
|
112
|
+
const colNumber = parseInt(match[2], 10);
|
|
113
|
+
let row;
|
|
114
|
+
if (rowLetters.length === 1) {
|
|
115
|
+
// Single letter: A-Z maps to rows 0-25
|
|
116
|
+
row = rowLetters.charCodeAt(0) - firstRowIdChar;
|
|
117
|
+
/* c8 ignore next 6 - functional code tested but coverage intermittently missed */
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
// Double letters: AA-ZZ for rows 26+
|
|
121
|
+
const firstLetter = rowLetters.charCodeAt(0) - firstRowIdChar;
|
|
122
|
+
const secondLetter = rowLetters.charCodeAt(1) - firstRowIdChar;
|
|
123
|
+
row = (firstLetter + 1) * 26 + secondLetter;
|
|
124
|
+
}
|
|
125
|
+
const col = colNumber - 1; // Convert from 1-based to 0-based
|
|
126
|
+
return { row, col };
|
|
127
|
+
}
|
|
64
128
|
function isRowColumn(from) {
|
|
65
129
|
return typeof from === 'object' && from !== null && 'row' in from && `col` in from;
|
|
66
130
|
}
|
|
@@ -72,6 +136,7 @@ class Ids {
|
|
|
72
136
|
if (typeof from === 'string') {
|
|
73
137
|
return Converters.cageId.convert(from);
|
|
74
138
|
}
|
|
139
|
+
/* c8 ignore next 2 - functional code tested but coverage intermittently missed */
|
|
75
140
|
return (0, ts_utils_1.succeed)(from.id);
|
|
76
141
|
}
|
|
77
142
|
static cellId(spec) {
|
|
@@ -79,22 +144,30 @@ class Ids {
|
|
|
79
144
|
if ('id' in spec) {
|
|
80
145
|
return (0, ts_utils_1.succeed)(spec.id);
|
|
81
146
|
}
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
|
|
147
|
+
// Standard sudoku convention: Letters for rows (A-I), numbers for columns (1-9)
|
|
148
|
+
const rowLetter = columnToLetters(spec.row);
|
|
149
|
+
// For 9x9 grids, use single digit format for backward compatibility
|
|
150
|
+
// For larger grids, use zero-padding
|
|
151
|
+
const colNumber = spec.col < 9 ? (spec.col + 1).toString() : rowToNumber(spec.col);
|
|
152
|
+
return (0, ts_utils_1.succeed)(`${rowLetter}${colNumber}`);
|
|
85
153
|
}
|
|
86
154
|
return Converters.cellId.convert(spec);
|
|
87
155
|
}
|
|
88
156
|
static rowCageId(row) {
|
|
89
|
-
return `R${
|
|
157
|
+
return `R${columnToLetters(row)}`;
|
|
90
158
|
}
|
|
91
159
|
static columnCageId(col) {
|
|
92
|
-
|
|
160
|
+
// For backward compatibility, use single digit for columns < 9
|
|
161
|
+
const colNumber = col < 9 ? (col + 1).toString() : rowToNumber(col);
|
|
162
|
+
return `C${colNumber}`;
|
|
93
163
|
}
|
|
94
|
-
static sectionCageId(row, col) {
|
|
95
|
-
row = Math.floor(row /
|
|
96
|
-
col = Math.floor(col /
|
|
97
|
-
|
|
164
|
+
static sectionCageId(row, col, cageHeight = 3, cageWidth = 3) {
|
|
165
|
+
row = Math.floor(row / cageHeight) * cageHeight;
|
|
166
|
+
col = Math.floor(col / cageWidth) * cageWidth;
|
|
167
|
+
// Section IDs use row letter and column number
|
|
168
|
+
const rowLetter = columnToLetters(row);
|
|
169
|
+
const colNumber = col < 9 ? (col + 1).toString() : rowToNumber(col);
|
|
170
|
+
return `S${rowLetter}${colNumber}`;
|
|
98
171
|
}
|
|
99
172
|
static cellIds(firstRow, numRows, firstCol, numCols) {
|
|
100
173
|
const cellIds = [];
|
|
@@ -2,10 +2,11 @@ import * as Converters from './converters';
|
|
|
2
2
|
export * from './cage';
|
|
3
3
|
export * from './cell';
|
|
4
4
|
export * from './common';
|
|
5
|
-
export { Ids } from './ids';
|
|
6
|
-
export
|
|
5
|
+
export { Ids, parseCellId } from './ids';
|
|
6
|
+
export { DefaultSudokuLogger } from './logging';
|
|
7
7
|
export * from './public';
|
|
8
8
|
export * from './puzzle';
|
|
9
|
+
export * from './puzzleDefinitions';
|
|
9
10
|
export * from './puzzleSession';
|
|
10
11
|
export * from './puzzleState';
|
|
11
12
|
export { Converters };
|
|
@@ -59,7 +59,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
59
59
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
60
60
|
};
|
|
61
61
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
62
|
-
exports.Converters = exports.Ids = void 0;
|
|
62
|
+
exports.Converters = exports.DefaultSudokuLogger = exports.parseCellId = exports.Ids = void 0;
|
|
63
63
|
const Converters = __importStar(require("./converters"));
|
|
64
64
|
exports.Converters = Converters;
|
|
65
65
|
__exportStar(require("./cage"), exports);
|
|
@@ -67,9 +67,12 @@ __exportStar(require("./cell"), exports);
|
|
|
67
67
|
__exportStar(require("./common"), exports);
|
|
68
68
|
var ids_1 = require("./ids");
|
|
69
69
|
Object.defineProperty(exports, "Ids", { enumerable: true, get: function () { return ids_1.Ids; } });
|
|
70
|
-
|
|
70
|
+
Object.defineProperty(exports, "parseCellId", { enumerable: true, get: function () { return ids_1.parseCellId; } });
|
|
71
|
+
var logging_1 = require("./logging");
|
|
72
|
+
Object.defineProperty(exports, "DefaultSudokuLogger", { enumerable: true, get: function () { return logging_1.DefaultSudokuLogger; } });
|
|
71
73
|
__exportStar(require("./public"), exports);
|
|
72
74
|
__exportStar(require("./puzzle"), exports);
|
|
75
|
+
__exportStar(require("./puzzleDefinitions"), exports);
|
|
73
76
|
__exportStar(require("./puzzleSession"), exports);
|
|
74
77
|
__exportStar(require("./puzzleState"), exports);
|
|
75
78
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* MIT License
|
|
4
|
+
*
|
|
5
|
+
* Copyright (c) 2023 Erik Fortune
|
|
6
|
+
*
|
|
7
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
* in the Software without restriction, including without limitation the rights
|
|
10
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
* furnished to do so, subject to the following conditions:
|
|
13
|
+
*
|
|
14
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
* copies or substantial portions of the Software.
|
|
16
|
+
*
|
|
17
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
* SOFTWARE.
|
|
24
|
+
*/
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.DefaultSudokuLogger = void 0;
|
|
27
|
+
const ts_utils_1 = require("@fgv/ts-utils");
|
|
28
|
+
/**
|
|
29
|
+
* Default no-op logger for use when diagnostic logging is not needed.
|
|
30
|
+
* @public
|
|
31
|
+
*/
|
|
32
|
+
exports.DefaultSudokuLogger = new ts_utils_1.Logging.LogReporter({
|
|
33
|
+
logger: new ts_utils_1.Logging.NoOpLogger()
|
|
34
|
+
});
|
|
35
|
+
//# sourceMappingURL=logging.js.map
|