@fgv/ts-sudoku-lib 5.0.1-8 → 5.0.1
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/dist/index.browser.js +37 -0
- package/dist/index.js +30 -0
- package/dist/packlets/collections/collections.js +136 -0
- package/dist/packlets/collections/data/puzzles.json +56 -0
- package/dist/packlets/collections/index.js +25 -0
- package/dist/packlets/common/cage.js +83 -0
- package/dist/packlets/common/cell.js +88 -0
- package/dist/packlets/common/common.js +70 -0
- package/dist/packlets/common/converters.js +65 -0
- package/dist/packlets/common/ids.js +152 -0
- package/dist/packlets/common/index.js +36 -0
- package/dist/packlets/common/logging.js +32 -0
- package/dist/packlets/common/public.js +25 -0
- package/dist/packlets/common/puzzle.js +411 -0
- package/dist/packlets/common/puzzleDefinitions.js +247 -0
- package/dist/packlets/common/puzzleSession.js +369 -0
- package/dist/packlets/common/puzzleState.js +99 -0
- package/dist/packlets/files/converters.js +67 -0
- package/dist/packlets/files/fileTreeHelpers.js +37 -0
- package/dist/packlets/files/filesystem.js +37 -0
- package/dist/packlets/files/index.browser.js +32 -0
- package/dist/packlets/files/index.js +30 -0
- package/dist/packlets/files/model.js +25 -0
- package/dist/packlets/hints/baseHintProvider.js +199 -0
- package/dist/packlets/hints/explanations.js +270 -0
- package/dist/packlets/hints/hiddenSingles.js +233 -0
- package/dist/packlets/hints/hintRegistry.js +222 -0
- package/dist/packlets/hints/hints.js +311 -0
- package/dist/packlets/hints/index.js +39 -0
- package/dist/packlets/hints/interfaces.js +25 -0
- package/dist/packlets/hints/nakedSingles.js +198 -0
- package/dist/packlets/hints/puzzleSessionHints.js +495 -0
- package/dist/packlets/hints/types.js +43 -0
- package/dist/packlets/puzzles/anyPuzzle.js +47 -0
- package/dist/packlets/puzzles/index.js +31 -0
- package/dist/packlets/puzzles/internal/combinationCache.js +75 -0
- package/dist/packlets/puzzles/internal/combinationGenerator.js +142 -0
- package/dist/packlets/puzzles/internal/possibilityAnalyzer.js +121 -0
- package/dist/packlets/puzzles/killerCombinations.js +222 -0
- package/dist/packlets/puzzles/killerCombinationsTypes.js +25 -0
- package/dist/packlets/puzzles/killerSudokuPuzzle.js +136 -0
- package/dist/packlets/puzzles/sudokuPuzzle.js +43 -0
- package/dist/packlets/puzzles/sudokuXPuzzle.js +65 -0
- package/dist/test/unit/helpers/puzzleBuilders.js +149 -0
- package/dist/ts-sudoku-lib.d.ts +12 -7
- package/dist/tsdoc-metadata.json +1 -1
- package/lib/index.browser.d.ts +7 -0
- package/lib/index.browser.js +78 -0
- package/lib/packlets/files/fileTreeHelpers.d.ts +13 -0
- package/lib/packlets/files/fileTreeHelpers.js +40 -0
- package/lib/packlets/files/index.browser.d.ts +5 -0
- package/lib/packlets/files/index.browser.js +70 -0
- package/lib/packlets/files/index.d.ts +3 -2
- package/lib/packlets/files/index.js +7 -4
- package/package.json +19 -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
|