@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.
Files changed (82) hide show
  1. package/debug_killer.js +50 -0
  2. package/debug_test.js +88 -0
  3. package/dist/ts-sudoku-lib.d.ts +1505 -42
  4. package/lib/index.d.ts +3 -2
  5. package/lib/index.js +5 -3
  6. package/lib/packlets/collections/collections.d.ts +15 -8
  7. package/lib/packlets/collections/collections.js +41 -11
  8. package/lib/packlets/collections/data/puzzles.json +28 -33
  9. package/lib/packlets/common/common.d.ts +15 -0
  10. package/lib/packlets/common/common.js +25 -0
  11. package/lib/packlets/common/converters.d.ts +0 -6
  12. package/lib/packlets/common/converters.js +17 -22
  13. package/lib/packlets/common/ids.d.ts +8 -1
  14. package/lib/packlets/common/ids.js +83 -10
  15. package/lib/packlets/common/index.d.ts +3 -2
  16. package/lib/packlets/common/index.js +5 -2
  17. package/lib/packlets/common/logging.d.ts +7 -0
  18. package/lib/packlets/common/logging.js +35 -0
  19. package/lib/packlets/common/puzzle.d.ts +23 -13
  20. package/lib/packlets/common/puzzle.js +51 -25
  21. package/lib/packlets/common/puzzleDefinitions.d.ts +127 -0
  22. package/lib/packlets/common/puzzleDefinitions.js +251 -0
  23. package/lib/packlets/common/puzzleSession.d.ts +8 -0
  24. package/lib/packlets/common/puzzleSession.js +12 -0
  25. package/lib/packlets/common/puzzleState.js +3 -0
  26. package/lib/packlets/files/converters.d.ts +27 -0
  27. package/lib/packlets/files/converters.js +71 -0
  28. package/lib/packlets/{file/converters.d.ts → files/filesystem.d.ts} +3 -8
  29. package/lib/packlets/{file/converters.js → files/filesystem.js} +5 -14
  30. package/lib/packlets/files/index.d.ts +5 -0
  31. package/lib/packlets/{file → files}/index.js +3 -1
  32. package/lib/packlets/files/model.d.ts +32 -0
  33. package/lib/packlets/hints/baseHintProvider.d.ts +103 -0
  34. package/lib/packlets/hints/baseHintProvider.js +203 -0
  35. package/lib/packlets/hints/explanations.d.ts +105 -0
  36. package/lib/packlets/hints/explanations.js +276 -0
  37. package/lib/packlets/hints/hiddenSingles.d.ts +81 -0
  38. package/lib/packlets/hints/hiddenSingles.js +237 -0
  39. package/lib/packlets/hints/hintRegistry.d.ts +95 -0
  40. package/lib/packlets/hints/hintRegistry.js +226 -0
  41. package/lib/packlets/hints/hints.d.ts +144 -0
  42. package/lib/packlets/hints/hints.js +316 -0
  43. package/lib/packlets/hints/index.d.ts +10 -0
  44. package/lib/packlets/hints/index.js +55 -0
  45. package/lib/packlets/hints/interfaces.d.ts +121 -0
  46. package/lib/packlets/{file/model.js → hints/interfaces.js} +1 -1
  47. package/lib/packlets/hints/nakedSingles.d.ts +57 -0
  48. package/lib/packlets/hints/nakedSingles.js +202 -0
  49. package/lib/packlets/hints/puzzleSessionHints.d.ts +306 -0
  50. package/lib/packlets/hints/puzzleSessionHints.js +499 -0
  51. package/lib/packlets/hints/types.d.ts +102 -0
  52. package/lib/packlets/hints/types.js +46 -0
  53. package/lib/packlets/puzzles/anyPuzzle.d.ts +3 -3
  54. package/lib/packlets/puzzles/anyPuzzle.js +1 -1
  55. package/lib/packlets/puzzles/index.d.ts +4 -2
  56. package/lib/packlets/puzzles/index.js +20 -3
  57. package/lib/packlets/puzzles/internal/combinationCache.d.ts +29 -0
  58. package/lib/packlets/puzzles/internal/combinationCache.js +79 -0
  59. package/lib/packlets/puzzles/internal/combinationGenerator.d.ts +34 -0
  60. package/lib/packlets/puzzles/internal/combinationGenerator.js +146 -0
  61. package/lib/packlets/puzzles/internal/possibilityAnalyzer.d.ts +25 -0
  62. package/lib/packlets/puzzles/internal/possibilityAnalyzer.js +125 -0
  63. package/lib/packlets/puzzles/killerCombinations.d.ts +90 -0
  64. package/lib/packlets/puzzles/killerCombinations.js +226 -0
  65. package/lib/packlets/puzzles/killerCombinationsTypes.d.ts +17 -0
  66. package/lib/packlets/puzzles/killerCombinationsTypes.js +26 -0
  67. package/lib/packlets/puzzles/killerSudokuPuzzle.d.ts +2 -2
  68. package/lib/packlets/puzzles/killerSudokuPuzzle.js +15 -11
  69. package/lib/packlets/puzzles/sudokuPuzzle.d.ts +2 -2
  70. package/lib/packlets/puzzles/sudokuXPuzzle.d.ts +2 -2
  71. package/lib/packlets/puzzles/sudokuXPuzzle.js +8 -4
  72. package/package.json +5 -5
  73. package/sample-12x12-puzzle.js +72 -0
  74. package/sample-4x4-puzzles.js +68 -0
  75. package/sample-6x6-puzzle.js +61 -0
  76. package/temp_12x12_cells.txt +12 -0
  77. package/temp_correct.txt +12 -0
  78. package/test-grid-sizes.js +132 -0
  79. package/lib/packlets/common/model.d.ts +0 -15
  80. package/lib/packlets/file/index.d.ts +0 -4
  81. package/lib/packlets/file/model.d.ts +0 -9
  82. /package/lib/packlets/{common → files}/model.js +0 -0
@@ -0,0 +1,226 @@
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.KillerCombinations = void 0;
27
+ const ts_utils_1 = require("@fgv/ts-utils");
28
+ const common_1 = require("../common");
29
+ const combinationCache_1 = require("./internal/combinationCache");
30
+ const combinationGenerator_1 = require("./internal/combinationGenerator");
31
+ const possibilityAnalyzer_1 = require("./internal/possibilityAnalyzer");
32
+ /**
33
+ * Helper class providing UI assistance functions for killer sudoku puzzle solving.
34
+ * Generates possible totals, number combinations with constraints, and cell-specific
35
+ * possibilities based on current puzzle state.
36
+ * @public
37
+ */
38
+ class KillerCombinations {
39
+ /**
40
+ * Gets all mathematically possible totals for a given cage size.
41
+ *
42
+ * Uses the existing totalsByCageSize constant to determine the valid range
43
+ * of totals for the specified cage size and returns all integers in that range.
44
+ *
45
+ * @param cageSize - The number of cells in the cage (must be 1-9)
46
+ * @returns Result containing array of possible totals in ascending order
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * // Get possible totals for a 3-cell cage
51
+ * const result = KillerCombinations.getPossibleTotals(3);
52
+ * if (result.isSuccess()) {
53
+ * console.log(result.value); // [6, 7, 8, 9, ..., 24]
54
+ * }
55
+ * ```
56
+ */
57
+ static getPossibleTotals(cageSize, maxValue = 9) {
58
+ // Validate cage size
59
+ if (!Number.isInteger(cageSize) || cageSize < 1 || cageSize > maxValue) {
60
+ return (0, ts_utils_1.fail)(`Cage size must be an integer between 1 and ${maxValue}, got ${cageSize}`);
61
+ }
62
+ const bounds = (0, common_1.getCageTotalBounds)(cageSize, maxValue);
63
+ // Generate all totals in the valid range
64
+ const totals = [];
65
+ for (let total = bounds.min; total <= bounds.max; total++) {
66
+ totals.push(total);
67
+ }
68
+ return (0, ts_utils_1.succeed)(totals);
69
+ }
70
+ /**
71
+ * Generates all possible number combinations that sum to the target total.
72
+ *
73
+ * Each combination contains unique numbers from 1-9 that sum exactly to the
74
+ * specified total. Combinations respect both excluded and required number
75
+ * constraints if provided.
76
+ *
77
+ * @param cageSize - The number of cells in the cage (must be 1-9)
78
+ * @param total - The target sum (must be valid for the cage size)
79
+ * @param constraints - Optional constraints on included/excluded numbers
80
+ * @returns Result containing array of combinations, each sorted in ascending order
81
+ *
82
+ * @example
83
+ * ```typescript
84
+ * // Get all combinations for a 3-cell cage with total 15
85
+ * const result = KillerCombinations.getCombinations(3, 15);
86
+ * if (result.isSuccess()) {
87
+ * console.log(result.value); // [[1,5,9], [1,6,8], [2,4,9], ...]
88
+ * }
89
+ *
90
+ * // With constraints - exclude 1 and 2, require 9
91
+ * const constrained = KillerCombinations.getCombinations(3, 15, {
92
+ * excludedNumbers: [1, 2],
93
+ * requiredNumbers: [9]
94
+ * });
95
+ * ```
96
+ */
97
+ static getCombinations(cageSize, total, constraints, maxValue = 9) {
98
+ // Validate cage size
99
+ if (!Number.isInteger(cageSize) || cageSize < 1 || cageSize > maxValue) {
100
+ return (0, ts_utils_1.fail)(`Cage size must be an integer between 1 and ${maxValue}, got ${cageSize}`);
101
+ }
102
+ // Validate total against cage size bounds
103
+ const bounds = (0, common_1.getCageTotalBounds)(cageSize, maxValue);
104
+ if (!Number.isInteger(total) || total < bounds.min || total > bounds.max) {
105
+ return (0, ts_utils_1.fail)(`Total ${total} is invalid for cage size ${cageSize} - valid range: ${bounds.min}-${bounds.max}`);
106
+ }
107
+ // Validate constraints
108
+ const constraintValidation = this._validateConstraints(constraints, maxValue);
109
+ if (constraintValidation.isFailure()) {
110
+ return (0, ts_utils_1.fail)(constraintValidation.message);
111
+ }
112
+ // Check cache first
113
+ const cacheKey = combinationCache_1.CombinationCache.generateKey(cageSize, total, constraints);
114
+ const cached = combinationCache_1.CombinationCache.get(cacheKey);
115
+ if (cached) {
116
+ return (0, ts_utils_1.succeed)(cached);
117
+ }
118
+ // Generate combinations
119
+ const combinations = combinationGenerator_1.CombinationGenerator.generate(cageSize, total, constraints, maxValue);
120
+ // Cache the result
121
+ combinationCache_1.CombinationCache.set(cacheKey, combinations);
122
+ return (0, ts_utils_1.succeed)(combinations);
123
+ }
124
+ /**
125
+ * Determines possible values for each cell in a killer cage based on current puzzle state.
126
+ *
127
+ * Analyzes the current state of the puzzle and cage to determine which values
128
+ * are possible for each empty cell, considering both killer cage constraints
129
+ * and standard sudoku constraints (row, column, section uniqueness).
130
+ *
131
+ * @param puzzle - The puzzle instance
132
+ * @param state - Current puzzle state
133
+ * @param cage - The killer cage to analyze (must be of type 'killer')
134
+ * @returns Result containing map of CellId to possible number arrays
135
+ *
136
+ * @example
137
+ * ```typescript
138
+ * const cage = puzzle.getCage(cageId).orThrow();
139
+ * const possibilities = KillerCombinations.getCellPossibilities(puzzle, state, cage);
140
+ * if (possibilities.isSuccess()) {
141
+ * for (const [cellId, values] of possibilities.value) {
142
+ * console.log(`Cell ${cellId} can have values: ${values.join(', ')}`);
143
+ * }
144
+ * }
145
+ * ```
146
+ */
147
+ static getCellPossibilities(puzzle, state, cage) {
148
+ // Validate cage type
149
+ if (cage.cageType !== 'killer') {
150
+ return (0, ts_utils_1.fail)(`Expected killer cage, got ${cage.cageType}`);
151
+ }
152
+ // Get current values in the cage to build constraints
153
+ // We need the concrete Cage class for containedValues method
154
+ const concreteCage = cage;
155
+ const currentValues = concreteCage.containedValues(state);
156
+ const constraints = {
157
+ excludedNumbers: Array.from(currentValues)
158
+ };
159
+ // Get valid combinations for this cage
160
+ const combinationsResult = this.getCombinations(cage.numCells, cage.total, constraints);
161
+ /* c8 ignore next 3 - error propagation from getCombinations already tested, integration error hard to trigger */
162
+ if (combinationsResult.isFailure()) {
163
+ return (0, ts_utils_1.fail)(`Failed to get combinations for cage ${cage.id}: ${combinationsResult.message}`);
164
+ }
165
+ // Analyze possibilities for each cell
166
+ return possibilityAnalyzer_1.PossibilityAnalyzer.analyze(puzzle, state, cage, combinationsResult.value);
167
+ }
168
+ /**
169
+ * Validate constraint parameters.
170
+ * @param constraints - Constraints to validate
171
+ * @param maxValue - Maximum value allowed in cells
172
+ * @returns Result indicating success or failure with error message
173
+ */
174
+ static _validateConstraints(constraints, maxValue = 9) {
175
+ if (!constraints) {
176
+ return (0, ts_utils_1.succeed)(true);
177
+ }
178
+ // Validate excluded numbers
179
+ if (constraints.excludedNumbers) {
180
+ const excluded = constraints.excludedNumbers;
181
+ if (!Array.isArray(excluded)) {
182
+ return (0, ts_utils_1.fail)('excludedNumbers must be an array');
183
+ }
184
+ for (const num of excluded) {
185
+ if (!Number.isInteger(num) || num < 1 || num > maxValue) {
186
+ return (0, ts_utils_1.fail)(`excludedNumbers must contain integers between 1-${maxValue}, got ${num}`);
187
+ }
188
+ }
189
+ // Check for duplicates
190
+ const uniqueExcluded = new Set(excluded);
191
+ if (uniqueExcluded.size !== excluded.length) {
192
+ return (0, ts_utils_1.fail)('excludedNumbers must not contain duplicates');
193
+ }
194
+ }
195
+ // Validate required numbers
196
+ if (constraints.requiredNumbers) {
197
+ const required = constraints.requiredNumbers;
198
+ if (!Array.isArray(required)) {
199
+ return (0, ts_utils_1.fail)('requiredNumbers must be an array');
200
+ }
201
+ for (const num of required) {
202
+ if (!Number.isInteger(num) || num < 1 || num > maxValue) {
203
+ return (0, ts_utils_1.fail)(`requiredNumbers must contain integers between 1-${maxValue}, got ${num}`);
204
+ }
205
+ }
206
+ // Check for duplicates
207
+ const uniqueRequired = new Set(required);
208
+ if (uniqueRequired.size !== required.length) {
209
+ return (0, ts_utils_1.fail)('requiredNumbers must not contain duplicates');
210
+ }
211
+ }
212
+ // Check for overlap between excluded and required
213
+ if (constraints.excludedNumbers && constraints.requiredNumbers) {
214
+ const excludedSet = new Set(constraints.excludedNumbers);
215
+ const requiredSet = new Set(constraints.requiredNumbers);
216
+ for (const required of requiredSet) {
217
+ if (excludedSet.has(required)) {
218
+ return (0, ts_utils_1.fail)(`Number ${required} cannot be both required and excluded`);
219
+ }
220
+ }
221
+ }
222
+ return (0, ts_utils_1.succeed)(true);
223
+ }
224
+ }
225
+ exports.KillerCombinations = KillerCombinations;
226
+ //# sourceMappingURL=killerCombinations.js.map
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Constraints that can be applied when generating killer cage combinations.
3
+ * @public
4
+ */
5
+ export interface IKillerConstraints {
6
+ /**
7
+ * Numbers that cannot be present in the combination.
8
+ * Must be unique values between 1-9.
9
+ */
10
+ readonly excludedNumbers?: readonly number[];
11
+ /**
12
+ * Numbers that must be present in the combination.
13
+ * Must be unique values between 1-9.
14
+ */
15
+ readonly requiredNumbers?: readonly number[];
16
+ }
17
+ //# sourceMappingURL=killerCombinationsTypes.d.ts.map
@@ -0,0 +1,26 @@
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
+ //# sourceMappingURL=killerCombinationsTypes.js.map
@@ -1,11 +1,11 @@
1
1
  import { Result } from '@fgv/ts-utils';
2
- import { IPuzzleDescription, Puzzle } from '../common';
2
+ import { IPuzzleDefinition, Puzzle } from '../common';
3
3
  /**
4
4
  * @public
5
5
  */
6
6
  export declare class KillerSudokuPuzzle extends Puzzle {
7
7
  private constructor();
8
- static create(desc: IPuzzleDescription): Result<Puzzle>;
8
+ static create(desc: IPuzzleDefinition): Result<Puzzle>;
9
9
  private static _getKillerCages;
10
10
  private static _getCageCells;
11
11
  private static _getCages;
@@ -53,6 +53,7 @@ class KillerSudokuPuzzle extends common_1.Puzzle {
53
53
  }
54
54
  static _getKillerCages(puzzle) {
55
55
  const decl = puzzle.cells.split('|');
56
+ /* c8 ignore next 3 - tested but coverage has intermittent issues */
56
57
  if (decl.length !== 2) {
57
58
  throw new Error(`malformed cells|cages "${puzzle.cells}"`);
58
59
  }
@@ -66,13 +67,14 @@ class KillerSudokuPuzzle extends common_1.Puzzle {
66
67
  var _a;
67
68
  const cages = new Map();
68
69
  const cageMapping = Array.from(mappingDecl);
69
- if (cageMapping.length !== puzzle.rows * puzzle.cols) {
70
- const expected = puzzle.rows * puzzle.cols;
70
+ /* c8 ignore next 5 - defensive coding: protected by validation in puzzleDefinitions.ts */
71
+ if (cageMapping.length !== puzzle.totalRows * puzzle.totalColumns) {
72
+ const expected = puzzle.totalRows * puzzle.totalColumns;
71
73
  const got = cageMapping.length;
72
74
  throw new Error(`expected ${expected} cell mappings, found ${got}`);
73
75
  }
74
- for (let row = 0; row < puzzle.rows; row++) {
75
- for (let col = 0; col < puzzle.cols; col++) {
76
+ for (let row = 0; row < puzzle.totalRows; row++) {
77
+ for (let col = 0; col < puzzle.totalColumns; col++) {
76
78
  const cage = cageMapping.shift();
77
79
  const cell = common_1.Ids.cellId({ row, col }).orThrow();
78
80
  const cells = (_a = cages.get(cage)) !== null && _a !== void 0 ? _a : [];
@@ -82,7 +84,7 @@ class KillerSudokuPuzzle extends common_1.Puzzle {
82
84
  }
83
85
  return cages;
84
86
  }
85
- static _getCages(__puzzle, cageCells, cagePart) {
87
+ static _getCages(puzzle, cageCells, cagePart) {
86
88
  const cages = new Map();
87
89
  const cageDefs = cagePart.split(',');
88
90
  if (cageDefs.length !== cageCells.size) {
@@ -100,10 +102,12 @@ class KillerSudokuPuzzle extends common_1.Puzzle {
100
102
  if (!cells) {
101
103
  throw new Error(`cage ${cageId} has no cells`);
102
104
  }
103
- if (cells.length < 1 || cells.length > 9) {
104
- throw new Error(`invalid cell count ${cells.length} for cage ${cageId}`);
105
+ // Use the puzzle's maxValue for dynamic validation
106
+ const maxValue = puzzle.maxValue;
107
+ if (cells.length < 1 || cells.length > maxValue) {
108
+ throw new Error(`invalid cell count ${cells.length} for cage ${cageId} (max ${maxValue})`);
105
109
  }
106
- const { min, max } = common_1.totalsByCageSize[cells.length];
110
+ const { min, max } = (0, common_1.getCageTotalBounds)(cells.length, maxValue);
107
111
  if (total < min || total > max) {
108
112
  throw new Error(`invalid total ${total} for cage ${cageId} (expected ${min}..${max})`);
109
113
  }
@@ -113,13 +117,13 @@ class KillerSudokuPuzzle extends common_1.Puzzle {
113
117
  }
114
118
  static _getKillerCells(puzzle, givens) {
115
119
  const cells = [];
116
- for (let row = 0; row < puzzle.rows; row++) {
117
- for (let col = 0; col < puzzle.cols; col++) {
120
+ for (let row = 0; row < puzzle.totalRows; row++) {
121
+ for (let col = 0; col < puzzle.totalColumns; col++) {
118
122
  const cellId = common_1.Ids.cellId({ row, col }).orThrow();
119
123
  const cage = givens.find((g) => g.cellIds[0] === cellId);
120
124
  if (cage) {
121
125
  /* c8 ignore next 3 - defense in depth should never happen */
122
- if (cage.total < 1 || cage.total > 9) {
126
+ if (cage.total < 1 || cage.total > puzzle.maxValue) {
123
127
  throw new Error(`invalid total ${cage.total} for cell ${cellId}`);
124
128
  }
125
129
  cells.push(String(cage.total));
@@ -1,10 +1,10 @@
1
1
  import { Result } from '@fgv/ts-utils';
2
- import { IPuzzleDescription, Puzzle } from '../common';
2
+ import { IPuzzleDefinition, Puzzle } from '../common';
3
3
  /**
4
4
  * @public
5
5
  */
6
6
  export declare class SudokuPuzzle extends Puzzle {
7
7
  private constructor();
8
- static create(puzzle: IPuzzleDescription): Result<Puzzle>;
8
+ static create(puzzle: IPuzzleDefinition): Result<Puzzle>;
9
9
  }
10
10
  //# sourceMappingURL=sudokuPuzzle.d.ts.map
@@ -1,11 +1,11 @@
1
1
  import { Result } from '@fgv/ts-utils';
2
- import { IPuzzleDescription, Puzzle } from '../common';
2
+ import { IPuzzleDefinition, Puzzle } from '../common';
3
3
  /**
4
4
  * @public
5
5
  */
6
6
  export declare class SudokuXPuzzle extends Puzzle {
7
7
  private constructor();
8
- static create(puzzle: IPuzzleDescription): Result<Puzzle>;
8
+ static create(puzzle: IPuzzleDefinition): Result<Puzzle>;
9
9
  private static _getXCages;
10
10
  }
11
11
  //# sourceMappingURL=sudokuXPuzzle.d.ts.map
@@ -36,7 +36,11 @@ class SudokuXPuzzle extends common_1.Puzzle {
36
36
  static create(puzzle) {
37
37
  /* c8 ignore next 3 */
38
38
  if (puzzle.type !== 'sudoku-x') {
39
- return fail(`Puzzle '${puzzle.description}' unsupported type ${puzzle.type}`);
39
+ return (0, ts_utils_1.fail)(`Puzzle '${puzzle.description}' unsupported type ${puzzle.type}`);
40
+ }
41
+ // Sudoku X diagonals work for any square puzzle (totalRows === totalColumns)
42
+ if (puzzle.totalRows !== puzzle.totalColumns) {
43
+ return (0, ts_utils_1.fail)(`Sudoku X puzzle must be square, got ${puzzle.totalRows}x${puzzle.totalColumns}`);
40
44
  }
41
45
  return (0, ts_utils_1.captureResult)(() => {
42
46
  return new SudokuXPuzzle(puzzle, SudokuXPuzzle._getXCages(puzzle));
@@ -47,14 +51,14 @@ class SudokuXPuzzle extends common_1.Puzzle {
47
51
  static _getXCages(puzzle) {
48
52
  const x1Cells = [];
49
53
  const x2Cells = [];
50
- for (let row = 0, col1 = 0, col2 = puzzle.cols - 1; row < puzzle.rows; row++, col1++, col2--) {
54
+ for (let row = 0, col1 = 0, col2 = puzzle.totalColumns - 1; row < puzzle.totalRows; row++, col1++, col2--) {
51
55
  x1Cells.push(common_1.Ids.cellId({ row, col: col1 }).orThrow());
52
56
  x2Cells.push(common_1.Ids.cellId({ row, col: col2 }).orThrow());
53
57
  }
54
58
  const x1Id = common_1.Ids.cageId('X1').orThrow();
55
59
  const x2Id = common_1.Ids.cageId('X2').orThrow();
56
- const x1 = common_1.Cage.create(x1Id, 'x', 45, x1Cells).orThrow();
57
- const x2 = common_1.Cage.create(x2Id, 'x', 45, x2Cells).orThrow();
60
+ const x1 = common_1.Cage.create(x1Id, 'x', puzzle.basicCageTotal, x1Cells).orThrow();
61
+ const x2 = common_1.Cage.create(x2Id, 'x', puzzle.basicCageTotal, x2Cells).orThrow();
58
62
  return [
59
63
  [x1Id, x1],
60
64
  [x2Id, x2]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fgv/ts-sudoku-lib",
3
- "version": "5.0.1-0",
3
+ "version": "5.0.1-2",
4
4
  "description": "Sudoku rules library",
5
5
  "main": "lib/index.js",
6
6
  "types": "dist/ts-sudoku-lib.d.ts",
@@ -43,12 +43,12 @@
43
43
  "@rushstack/eslint-config": "4.4.0",
44
44
  "@rushstack/heft-jest-plugin": "0.16.13",
45
45
  "eslint-plugin-tsdoc": "~0.4.0",
46
- "@fgv/ts-utils": "5.0.1-0",
47
- "@fgv/ts-utils-jest": "5.0.1-0",
48
- "@fgv/ts-json-base": "5.0.1-0"
46
+ "@fgv/ts-utils": "5.0.1-2",
47
+ "@fgv/ts-json-base": "5.0.1-2",
48
+ "@fgv/ts-utils-jest": "5.0.1-2"
49
49
  },
50
50
  "peerDependencies": {
51
- "@fgv/ts-utils": "5.0.1-0"
51
+ "@fgv/ts-utils": "5.0.1-2"
52
52
  },
53
53
  "scripts": {
54
54
  "build": "heft build --clean",
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Sample 12x12 puzzle generated from screenshot
4
+ const { PuzzleDefinitionFactory, STANDARD_CONFIGS } = require('./lib/packlets/common');
5
+
6
+ console.log('🧩 Sample 12x12 Sudoku Puzzle\n');
7
+
8
+ // 12x12 puzzle from screenshot
9
+ // Note: A=10, B=11, C=12 in standard 12x12 Sudoku
10
+ // Rebuilding with exact 12 chars per row (total 144):
11
+ // Row 0: ..1.6.2.A7.. (12 chars)
12
+ // Row 1: 62.5B....1.. (12 chars)
13
+ // Row 2: .8....C3B.62 (12 chars)
14
+ // Row 3: A97.2.6..B5. (12 chars)
15
+ // Row 4: .5......37C. (12 chars) - was missing final .
16
+ // Row 5: 3B....7...9. (12 chars) - was missing final .
17
+ // Row 6: 57B.4.....1. (12 chars) - was missing final .
18
+ // Row 7: 8.....A.9... (12 chars) - was missing final .
19
+ // Row 8: .4..97...6.. (12 chars)
20
+ // Row 9: .1C.......B. (12 chars) - was missing final .
21
+ // Row 10: 4....2.C.... (12 chars) - was missing final .
22
+ // Row 11: ....5C..7126 (12 chars)
23
+
24
+ const puzzle12x12Description = {
25
+ description: 'Sample 12x12 Sudoku #1',
26
+ type: 'sudoku',
27
+ level: 3,
28
+ cells:
29
+ '..1.6.2.A7..62.5B....1...8....C3B.62A97.2.6..B5..5......37C.3B....7...9.57B.4.....1.8.....A.9....4..97...6...1C.......B.4....2.C........5C..7126'
30
+ };
31
+
32
+ // Create puzzle definition
33
+ console.log('Creating 12x12 puzzle definition...\n');
34
+
35
+ const puzzleResult = PuzzleDefinitionFactory.create(STANDARD_CONFIGS.puzzle12x12, puzzle12x12Description);
36
+
37
+ if (puzzleResult.isSuccess()) {
38
+ const puzzle = puzzleResult.value;
39
+
40
+ console.log('✅ 12x12 Puzzle:');
41
+ console.log(` Description: ${puzzle.description}`);
42
+ console.log(` Grid: ${puzzle.totalRows}x${puzzle.totalColumns}`);
43
+ console.log(` Cage dimensions: ${puzzle.cageWidthInCells}x${puzzle.cageHeightInCells} (4 wide x 3 high)`);
44
+ console.log(` Board dimensions: ${puzzle.boardWidthInCages}x${puzzle.boardHeightInCages} cages`);
45
+ console.log(` Max value: ${puzzle.maxValue}`);
46
+ console.log(` Cage total: ${puzzle.basicCageTotal} (sum 1+2+...+12 = 78)`);
47
+ console.log(` Cells: ${puzzle.cells}`);
48
+ console.log('\n Grid layout:');
49
+
50
+ for (let row = 0; row < 12; row++) {
51
+ const rowCells = puzzle.cells.slice(row * 12, (row + 1) * 12).split('');
52
+ const displayRow = rowCells.map((c) => (c === '.' ? ' ' : c)).join(' ');
53
+ console.log(` ${displayRow}`);
54
+
55
+ // Show cage boundaries after rows 2, 5, 8
56
+ if (row === 2 || row === 5 || row === 8) {
57
+ console.log(' -----------------------------------------------'); // Horizontal cage separator
58
+ }
59
+ }
60
+
61
+ console.log('\n Cage structure (4x3 sections):');
62
+ console.log(' Each cage is 4 cells wide × 3 cells high');
63
+ console.log(' Grid has 3×4 = 12 total cages');
64
+ console.log(' Values: 1,2,3,4,5,6,7,8,9,A(10),B(11),C(12)');
65
+
66
+ console.log('\n🎯 12x12 puzzle definition created successfully!');
67
+ console.log('This puzzle uses 4x3 cage dimensions with 3x4 cage layout.');
68
+ console.log('Each cage must contain all values 1-12 (represented as 1-9,A,B,C).');
69
+ } else {
70
+ console.error('❌ Failed to create 12x12 puzzle definition:');
71
+ console.error(` Error: ${puzzleResult.message}`);
72
+ }
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Sample 4x4 puzzles generated from screenshot
4
+ const { PuzzleDefinitionFactory, STANDARD_CONFIGS } = require('./lib/packlets/common');
5
+
6
+ console.log('🧩 Sample 4x4 Sudoku Puzzles\n');
7
+
8
+ // First 4x4 puzzle from screenshot
9
+ const puzzle1Description = {
10
+ description: 'Sample 4x4 Sudoku #1',
11
+ type: 'sudoku',
12
+ level: 1,
13
+ cells: '..1....2.2....4.'
14
+ };
15
+
16
+ // Second 4x4 puzzle from screenshot
17
+ const puzzle2Description = {
18
+ description: 'Sample 4x4 Sudoku #2',
19
+ type: 'sudoku',
20
+ level: 1,
21
+ cells: '3..1..2.4.......'
22
+ };
23
+
24
+ // Create puzzle definitions
25
+ console.log('Creating puzzle definitions...\n');
26
+
27
+ const puzzle1Result = PuzzleDefinitionFactory.create(STANDARD_CONFIGS.puzzle4x4, puzzle1Description);
28
+ const puzzle2Result = PuzzleDefinitionFactory.create(STANDARD_CONFIGS.puzzle4x4, puzzle2Description);
29
+
30
+ if (puzzle1Result.isSuccess() && puzzle2Result.isSuccess()) {
31
+ const puzzle1 = puzzle1Result.value;
32
+ const puzzle2 = puzzle2Result.value;
33
+
34
+ console.log('✅ Puzzle #1:');
35
+ console.log(` Description: ${puzzle1.description}`);
36
+ console.log(` Grid: ${puzzle1.totalRows}x${puzzle1.totalColumns}`);
37
+ console.log(` Cage dimensions: ${puzzle1.cageWidthInCells}x${puzzle1.cageHeightInCells}`);
38
+ console.log(` Max value: ${puzzle1.maxValue}`);
39
+ console.log(` Cells: ${puzzle1.cells}`);
40
+ console.log(' Grid layout:');
41
+ for (let row = 0; row < 4; row++) {
42
+ const rowCells = puzzle1.cells.slice(row * 4, (row + 1) * 4).split('');
43
+ console.log(` ${rowCells.map((c) => (c === '.' ? ' ' : c)).join(' ')}`);
44
+ }
45
+
46
+ console.log('\n✅ Puzzle #2:');
47
+ console.log(` Description: ${puzzle2.description}`);
48
+ console.log(` Grid: ${puzzle2.totalRows}x${puzzle2.totalColumns}`);
49
+ console.log(` Cage dimensions: ${puzzle2.cageWidthInCells}x${puzzle2.cageHeightInCells}`);
50
+ console.log(` Max value: ${puzzle2.maxValue}`);
51
+ console.log(` Cells: ${puzzle2.cells}`);
52
+ console.log(' Grid layout:');
53
+ for (let row = 0; row < 4; row++) {
54
+ const rowCells = puzzle2.cells.slice(row * 4, (row + 1) * 4).split('');
55
+ console.log(` ${rowCells.map((c) => (c === '.' ? ' ' : c)).join(' ')}`);
56
+ }
57
+
58
+ console.log('\n🎯 Both 4x4 puzzle definitions created successfully!');
59
+ console.log('These can be used with the Sudoku puzzle engine.');
60
+ } else {
61
+ console.error('❌ Failed to create puzzle definitions:');
62
+ if (puzzle1Result.isFailure()) {
63
+ console.error(` Puzzle #1: ${puzzle1Result.message}`);
64
+ }
65
+ if (puzzle2Result.isFailure()) {
66
+ console.error(` Puzzle #2: ${puzzle2Result.message}`);
67
+ }
68
+ }
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Sample 6x6 puzzle generated from screenshot
4
+ const { PuzzleDefinitionFactory, STANDARD_CONFIGS } = require('./lib/packlets/common');
5
+
6
+ console.log('🧩 Sample 6x6 Sudoku Puzzle\n');
7
+
8
+ // 6x6 puzzle from screenshot
9
+ // Row by row: 62..5.3, ......, 5....3, ..6..2, ....346, 3.6...
10
+ const puzzle6x6Description = {
11
+ description: 'Sample 6x6 Sudoku #1',
12
+ type: 'sudoku',
13
+ level: 2,
14
+ cells: '62..533....5....3..6..2....3463.6...'
15
+ };
16
+
17
+ // Create puzzle definition
18
+ console.log('Creating 6x6 puzzle definition...\n');
19
+
20
+ const puzzleResult = PuzzleDefinitionFactory.create(STANDARD_CONFIGS.puzzle6x6, puzzle6x6Description);
21
+
22
+ if (puzzleResult.isSuccess()) {
23
+ const puzzle = puzzleResult.value;
24
+
25
+ console.log('✅ 6x6 Puzzle:');
26
+ console.log(` Description: ${puzzle.description}`);
27
+ console.log(` Grid: ${puzzle.totalRows}x${puzzle.totalColumns}`);
28
+ console.log(` Cage dimensions: ${puzzle.cageWidthInCells}x${puzzle.cageHeightInCells} (3 wide x 2 high)`);
29
+ console.log(` Board dimensions: ${puzzle.boardWidthInCages}x${puzzle.boardHeightInCages} cages`);
30
+ console.log(` Max value: ${puzzle.maxValue}`);
31
+ console.log(` Cage total: ${puzzle.basicCageTotal} (sum 1+2+3+4+5+6)`);
32
+ console.log(` Cells: ${puzzle.cells}`);
33
+ console.log(' Grid layout:');
34
+
35
+ for (let row = 0; row < 6; row++) {
36
+ const rowCells = puzzle.cells.slice(row * 6, (row + 1) * 6).split('');
37
+ const displayRow = rowCells.map((c) => (c === '.' ? ' ' : c)).join(' ');
38
+ console.log(` ${displayRow}`);
39
+
40
+ // Show cage boundaries after rows 1, 3, 5
41
+ if (row === 1 || row === 3) {
42
+ console.log(' -----------'); // Horizontal cage separator
43
+ }
44
+ }
45
+
46
+ console.log('\n Cage structure (3x2 sections):');
47
+ console.log(' Top left | Top right');
48
+ console.log(' (rows 0-1) | (rows 0-1)');
49
+ console.log(' -----------');
50
+ console.log(' Mid left | Mid right');
51
+ console.log(' (rows 2-3) | (rows 2-3)');
52
+ console.log(' -----------');
53
+ console.log(' Bot left | Bot right');
54
+ console.log(' (rows 4-5) | (rows 4-5)');
55
+
56
+ console.log('\n🎯 6x6 puzzle definition created successfully!');
57
+ console.log('This puzzle uses 3x2 cage dimensions with 2x3 cage layout.');
58
+ } else {
59
+ console.error('❌ Failed to create 6x6 puzzle definition:');
60
+ console.error(` Error: ${puzzleResult.message}`);
61
+ }
@@ -0,0 +1,12 @@
1
+ Row 0: ..1.6.2.A7..
2
+ Row 1: 62.5B....1..
3
+ Row 2: .8....C3B.62
4
+ Row 3: A97.2.6..B5.
5
+ Row 4: .5......37C
6
+ Row 5: 3B....7...9
7
+ Row 6: 57B.4.....1
8
+ Row 7: 8.....A.9..
9
+ Row 8: .4..97...6..
10
+ Row 9: .1C.......B
11
+ Row 10: 4....2.C...
12
+ Row 11: ....5C..7126