@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
|
@@ -0,0 +1,499 @@
|
|
|
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.PuzzleSessionHints = void 0;
|
|
27
|
+
const ts_utils_1 = require("@fgv/ts-utils");
|
|
28
|
+
const common_1 = require("../common");
|
|
29
|
+
const hints_1 = require("./hints");
|
|
30
|
+
/**
|
|
31
|
+
* Wrapper class that integrates hint functionality with PuzzleSession.
|
|
32
|
+
* Provides hint generation, application, and explanation capabilities while
|
|
33
|
+
* maintaining integration with existing state management and undo/redo functionality.
|
|
34
|
+
* @public
|
|
35
|
+
*/
|
|
36
|
+
class PuzzleSessionHints {
|
|
37
|
+
/**
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
40
|
+
constructor(session, hintSystem, config) {
|
|
41
|
+
this._session = session;
|
|
42
|
+
this._hintSystem = hintSystem;
|
|
43
|
+
this._config = config;
|
|
44
|
+
this._hintCache = undefined;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Creates a new PuzzleSessionHints wrapper for an existing PuzzleSession.
|
|
48
|
+
* @param session - The PuzzleSession to wrap
|
|
49
|
+
* @param config - Optional configuration for the hint system
|
|
50
|
+
* @returns Result containing the new PuzzleSessionHints wrapper
|
|
51
|
+
*/
|
|
52
|
+
static create(session, config) {
|
|
53
|
+
const finalConfig = Object.assign({ enableNakedSingles: true, enableHiddenSingles: true, defaultExplanationLevel: 'detailed', cacheTimeoutMs: 5000, maxCacheEntries: 1 }, config);
|
|
54
|
+
return hints_1.HintSystem.create(finalConfig).onSuccess((hintSystem) => {
|
|
55
|
+
return (0, ts_utils_1.succeed)(new PuzzleSessionHints(session, hintSystem, finalConfig));
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Gets the wrapped PuzzleSession instance.
|
|
60
|
+
* @returns The underlying PuzzleSession
|
|
61
|
+
*/
|
|
62
|
+
get session() {
|
|
63
|
+
return this._session;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Gets the HintSystem instance.
|
|
67
|
+
* @returns The hint system
|
|
68
|
+
*/
|
|
69
|
+
get hintSystem() {
|
|
70
|
+
return this._hintSystem;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Gets the configuration.
|
|
74
|
+
* @returns The configuration
|
|
75
|
+
*/
|
|
76
|
+
get config() {
|
|
77
|
+
return this._config;
|
|
78
|
+
}
|
|
79
|
+
// Delegate all PuzzleSession methods for transparent access
|
|
80
|
+
/**
|
|
81
|
+
* Gets the puzzle ID.
|
|
82
|
+
* @returns The puzzle ID
|
|
83
|
+
*/
|
|
84
|
+
get id() {
|
|
85
|
+
return this._session.id;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Gets the puzzle description.
|
|
89
|
+
* @returns The puzzle description
|
|
90
|
+
*/
|
|
91
|
+
get description() {
|
|
92
|
+
return this._session.description;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Gets the number of rows in the puzzle.
|
|
96
|
+
* @returns The number of rows
|
|
97
|
+
*/
|
|
98
|
+
get numRows() {
|
|
99
|
+
return this._session.numRows;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Gets the number of columns in the puzzle.
|
|
103
|
+
* @returns The number of columns
|
|
104
|
+
*/
|
|
105
|
+
get numColumns() {
|
|
106
|
+
return this._session.numColumns;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Gets the current puzzle state.
|
|
110
|
+
* @returns The current state
|
|
111
|
+
*/
|
|
112
|
+
get state() {
|
|
113
|
+
return this._session.state;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Gets whether undo is possible.
|
|
117
|
+
* @returns true if undo is possible
|
|
118
|
+
*/
|
|
119
|
+
get canUndo() {
|
|
120
|
+
return this._session.canUndo;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Gets whether redo is possible.
|
|
124
|
+
* @returns true if redo is possible
|
|
125
|
+
*/
|
|
126
|
+
get canRedo() {
|
|
127
|
+
return this._session.canRedo;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Checks if the puzzle is solved.
|
|
131
|
+
* @returns true if the puzzle is solved
|
|
132
|
+
*/
|
|
133
|
+
checkIsSolved() {
|
|
134
|
+
return this._session.checkIsSolved();
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Checks if the puzzle is valid.
|
|
138
|
+
* @returns true if the puzzle is valid
|
|
139
|
+
*/
|
|
140
|
+
checkIsValid() {
|
|
141
|
+
return this._session.checkIsValid();
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Updates a cell value.
|
|
145
|
+
* @param spec - Cell specification
|
|
146
|
+
* @param value - New value
|
|
147
|
+
* @returns Result with this instance
|
|
148
|
+
*/
|
|
149
|
+
updateCellValue(spec, value) {
|
|
150
|
+
return this._session.updateCellValue(spec, value).onSuccess(() => {
|
|
151
|
+
this._invalidateCache();
|
|
152
|
+
return (0, ts_utils_1.succeed)(this);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Updates cell notes.
|
|
157
|
+
* @param spec - Cell specification
|
|
158
|
+
* @param notes - New notes
|
|
159
|
+
* @returns Result with this instance
|
|
160
|
+
*/
|
|
161
|
+
updateCellNotes(spec, notes) {
|
|
162
|
+
return this._session.updateCellNotes(spec, notes).onSuccess(() => {
|
|
163
|
+
this._invalidateCache();
|
|
164
|
+
return (0, ts_utils_1.succeed)(this);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Updates multiple cells.
|
|
169
|
+
* @param updates - Array of cell updates
|
|
170
|
+
* @returns Result with this instance
|
|
171
|
+
*/
|
|
172
|
+
updateCells(updates) {
|
|
173
|
+
return this._session.updateCells(updates).onSuccess(() => {
|
|
174
|
+
this._invalidateCache();
|
|
175
|
+
return (0, ts_utils_1.succeed)(this);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Performs an undo operation.
|
|
180
|
+
* @returns Result with this instance
|
|
181
|
+
*/
|
|
182
|
+
undo() {
|
|
183
|
+
return this._session.undo().onSuccess(() => {
|
|
184
|
+
this._invalidateCache();
|
|
185
|
+
return (0, ts_utils_1.succeed)(this);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Performs a redo operation.
|
|
190
|
+
* @returns Result with this instance
|
|
191
|
+
*/
|
|
192
|
+
redo() {
|
|
193
|
+
return this._session.redo().onSuccess(() => {
|
|
194
|
+
this._invalidateCache();
|
|
195
|
+
return (0, ts_utils_1.succeed)(this);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
// Hint-specific functionality
|
|
199
|
+
/**
|
|
200
|
+
* Gets the best available hint for the current puzzle state.
|
|
201
|
+
* @param options - Optional hint generation options
|
|
202
|
+
* @returns Result containing the best hint
|
|
203
|
+
*/
|
|
204
|
+
getHint(options) {
|
|
205
|
+
return this._hintSystem.getBestHint(this._session.puzzle, this._session.state, options);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Gets all available hints for the current puzzle state.
|
|
209
|
+
* @param options - Optional hint generation options
|
|
210
|
+
* @returns Result containing array of hints
|
|
211
|
+
*/
|
|
212
|
+
getAllHints(options) {
|
|
213
|
+
// Check cache first
|
|
214
|
+
const cachedHints = this._getCachedHints(options);
|
|
215
|
+
if (cachedHints) {
|
|
216
|
+
return (0, ts_utils_1.succeed)(cachedHints);
|
|
217
|
+
}
|
|
218
|
+
// Generate new hints
|
|
219
|
+
return this._hintSystem
|
|
220
|
+
.generateHints(this._session.puzzle, this._session.state, options)
|
|
221
|
+
.onSuccess((hints) => {
|
|
222
|
+
this._updateCache(hints, options);
|
|
223
|
+
return (0, ts_utils_1.succeed)(hints);
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Applies a hint to the puzzle, updating the state and adding to undo history.
|
|
228
|
+
* @param hint - The hint to apply
|
|
229
|
+
* @returns Result with this instance if successful
|
|
230
|
+
*/
|
|
231
|
+
applyHint(hint) {
|
|
232
|
+
return this._hintSystem
|
|
233
|
+
.applyHint(hint, this._session.puzzle, this._session.state)
|
|
234
|
+
.onSuccess((updates) => {
|
|
235
|
+
// Convert readonly array to mutable array for PuzzleSession.updateCells
|
|
236
|
+
const mutableUpdates = [...updates];
|
|
237
|
+
return this._session.updateCells(mutableUpdates).onSuccess(() => {
|
|
238
|
+
this._invalidateCache();
|
|
239
|
+
return (0, ts_utils_1.succeed)(this);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Gets hints that specifically affect a given cell.
|
|
245
|
+
* @param spec - Cell specification (ID, row/column, or cell object)
|
|
246
|
+
* @param options - Optional hint generation options
|
|
247
|
+
* @returns Result containing hints affecting the specified cell
|
|
248
|
+
*/
|
|
249
|
+
getHintsForCell(spec, options) {
|
|
250
|
+
const cellIdResult = common_1.Ids.cellId(spec);
|
|
251
|
+
return cellIdResult.onSuccess((cellId) => {
|
|
252
|
+
return this.getAllHints(options).onSuccess((allHints) => {
|
|
253
|
+
const relevantHints = allHints.filter((hint) => {
|
|
254
|
+
// Check if the hint affects this specific cell
|
|
255
|
+
return (hint.cellActions.some((action) => action.cellId === cellId) ||
|
|
256
|
+
hint.relevantCells.primary.includes(cellId) ||
|
|
257
|
+
hint.relevantCells.secondary.includes(cellId) ||
|
|
258
|
+
hint.relevantCells.affected.includes(cellId));
|
|
259
|
+
});
|
|
260
|
+
return (0, ts_utils_1.succeed)(relevantHints);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Gets a formatted explanation for a hint.
|
|
266
|
+
* @param hint - The hint to explain
|
|
267
|
+
* @param level - The explanation level (defaults to configured default)
|
|
268
|
+
* @returns Formatted explanation string
|
|
269
|
+
*/
|
|
270
|
+
getExplanation(hint, level) {
|
|
271
|
+
return this._hintSystem.formatHintExplanation(hint, level);
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Validates that a hint can be applied to the current state.
|
|
275
|
+
* @param hint - The hint to validate
|
|
276
|
+
* @returns Result indicating validation success or failure
|
|
277
|
+
*/
|
|
278
|
+
validateHint(hint) {
|
|
279
|
+
return this._hintSystem.validateHint(hint, this._session.puzzle, this._session.state);
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Checks if hints are available for the current state.
|
|
283
|
+
* @param options - Optional hint generation options
|
|
284
|
+
* @returns Result containing boolean indicating availability
|
|
285
|
+
*/
|
|
286
|
+
hasHints(options) {
|
|
287
|
+
return this._hintSystem.hasHints(this._session.puzzle, this._session.state);
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Gets statistics about available hints.
|
|
291
|
+
* @param options - Optional hint generation options
|
|
292
|
+
* @returns Result containing hint statistics
|
|
293
|
+
*/
|
|
294
|
+
getHintStatistics(options) {
|
|
295
|
+
return this._hintSystem.getHintStatistics(this._session.puzzle, this._session.state);
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Gets a summary of the hint system capabilities.
|
|
299
|
+
* @returns System capabilities summary
|
|
300
|
+
*/
|
|
301
|
+
getSystemSummary() {
|
|
302
|
+
return this._hintSystem.getSystemSummary();
|
|
303
|
+
}
|
|
304
|
+
// Delegate remaining PuzzleSession methods for complete transparency
|
|
305
|
+
/**
|
|
306
|
+
* Gets the rows.
|
|
307
|
+
* @returns Array of row cages
|
|
308
|
+
*/
|
|
309
|
+
get rows() {
|
|
310
|
+
return this._session.rows;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Gets the columns.
|
|
314
|
+
* @returns Array of column cages
|
|
315
|
+
*/
|
|
316
|
+
get cols() {
|
|
317
|
+
return this._session.cols;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Gets the sections.
|
|
321
|
+
* @returns Array of section cages
|
|
322
|
+
*/
|
|
323
|
+
get sections() {
|
|
324
|
+
return this._session.sections;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Gets all cages.
|
|
328
|
+
* @returns Array of all cages
|
|
329
|
+
*/
|
|
330
|
+
get cages() {
|
|
331
|
+
return this._session.cages;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Gets all cells.
|
|
335
|
+
* @returns Array of all cells
|
|
336
|
+
*/
|
|
337
|
+
get cells() {
|
|
338
|
+
return this._session.cells;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Gets the next step index.
|
|
342
|
+
* @returns Next step index
|
|
343
|
+
*/
|
|
344
|
+
get nextStep() {
|
|
345
|
+
return this._session.nextStep;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Gets the number of steps.
|
|
349
|
+
* @returns Number of steps
|
|
350
|
+
*/
|
|
351
|
+
get numSteps() {
|
|
352
|
+
return this._session.numSteps;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Gets empty cells.
|
|
356
|
+
* @returns Array of empty cells
|
|
357
|
+
*/
|
|
358
|
+
getEmptyCells() {
|
|
359
|
+
return this._session.getEmptyCells();
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Gets invalid cells.
|
|
363
|
+
* @returns Array of invalid cells
|
|
364
|
+
*/
|
|
365
|
+
getInvalidCells() {
|
|
366
|
+
return this._session.getInvalidCells();
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Checks if a cell is valid.
|
|
370
|
+
* @param spec - Cell specification
|
|
371
|
+
* @returns true if valid
|
|
372
|
+
*/
|
|
373
|
+
cellIsValid(spec) {
|
|
374
|
+
return this._session.cellIsValid(spec);
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Checks if a cell has a value.
|
|
378
|
+
* @param spec - Cell specification
|
|
379
|
+
* @returns true if cell has value
|
|
380
|
+
*/
|
|
381
|
+
cellHasValue(spec) {
|
|
382
|
+
return this._session.cellHasValue(spec);
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Checks if a value is valid for a cell.
|
|
386
|
+
* @param spec - Cell specification
|
|
387
|
+
* @param value - Value to check
|
|
388
|
+
* @returns true if valid
|
|
389
|
+
*/
|
|
390
|
+
isValidForCell(spec, value) {
|
|
391
|
+
return this._session.isValidForCell(spec, value);
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Gets a cell neighbor.
|
|
395
|
+
* @param spec - Cell specification
|
|
396
|
+
* @param direction - Navigation direction
|
|
397
|
+
* @param wrap - Wrap behavior
|
|
398
|
+
* @returns Result containing neighbor cell
|
|
399
|
+
*/
|
|
400
|
+
getCellNeighbor(spec, direction, wrap) {
|
|
401
|
+
return this._session.getCellNeighbor(spec, direction, wrap);
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Gets cell contents.
|
|
405
|
+
* @param spec - Cell specification
|
|
406
|
+
* @returns Result containing cell and contents
|
|
407
|
+
*/
|
|
408
|
+
getCellContents(spec) {
|
|
409
|
+
return this._session.getCellContents(spec);
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Checks if a cage contains a value.
|
|
413
|
+
* @param spec - Cage specification
|
|
414
|
+
* @param value - Value to check
|
|
415
|
+
* @returns true if cage contains value
|
|
416
|
+
*/
|
|
417
|
+
cageContainsValue(spec, value) {
|
|
418
|
+
return this._session.cageContainsValue(spec, value);
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Gets contained values in a cage.
|
|
422
|
+
* @param spec - Cage specification
|
|
423
|
+
* @returns Set of contained values
|
|
424
|
+
*/
|
|
425
|
+
cageContainedValues(spec) {
|
|
426
|
+
return this._session.cageContainedValues(spec);
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Gets string representation of the puzzle.
|
|
430
|
+
* @returns Array of strings representing puzzle rows
|
|
431
|
+
*/
|
|
432
|
+
toStrings() {
|
|
433
|
+
return this._session.toStrings();
|
|
434
|
+
}
|
|
435
|
+
// Private cache management methods
|
|
436
|
+
/**
|
|
437
|
+
* Generates a cache key for the current state and options.
|
|
438
|
+
* @param options - Hint generation options
|
|
439
|
+
* @returns Cache key
|
|
440
|
+
*/
|
|
441
|
+
_generateCacheKey(options) {
|
|
442
|
+
// Simple state hash based on cell values and notes
|
|
443
|
+
const stateString = this._session.toStrings().join('|');
|
|
444
|
+
const notesString = this._session.cells
|
|
445
|
+
.map((cell) => {
|
|
446
|
+
const contents = this._session
|
|
447
|
+
.getCellContents(cell.id)
|
|
448
|
+
.orDefault({ cell, contents: { value: undefined, notes: [] } });
|
|
449
|
+
return `${cell.id}:${contents.contents.notes.join(',')}`;
|
|
450
|
+
})
|
|
451
|
+
.join('|');
|
|
452
|
+
const stateHash = `${stateString}#${notesString}`;
|
|
453
|
+
const optionsString = options ? JSON.stringify(options) : '';
|
|
454
|
+
return { stateHash, options: optionsString };
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Gets cached hints if valid.
|
|
458
|
+
* @param options - Hint generation options
|
|
459
|
+
* @returns Cached hints or undefined
|
|
460
|
+
*/
|
|
461
|
+
_getCachedHints(options) {
|
|
462
|
+
var _a;
|
|
463
|
+
if (!this._hintCache) {
|
|
464
|
+
return undefined;
|
|
465
|
+
}
|
|
466
|
+
const cacheKey = this._generateCacheKey(options);
|
|
467
|
+
/* c8 ignore next 1 - ?? is defense in depth */
|
|
468
|
+
const timeoutMs = (_a = this._config.cacheTimeoutMs) !== null && _a !== void 0 ? _a : 5000;
|
|
469
|
+
const now = Date.now();
|
|
470
|
+
// Check if cache is still valid
|
|
471
|
+
if (this._hintCache.cacheKey.stateHash === cacheKey.stateHash &&
|
|
472
|
+
this._hintCache.cacheKey.options === cacheKey.options &&
|
|
473
|
+
now - this._hintCache.generatedAt < timeoutMs) {
|
|
474
|
+
return this._hintCache.hints;
|
|
475
|
+
}
|
|
476
|
+
return undefined;
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Updates the cache with new hints.
|
|
480
|
+
* @param hints - Generated hints
|
|
481
|
+
* @param options - Generation options
|
|
482
|
+
*/
|
|
483
|
+
_updateCache(hints, options) {
|
|
484
|
+
const cacheKey = this._generateCacheKey(options);
|
|
485
|
+
this._hintCache = {
|
|
486
|
+
hints,
|
|
487
|
+
generatedAt: Date.now(),
|
|
488
|
+
cacheKey
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Invalidates the current hint cache.
|
|
493
|
+
*/
|
|
494
|
+
_invalidateCache() {
|
|
495
|
+
this._hintCache = undefined;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
exports.PuzzleSessionHints = PuzzleSessionHints;
|
|
499
|
+
//# sourceMappingURL=puzzleSessionHints.js.map
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Brand } from '@fgv/ts-utils';
|
|
2
|
+
import { CellId } from '../common';
|
|
3
|
+
/**
|
|
4
|
+
* Nominal identifier for a hint generation technique.
|
|
5
|
+
* @public
|
|
6
|
+
*/
|
|
7
|
+
export type TechniqueId = Brand<string, 'TechniqueId'>;
|
|
8
|
+
/**
|
|
9
|
+
* Confidence level for a generated hint, ranging from 1 (low) to 5 (high).
|
|
10
|
+
* @public
|
|
11
|
+
*/
|
|
12
|
+
export type ConfidenceLevel = Brand<1 | 2 | 3 | 4 | 5, 'ConfidenceLevel'>;
|
|
13
|
+
/**
|
|
14
|
+
* The type of action that should be taken on a cell as part of a hint.
|
|
15
|
+
* @public
|
|
16
|
+
*/
|
|
17
|
+
export type CellAction = 'set-value' | 'eliminate-candidate' | 'add-candidate' | 'highlight';
|
|
18
|
+
/**
|
|
19
|
+
* The level of detail for hint explanations.
|
|
20
|
+
* @public
|
|
21
|
+
*/
|
|
22
|
+
export type ExplanationLevel = 'brief' | 'detailed' | 'educational';
|
|
23
|
+
/**
|
|
24
|
+
* Standard difficulty classifications for Sudoku techniques.
|
|
25
|
+
* @public
|
|
26
|
+
*/
|
|
27
|
+
export type DifficultyLevel = 'beginner' | 'intermediate' | 'advanced' | 'expert';
|
|
28
|
+
/**
|
|
29
|
+
* Describes an action to be taken on a specific cell as part of a hint.
|
|
30
|
+
* @public
|
|
31
|
+
*/
|
|
32
|
+
export interface ICellAction {
|
|
33
|
+
readonly cellId: CellId;
|
|
34
|
+
readonly action: CellAction;
|
|
35
|
+
readonly value?: number;
|
|
36
|
+
readonly reason?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Information about the cells that are relevant to understanding a hint.
|
|
40
|
+
* @public
|
|
41
|
+
*/
|
|
42
|
+
export interface IRelevantCells {
|
|
43
|
+
readonly primary: readonly CellId[];
|
|
44
|
+
readonly secondary: readonly CellId[];
|
|
45
|
+
readonly affected: readonly CellId[];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A hint explanation at a specific level of detail.
|
|
49
|
+
* @public
|
|
50
|
+
*/
|
|
51
|
+
export interface IHintExplanation {
|
|
52
|
+
readonly level: ExplanationLevel;
|
|
53
|
+
readonly title: string;
|
|
54
|
+
readonly description: string;
|
|
55
|
+
readonly steps?: readonly string[];
|
|
56
|
+
readonly tips?: readonly string[];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A complete hint with all necessary information for display and application.
|
|
60
|
+
* @public
|
|
61
|
+
*/
|
|
62
|
+
export interface IHint {
|
|
63
|
+
readonly techniqueId: TechniqueId;
|
|
64
|
+
readonly techniqueName: string;
|
|
65
|
+
readonly difficulty: DifficultyLevel;
|
|
66
|
+
readonly confidence: ConfidenceLevel;
|
|
67
|
+
readonly cellActions: readonly ICellAction[];
|
|
68
|
+
readonly relevantCells: IRelevantCells;
|
|
69
|
+
readonly explanations: readonly IHintExplanation[];
|
|
70
|
+
readonly priority: number;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Configuration options for hint generation.
|
|
74
|
+
* @public
|
|
75
|
+
*/
|
|
76
|
+
export interface IHintGenerationOptions {
|
|
77
|
+
readonly maxHints?: number;
|
|
78
|
+
readonly minConfidence?: ConfidenceLevel;
|
|
79
|
+
readonly preferredDifficulty?: DifficultyLevel;
|
|
80
|
+
readonly enabledTechniques?: readonly TechniqueId[];
|
|
81
|
+
readonly explanationLevel?: ExplanationLevel;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Standard technique identifiers for built-in solving techniques.
|
|
85
|
+
* @public
|
|
86
|
+
*/
|
|
87
|
+
export declare const TechniqueIds: {
|
|
88
|
+
readonly NAKED_SINGLES: TechniqueId;
|
|
89
|
+
readonly HIDDEN_SINGLES: TechniqueId;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Standard confidence levels as branded types.
|
|
93
|
+
* @public
|
|
94
|
+
*/
|
|
95
|
+
export declare const ConfidenceLevels: {
|
|
96
|
+
readonly LOW: ConfidenceLevel;
|
|
97
|
+
readonly MEDIUM_LOW: ConfidenceLevel;
|
|
98
|
+
readonly MEDIUM: ConfidenceLevel;
|
|
99
|
+
readonly MEDIUM_HIGH: ConfidenceLevel;
|
|
100
|
+
readonly HIGH: ConfidenceLevel;
|
|
101
|
+
};
|
|
102
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,46 @@
|
|
|
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.ConfidenceLevels = exports.TechniqueIds = void 0;
|
|
27
|
+
/**
|
|
28
|
+
* Standard technique identifiers for built-in solving techniques.
|
|
29
|
+
* @public
|
|
30
|
+
*/
|
|
31
|
+
exports.TechniqueIds = {
|
|
32
|
+
NAKED_SINGLES: 'naked-singles',
|
|
33
|
+
HIDDEN_SINGLES: 'hidden-singles'
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Standard confidence levels as branded types.
|
|
37
|
+
* @public
|
|
38
|
+
*/
|
|
39
|
+
exports.ConfidenceLevels = {
|
|
40
|
+
LOW: 1,
|
|
41
|
+
MEDIUM_LOW: 2,
|
|
42
|
+
MEDIUM: 3,
|
|
43
|
+
MEDIUM_HIGH: 4,
|
|
44
|
+
HIGH: 5
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Result } from '@fgv/ts-utils';
|
|
2
|
-
import {
|
|
2
|
+
import { IPuzzleDefinition, Puzzle } from '../common';
|
|
3
3
|
/**
|
|
4
|
-
* Static class to instantiate any puzzle from a {@link
|
|
4
|
+
* Static class to instantiate any puzzle from a {@link IPuzzleDefinition | puzzle definition}.
|
|
5
5
|
* @internal
|
|
6
6
|
*/
|
|
7
7
|
export declare class AnyPuzzle {
|
|
8
|
-
static create(puzzle:
|
|
8
|
+
static create(puzzle: IPuzzleDefinition): Result<Puzzle>;
|
|
9
9
|
}
|
|
10
10
|
//# sourceMappingURL=anyPuzzle.d.ts.map
|
|
@@ -29,7 +29,7 @@ const killerSudokuPuzzle_1 = require("./killerSudokuPuzzle");
|
|
|
29
29
|
const sudokuPuzzle_1 = require("./sudokuPuzzle");
|
|
30
30
|
const sudokuXPuzzle_1 = require("./sudokuXPuzzle");
|
|
31
31
|
/**
|
|
32
|
-
* Static class to instantiate any puzzle from a {@link
|
|
32
|
+
* Static class to instantiate any puzzle from a {@link IPuzzleDefinition | puzzle definition}.
|
|
33
33
|
* @internal
|
|
34
34
|
*/
|
|
35
35
|
class AnyPuzzle {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { AnyPuzzle as Any } from './anyPuzzle';
|
|
2
1
|
import { KillerSudokuPuzzle as Killer } from './killerSudokuPuzzle';
|
|
3
2
|
import { SudokuPuzzle as Sudoku } from './sudokuPuzzle';
|
|
4
3
|
import { SudokuXPuzzle as SudokuX } from './sudokuXPuzzle';
|
|
5
|
-
|
|
4
|
+
import { KillerCombinations } from './killerCombinations';
|
|
5
|
+
import { AnyPuzzle as Any } from './anyPuzzle';
|
|
6
|
+
export * from './killerCombinationsTypes';
|
|
7
|
+
export { Any, Killer, KillerCombinations, Sudoku, SudokuX };
|
|
6
8
|
//# sourceMappingURL=index.d.ts.map
|