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