@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
package/lib/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- import * as File from './packlets/file';
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 { File, Puzzles };
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.File = void 0;
63
- const File = __importStar(require("./packlets/file"));
64
- exports.File = File;
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 FileData from '../file';
1
+ import * as Files from '../files';
2
2
  import { Result } from '@fgv/ts-utils';
3
- import { IPuzzleDescription, PuzzleSession } from '../common';
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 IPuzzleDescription[];
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 File.Model.IPuzzlesFile | PuzzlesFile}
17
- * @param from - The {@link File.Model.IPuzzlesFile | puzzles file} to be loaded.
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: FileData.Model.IPuzzlesFile): Result<PuzzleCollection>;
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(path: string): Result<PuzzleCollection>;
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<IPuzzleDescription>;
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 FileData = __importStar(require("../file"));
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 File.Model.IPuzzlesFile | PuzzlesFile}
81
- * @param from - The {@link File.Model.IPuzzlesFile | puzzles file} to be loaded.
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(path) {
95
- return FileData.Converters.loadJsonPuzzlesFileSync(path).onSuccess(PuzzleCollection.create);
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 Puzzles.Any.create(desc);
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 desc = this._byId.get(id);
120
- if (!desc) {
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)(desc);
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 = FileData.Converters.puzzlesFile
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
- "rows": 9,
9
- "cols": 9,
10
- "cells": [
11
- ".........",
12
- "9.46.7...",
13
- ".768.41..",
14
- "3.97.1.8.",
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
- "rows": 9,
28
- "cols": 9,
29
- "cells": ".46728539837549126259316748685297413312684957794153862428965371571832694963471285"
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
- "rows": 9,
37
- "cols": 9,
38
- "cells": "4.....13....6.1.....7..29...76.....2....3..9.9.1....577...1.6..3...5.7...4......1"
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
- "rows": 9,
46
- "cols": 9,
47
- "cells": [
48
- "ABCCCDDDE",
49
- "ABFFGGGDE",
50
- "HIJKGGLLL",
51
- "HIJKMGLNN",
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.puzzleDescription = exports.puzzleType = exports.cellId = exports.cageId = void 0;
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
- const cageIdRegExp = /^(R[A-Z]$)|(C[0-9]$)|(S[A-Z][0-9]$)|(X[1-2]$)|(K[A-Za-z]$)/;
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}" (expected "R[A-Z]", "C[0-9]" or "S[A-Z][0-9]")`);
40
+ return (0, ts_utils_1.fail)(`malformed cage ID "${from}"`);
35
41
  }
36
- const cellIdRegExp = /^[A-Z][0-9]$/;
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
- if (cellIdRegExp.test(from)) {
39
- return (0, ts_utils_1.succeed)(from);
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.fail)(`malformed cell ID "${from}" (expected "[A-Z][0-9]")`);
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
- const firstColIdChar = '1'.charCodeAt(0);
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
- const row = String.fromCharCode(firstRowIdChar + spec.row);
83
- const col = String.fromCharCode(firstColIdChar + spec.col);
84
- return (0, ts_utils_1.succeed)(`${row}${col}`);
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${String.fromCharCode(firstRowIdChar + row)}`;
157
+ return `R${columnToLetters(row)}`;
90
158
  }
91
159
  static columnCageId(col) {
92
- return `C${String.fromCharCode(firstColIdChar + col)}`;
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 / 3) * 3;
96
- col = Math.floor(col / 3) * 3;
97
- return `S${String.fromCharCode(firstRowIdChar + row)}${String.fromCharCode(firstColIdChar + col)}`;
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 * from './model';
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
- __exportStar(require("./model"), exports);
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,7 @@
1
+ import { Logging } from '@fgv/ts-utils';
2
+ /**
3
+ * Default no-op logger for use when diagnostic logging is not needed.
4
+ * @public
5
+ */
6
+ export declare const DefaultSudokuLogger: Logging.LogReporter<unknown>;
7
+ //# sourceMappingURL=logging.d.ts.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