@fgv/ts-sudoku-lib 5.0.1-9 → 5.0.2-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/index.browser.js +37 -0
  2. package/dist/index.js +30 -0
  3. package/dist/packlets/collections/collections.js +136 -0
  4. package/dist/packlets/collections/data/puzzles.json +56 -0
  5. package/dist/packlets/collections/index.js +25 -0
  6. package/dist/packlets/common/cage.js +83 -0
  7. package/dist/packlets/common/cell.js +88 -0
  8. package/dist/packlets/common/common.js +70 -0
  9. package/dist/packlets/common/converters.js +65 -0
  10. package/dist/packlets/common/ids.js +152 -0
  11. package/dist/packlets/common/index.js +36 -0
  12. package/dist/packlets/common/logging.js +32 -0
  13. package/dist/packlets/common/public.js +25 -0
  14. package/dist/packlets/common/puzzle.js +411 -0
  15. package/dist/packlets/common/puzzleDefinitions.js +247 -0
  16. package/dist/packlets/common/puzzleSession.js +369 -0
  17. package/dist/packlets/common/puzzleState.js +99 -0
  18. package/dist/packlets/files/converters.js +67 -0
  19. package/dist/packlets/files/fileTreeHelpers.js +37 -0
  20. package/dist/packlets/files/filesystem.js +37 -0
  21. package/dist/packlets/files/index.browser.js +32 -0
  22. package/dist/packlets/files/index.js +30 -0
  23. package/dist/packlets/files/model.js +25 -0
  24. package/dist/packlets/hints/baseHintProvider.js +199 -0
  25. package/dist/packlets/hints/explanations.js +270 -0
  26. package/dist/packlets/hints/hiddenSingles.js +233 -0
  27. package/dist/packlets/hints/hintRegistry.js +222 -0
  28. package/dist/packlets/hints/hints.js +311 -0
  29. package/dist/packlets/hints/index.js +39 -0
  30. package/dist/packlets/hints/interfaces.js +25 -0
  31. package/dist/packlets/hints/nakedSingles.js +198 -0
  32. package/dist/packlets/hints/puzzleSessionHints.js +495 -0
  33. package/dist/packlets/hints/types.js +43 -0
  34. package/dist/packlets/puzzles/anyPuzzle.js +47 -0
  35. package/dist/packlets/puzzles/index.js +31 -0
  36. package/dist/packlets/puzzles/internal/combinationCache.js +75 -0
  37. package/dist/packlets/puzzles/internal/combinationGenerator.js +142 -0
  38. package/dist/packlets/puzzles/internal/possibilityAnalyzer.js +121 -0
  39. package/dist/packlets/puzzles/killerCombinations.js +222 -0
  40. package/dist/packlets/puzzles/killerCombinationsTypes.js +25 -0
  41. package/dist/packlets/puzzles/killerSudokuPuzzle.js +136 -0
  42. package/dist/packlets/puzzles/sudokuPuzzle.js +43 -0
  43. package/dist/packlets/puzzles/sudokuXPuzzle.js +65 -0
  44. package/dist/test/unit/helpers/puzzleBuilders.js +149 -0
  45. package/dist/ts-sudoku-lib.d.ts +12 -7
  46. package/dist/tsdoc-metadata.json +1 -1
  47. package/lib/index.browser.d.ts +7 -0
  48. package/lib/index.browser.js +78 -0
  49. package/lib/packlets/files/fileTreeHelpers.d.ts +13 -0
  50. package/lib/packlets/files/fileTreeHelpers.js +40 -0
  51. package/lib/packlets/files/index.browser.d.ts +5 -0
  52. package/lib/packlets/files/index.browser.js +70 -0
  53. package/lib/packlets/files/index.d.ts +3 -2
  54. package/lib/packlets/files/index.js +7 -4
  55. package/package.json +23 -5
@@ -0,0 +1,222 @@
1
+ /*
2
+ * MIT License
3
+ *
4
+ * Copyright (c) 2023 Erik Fortune
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+ import { fail, succeed, mapResults } from '@fgv/ts-utils';
25
+ /**
26
+ * Implementation of the hint registry that manages and coordinates multiple hint providers.
27
+ * @public
28
+ */
29
+ export class HintRegistry {
30
+ /**
31
+ * Creates a new HintRegistry instance.
32
+ */
33
+ constructor() {
34
+ this._providers = new Map();
35
+ }
36
+ /**
37
+ * Creates a new HintRegistry with the specified providers pre-registered.
38
+ * @param providers - The providers to register
39
+ * @returns Result containing the new registry or failure if registration fails
40
+ */
41
+ static create(providers) {
42
+ const registry = new HintRegistry();
43
+ if (providers && providers.length > 0) {
44
+ for (const provider of providers) {
45
+ const result = registry.registerProvider(provider);
46
+ if (result.isFailure()) {
47
+ return fail(`Failed to register provider ${provider.techniqueId}: ${result.message}`);
48
+ }
49
+ }
50
+ }
51
+ return succeed(registry);
52
+ }
53
+ /**
54
+ * Registers a new hint provider.
55
+ * @param provider - The provider to register
56
+ * @returns Result indicating success or failure of registration
57
+ */
58
+ registerProvider(provider) {
59
+ if (this._providers.has(provider.techniqueId)) {
60
+ return fail(`Provider for technique ${provider.techniqueId} is already registered`);
61
+ }
62
+ this._providers.set(provider.techniqueId, provider);
63
+ return succeed(undefined);
64
+ }
65
+ /**
66
+ * Unregisters a hint provider.
67
+ * @param techniqueId - The ID of the technique to unregister
68
+ * @returns Result indicating success or failure of unregistration
69
+ */
70
+ unregisterProvider(techniqueId) {
71
+ if (!this._providers.has(techniqueId)) {
72
+ return fail(`No provider registered for technique ${techniqueId}`);
73
+ }
74
+ this._providers.delete(techniqueId);
75
+ return succeed(undefined);
76
+ }
77
+ /**
78
+ * Gets a specific provider by technique ID.
79
+ * @param techniqueId - The ID of the technique
80
+ * @returns Result containing the provider, or failure if not found
81
+ */
82
+ getProvider(techniqueId) {
83
+ const provider = this._providers.get(techniqueId);
84
+ return provider ? succeed(provider) : fail(`No provider found for technique ${techniqueId}`);
85
+ }
86
+ /**
87
+ * Gets all registered providers, optionally filtered by criteria.
88
+ * @param options - Optional filtering options
89
+ * @returns Array of providers matching the criteria
90
+ */
91
+ getProviders(options) {
92
+ const providers = Array.from(this._providers.values());
93
+ if (!options) {
94
+ return providers;
95
+ }
96
+ let filtered = providers;
97
+ // Filter by enabled techniques
98
+ if (options.enabledTechniques && options.enabledTechniques.length > 0) {
99
+ filtered = filtered.filter((provider) => options.enabledTechniques.includes(provider.techniqueId));
100
+ }
101
+ // Filter by preferred difficulty (exact match for now)
102
+ if (options.preferredDifficulty) {
103
+ filtered = filtered.filter((provider) => provider.difficulty === options.preferredDifficulty);
104
+ }
105
+ return filtered;
106
+ }
107
+ /**
108
+ * Generates hints using all applicable providers.
109
+ * @param puzzle - The puzzle structure containing constraints
110
+ * @param state - The current puzzle state
111
+ * @param options - Optional generation options
112
+ * @returns Result containing array of hints from all providers
113
+ */
114
+ generateAllHints(puzzle, state, options) {
115
+ const applicableProviders = this.getProviders(options).filter((provider) => provider.canProvideHints(puzzle, state));
116
+ if (applicableProviders.length === 0) {
117
+ return succeed([]);
118
+ }
119
+ // Generate hints from all applicable providers
120
+ const hintResults = applicableProviders.map((provider) => provider.generateHints(puzzle, state, options));
121
+ return mapResults(hintResults).onSuccess((allHints) => {
122
+ // Flatten the array of hint arrays
123
+ const flatHints = allHints.flat();
124
+ // Sort by priority (ascending) then confidence (descending)
125
+ const sortedHints = flatHints.sort((a, b) => {
126
+ if (a.priority !== b.priority) {
127
+ return a.priority - b.priority;
128
+ }
129
+ return b.confidence - a.confidence;
130
+ });
131
+ // Apply global filtering options
132
+ return this._applyGlobalFiltering(sortedHints, options);
133
+ });
134
+ }
135
+ /**
136
+ * Gets the best available hint based on difficulty and confidence.
137
+ * @param puzzle - The puzzle structure containing constraints
138
+ * @param state - The current puzzle state
139
+ * @param options - Optional generation options
140
+ * @returns Result containing the best hint, or failure if no hints available
141
+ */
142
+ getBestHint(puzzle, state, options) {
143
+ return this.generateAllHints(puzzle, state, options).onSuccess((hints) => {
144
+ if (hints.length === 0) {
145
+ return fail('No hints available for the current puzzle state');
146
+ }
147
+ // Return the first hint (already sorted by priority and confidence)
148
+ return succeed(hints[0]);
149
+ });
150
+ }
151
+ /**
152
+ * Gets the number of registered providers.
153
+ * @returns The number of registered providers
154
+ */
155
+ get providerCount() {
156
+ return this._providers.size;
157
+ }
158
+ /**
159
+ * Gets all registered technique IDs.
160
+ * @returns Array of technique IDs
161
+ */
162
+ getRegisteredTechniques() {
163
+ return Array.from(this._providers.keys());
164
+ }
165
+ /**
166
+ * Checks if a specific technique is registered.
167
+ * @param techniqueId - The technique ID to check
168
+ * @returns true if the technique is registered
169
+ */
170
+ hasProvider(techniqueId) {
171
+ return this._providers.has(techniqueId);
172
+ }
173
+ /**
174
+ * Clears all registered providers.
175
+ * @returns Result indicating success
176
+ */
177
+ clear() {
178
+ this._providers.clear();
179
+ return succeed(undefined);
180
+ }
181
+ /**
182
+ * Gets providers grouped by difficulty level.
183
+ * @returns Map of difficulty levels to providers
184
+ */
185
+ getProvidersByDifficulty() {
186
+ var _a;
187
+ const providersByDifficulty = new Map();
188
+ for (const provider of this._providers.values()) {
189
+ const existing = (_a = providersByDifficulty.get(provider.difficulty)) !== null && _a !== void 0 ? _a : [];
190
+ existing.push(provider);
191
+ providersByDifficulty.set(provider.difficulty, existing);
192
+ }
193
+ // Convert to readonly arrays
194
+ const result = new Map();
195
+ for (const [difficulty, providers] of providersByDifficulty.entries()) {
196
+ result.set(difficulty, providers);
197
+ }
198
+ return result;
199
+ }
200
+ /**
201
+ * Applies global filtering options to the collected hints.
202
+ * @param hints - The hints to filter
203
+ * @param options - The filtering options
204
+ * @returns Result containing the filtered hints
205
+ */
206
+ _applyGlobalFiltering(hints, options) {
207
+ if (!options) {
208
+ return succeed(hints);
209
+ }
210
+ let filtered = [...hints];
211
+ // Apply minimum confidence filter
212
+ if (options.minConfidence) {
213
+ filtered = filtered.filter((hint) => hint.confidence >= options.minConfidence);
214
+ }
215
+ // Apply maximum hints limit
216
+ if (options.maxHints && options.maxHints > 0 && filtered.length > options.maxHints) {
217
+ filtered = filtered.slice(0, options.maxHints);
218
+ }
219
+ return succeed(filtered);
220
+ }
221
+ }
222
+ //# sourceMappingURL=hintRegistry.js.map
@@ -0,0 +1,311 @@
1
+ /*
2
+ * MIT License
3
+ *
4
+ * Copyright (c) 2023 Erik Fortune
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+ import { fail, succeed, mapResults } from '@fgv/ts-utils';
25
+ import { DefaultSudokuLogger } from '../common';
26
+ import { ExplanationFormatter } from './explanations';
27
+ import { HintRegistry } from './hintRegistry';
28
+ import { HiddenSinglesProvider } from './hiddenSingles';
29
+ import { NakedSinglesProvider } from './nakedSingles';
30
+ /**
31
+ * Default hint applicator that converts hints to cell state updates.
32
+ * @public
33
+ */
34
+ export class DefaultHintApplicator {
35
+ /**
36
+ * Creates a new {@link Hints.DefaultHintApplicator | DefaultHintApplicator}.
37
+ * @param logger - Optional logger for diagnostic output
38
+ */
39
+ constructor(logger) {
40
+ /* c8 ignore next 1 - defense in depth */
41
+ this._log = logger !== null && logger !== void 0 ? logger : DefaultSudokuLogger;
42
+ }
43
+ /**
44
+ * Validates that a hint can be safely applied to the given state.
45
+ * @param hint - The hint to validate
46
+ * @param puzzle - The puzzle structure containing constraints
47
+ * @param state - The current puzzle state
48
+ * @returns Result indicating validation success or failure with details
49
+ */
50
+ canApplyHint(hint, puzzle, state) {
51
+ const maxValue = Math.sqrt(puzzle.numRows * puzzle.numColumns);
52
+ // Validate all cell actions
53
+ const validations = hint.cellActions.map((action) => {
54
+ // Only support set-value actions for now
55
+ if (action.action !== 'set-value') {
56
+ return fail(`Unsupported action type: ${action.action}`);
57
+ }
58
+ // Validate the value to be set
59
+ if (action.value === undefined) {
60
+ return fail(`No value specified for set-value action on cell ${action.cellId}`);
61
+ }
62
+ if (action.value < 1 || action.value > maxValue) {
63
+ return fail(`Invalid value ${action.value} for cell ${action.cellId} (must be 1-${maxValue})`);
64
+ }
65
+ // Check that the cell exists and is empty
66
+ return state
67
+ .getCellContents(action.cellId)
68
+ .withErrorFormat((e) => `Invalid cell ${action.cellId}: ${e}`)
69
+ .onSuccess((cellContents) => {
70
+ if (cellContents.value !== undefined) {
71
+ return fail(`Cell ${action.cellId} already has value ${cellContents.value}`);
72
+ }
73
+ return succeed(undefined);
74
+ });
75
+ });
76
+ return mapResults(validations).onSuccess(() => succeed(undefined));
77
+ }
78
+ /**
79
+ * Applies a hint to the puzzle state, generating the necessary cell updates.
80
+ * @param hint - The hint to apply
81
+ * @param puzzle - The puzzle structure containing constraints
82
+ * @param state - The current puzzle state
83
+ * @returns Result containing the cell state updates needed to apply the hint
84
+ */
85
+ applyHint(hint, puzzle, state) {
86
+ this._log.detail(`Applying hint: ${hint.techniqueName} affecting ${hint.cellActions.length} cell(s)`);
87
+ return this.canApplyHint(hint, puzzle, state).onSuccess(() => {
88
+ const updates = [];
89
+ for (const action of hint.cellActions) {
90
+ if (action.action === 'set-value' && action.value !== undefined) {
91
+ // Get current cell contents to preserve notes
92
+ const cellResult = state.getCellContents(action.cellId);
93
+ /* c8 ignore next 3 - internal error should never happen in practice */
94
+ if (cellResult.isFailure()) {
95
+ return fail(`Failed to get cell contents: ${cellResult.message}`);
96
+ }
97
+ updates.push({
98
+ id: action.cellId,
99
+ value: action.value,
100
+ notes: cellResult.value.notes // Preserve existing notes
101
+ });
102
+ }
103
+ }
104
+ this._log.info(`Successfully applied hint: ${hint.techniqueName}, updated ${updates.length} cell(s)`);
105
+ return succeed(updates);
106
+ });
107
+ }
108
+ }
109
+ /**
110
+ * Main hint system that provides comprehensive hint generation and management.
111
+ * @public
112
+ */
113
+ export class HintSystem {
114
+ /**
115
+ * Creates a new HintSystem instance.
116
+ * @param registry - The hint registry to use
117
+ * @param applicator - The hint applicator to use
118
+ * @param config - Configuration options
119
+ */
120
+ constructor(registry, applicator, config) {
121
+ var _a;
122
+ this._registry = registry;
123
+ this._applicator = applicator;
124
+ this._config = config;
125
+ this._log = (_a = config.logger) !== null && _a !== void 0 ? _a : DefaultSudokuLogger;
126
+ this._logHints = this._log.withValueFormatter((hints) => `Generated ${hints.length} hint(s) using ${new Set(hints.map((h) => h.techniqueId)).size} technique(s)`);
127
+ }
128
+ /**
129
+ * Creates a new HintSystem with default providers and configuration.
130
+ * @param config - Optional configuration options
131
+ * @returns Result containing the new hint system
132
+ */
133
+ static create(config) {
134
+ const finalConfig = Object.assign({ enableNakedSingles: true, enableHiddenSingles: true, defaultExplanationLevel: 'detailed' }, config);
135
+ return HintRegistry.create()
136
+ .withErrorFormat((e) => `Failed to create hint registry: ${e}`)
137
+ .onSuccess((registry) => {
138
+ // Register naked singles provider if enabled
139
+ if (finalConfig.enableNakedSingles) {
140
+ const nakedResult = NakedSinglesProvider.create()
141
+ .withErrorFormat((e) => `Failed to create naked singles provider: ${e}`)
142
+ .onSuccess((provider) => registry.registerProvider(provider))
143
+ .withErrorFormat((e) => `Failed to register naked singles provider: ${e}`);
144
+ /* c8 ignore next 3 - defensive coding: provider creation/registration should not fail */
145
+ if (nakedResult.isFailure()) {
146
+ return fail(nakedResult.message);
147
+ }
148
+ }
149
+ // Register hidden singles provider if enabled
150
+ if (finalConfig.enableHiddenSingles) {
151
+ const hiddenResult = HiddenSinglesProvider.create()
152
+ .withErrorFormat((e) => `Failed to create hidden singles provider: ${e}`)
153
+ .onSuccess((provider) => registry.registerProvider(provider))
154
+ .withErrorFormat((e) => `Failed to register hidden singles provider: ${e}`);
155
+ /* c8 ignore next 3 - defensive coding: provider creation/registration should not fail */
156
+ if (hiddenResult.isFailure()) {
157
+ return fail(hiddenResult.message);
158
+ }
159
+ }
160
+ // Create applicator and return complete system
161
+ const applicator = new DefaultHintApplicator(finalConfig.logger);
162
+ return succeed(new HintSystem(registry, applicator, finalConfig));
163
+ });
164
+ }
165
+ /**
166
+ * Gets the hint registry.
167
+ * @returns The hint registry
168
+ */
169
+ get registry() {
170
+ return this._registry;
171
+ }
172
+ /**
173
+ * Gets the hint applicator.
174
+ * @returns The hint applicator
175
+ */
176
+ get applicator() {
177
+ return this._applicator;
178
+ }
179
+ /**
180
+ * Gets the system configuration.
181
+ * @returns The configuration
182
+ */
183
+ get config() {
184
+ return this._config;
185
+ }
186
+ /**
187
+ * Generates all available hints for the current puzzle state.
188
+ * @param puzzle - The puzzle structure containing constraints
189
+ * @param state - The current puzzle state
190
+ * @param options - Optional generation options
191
+ * @returns Result containing array of hints
192
+ */
193
+ generateHints(puzzle, state, options) {
194
+ this._log.detail('Generating hints for puzzle state');
195
+ return this._registry.generateAllHints(puzzle, state, options).report(this._logHints, {
196
+ success: { level: 'info' },
197
+ failure: {
198
+ level: 'warning',
199
+ message: (msg) => `Failed to generate hints: ${msg}`
200
+ }
201
+ });
202
+ }
203
+ /**
204
+ * Gets the best available hint for the current puzzle state.
205
+ * @param puzzle - The puzzle structure containing constraints
206
+ * @param state - The current puzzle state
207
+ * @param options - Optional generation options
208
+ * @returns Result containing the best hint
209
+ */
210
+ getBestHint(puzzle, state, options) {
211
+ return this._registry.getBestHint(puzzle, state, options);
212
+ }
213
+ /**
214
+ * Applies a hint to generate cell state updates.
215
+ * @param hint - The hint to apply
216
+ * @param puzzle - The puzzle structure containing constraints
217
+ * @param state - The current puzzle state
218
+ * @returns Result containing the cell updates
219
+ */
220
+ applyHint(hint, puzzle, state) {
221
+ return this._applicator.applyHint(hint, puzzle, state);
222
+ }
223
+ /**
224
+ * Validates that a hint can be applied to the current state.
225
+ * @param hint - The hint to validate
226
+ * @param puzzle - The puzzle structure containing constraints
227
+ * @param state - The current puzzle state
228
+ * @returns Result indicating validation success or failure
229
+ */
230
+ validateHint(hint, puzzle, state) {
231
+ return this._applicator.canApplyHint(hint, puzzle, state);
232
+ }
233
+ /**
234
+ * Formats a hint explanation for display.
235
+ * @param hint - The hint to format
236
+ * @param level - The explanation level to use (defaults to config default)
237
+ * @returns Formatted explanation string
238
+ */
239
+ formatHintExplanation(hint, level) {
240
+ var _a;
241
+ /* c8 ignore next 1 - ?? is defense in depth */
242
+ const explanationLevel = (_a = level !== null && level !== void 0 ? level : this._config.defaultExplanationLevel) !== null && _a !== void 0 ? _a : 'detailed';
243
+ const explanation = hint.explanations.find((exp) => exp.level === explanationLevel);
244
+ if (!explanation) {
245
+ return `No explanation available at level ${explanationLevel} for ${hint.techniqueName}`;
246
+ }
247
+ return ExplanationFormatter.formatExplanation(explanation);
248
+ }
249
+ /**
250
+ * Gets a summary of the hint system capabilities.
251
+ * @returns System capabilities summary
252
+ */
253
+ getSystemSummary() {
254
+ var _a;
255
+ const techniques = this._registry.getRegisteredTechniques();
256
+ const techniqueNames = techniques.map((id) => {
257
+ const provider = this._registry.getProvider(id);
258
+ /* c8 ignore next 1 - fallback to id should not happen in practice */
259
+ return provider.isSuccess() ? provider.value.techniqueName : id;
260
+ });
261
+ /* c8 ignore next 1 - ?? is defense in depth */
262
+ const defaultExplanationLevel = (_a = this._config.defaultExplanationLevel) !== null && _a !== void 0 ? _a : 'detailed';
263
+ return [
264
+ 'Sudoku Hint System',
265
+ '='.repeat(20),
266
+ `Registered Techniques: ${techniques.length}`,
267
+ ...techniqueNames.map((name) => `• ${name}`),
268
+ '',
269
+ `Default Explanation Level: ${defaultExplanationLevel}`
270
+ ].join('\n');
271
+ }
272
+ /**
273
+ * Checks if the puzzle state has any available hints.
274
+ * @param puzzle - The puzzle structure containing constraints
275
+ * @param state - The current puzzle state
276
+ * @returns Result containing boolean indicating if hints are available
277
+ */
278
+ hasHints(puzzle, state) {
279
+ return this.generateHints(puzzle, state, { maxHints: 1 }).onSuccess((hints) => {
280
+ return succeed(hints.length > 0);
281
+ });
282
+ }
283
+ /**
284
+ * Gets statistics about available hints for the current state.
285
+ * @param puzzle - The puzzle structure containing constraints
286
+ * @param state - The current puzzle state
287
+ * @returns Result containing hint statistics
288
+ */
289
+ getHintStatistics(puzzle, state) {
290
+ return this.generateHints(puzzle, state).onSuccess((hints) => {
291
+ var _a, _b;
292
+ const hintsByTechnique = new Map();
293
+ const hintsByDifficulty = new Map();
294
+ for (const hint of hints) {
295
+ // Count by technique
296
+ /* c8 ignore next 1 - ?? is defense in depth */
297
+ const techniqueCount = (_a = hintsByTechnique.get(hint.techniqueName)) !== null && _a !== void 0 ? _a : 0;
298
+ hintsByTechnique.set(hint.techniqueName, techniqueCount + 1);
299
+ // Count by difficulty
300
+ const difficultyCount = (_b = hintsByDifficulty.get(hint.difficulty)) !== null && _b !== void 0 ? _b : 0;
301
+ hintsByDifficulty.set(hint.difficulty, difficultyCount + 1);
302
+ }
303
+ return succeed({
304
+ totalHints: hints.length,
305
+ hintsByTechnique,
306
+ hintsByDifficulty
307
+ });
308
+ });
309
+ }
310
+ }
311
+ //# sourceMappingURL=hints.js.map
@@ -0,0 +1,39 @@
1
+ /*
2
+ * MIT License
3
+ *
4
+ * Copyright (c) 2023 Erik Fortune
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+ // Core types and interfaces
25
+ export * from './types';
26
+ export * from './interfaces';
27
+ // Base classes and utilities
28
+ export * from './baseHintProvider';
29
+ export * from './hintRegistry';
30
+ // Technique implementations
31
+ export * from './nakedSingles';
32
+ export * from './hiddenSingles';
33
+ // Educational framework
34
+ export * from './explanations';
35
+ // Main hint system
36
+ export * from './hints';
37
+ // PuzzleSession integration
38
+ export * from './puzzleSessionHints';
39
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,25 @@
1
+ /*
2
+ * MIT License
3
+ *
4
+ * Copyright (c) 2023 Erik Fortune
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+ export {};
25
+ //# sourceMappingURL=interfaces.js.map