@fgv/ts-sudoku-lib 5.0.1-0 → 5.0.1-2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/debug_killer.js +50 -0
  2. package/debug_test.js +88 -0
  3. package/dist/ts-sudoku-lib.d.ts +1505 -42
  4. package/lib/index.d.ts +3 -2
  5. package/lib/index.js +5 -3
  6. package/lib/packlets/collections/collections.d.ts +15 -8
  7. package/lib/packlets/collections/collections.js +41 -11
  8. package/lib/packlets/collections/data/puzzles.json +28 -33
  9. package/lib/packlets/common/common.d.ts +15 -0
  10. package/lib/packlets/common/common.js +25 -0
  11. package/lib/packlets/common/converters.d.ts +0 -6
  12. package/lib/packlets/common/converters.js +17 -22
  13. package/lib/packlets/common/ids.d.ts +8 -1
  14. package/lib/packlets/common/ids.js +83 -10
  15. package/lib/packlets/common/index.d.ts +3 -2
  16. package/lib/packlets/common/index.js +5 -2
  17. package/lib/packlets/common/logging.d.ts +7 -0
  18. package/lib/packlets/common/logging.js +35 -0
  19. package/lib/packlets/common/puzzle.d.ts +23 -13
  20. package/lib/packlets/common/puzzle.js +51 -25
  21. package/lib/packlets/common/puzzleDefinitions.d.ts +127 -0
  22. package/lib/packlets/common/puzzleDefinitions.js +251 -0
  23. package/lib/packlets/common/puzzleSession.d.ts +8 -0
  24. package/lib/packlets/common/puzzleSession.js +12 -0
  25. package/lib/packlets/common/puzzleState.js +3 -0
  26. package/lib/packlets/files/converters.d.ts +27 -0
  27. package/lib/packlets/files/converters.js +71 -0
  28. package/lib/packlets/{file/converters.d.ts → files/filesystem.d.ts} +3 -8
  29. package/lib/packlets/{file/converters.js → files/filesystem.js} +5 -14
  30. package/lib/packlets/files/index.d.ts +5 -0
  31. package/lib/packlets/{file → files}/index.js +3 -1
  32. package/lib/packlets/files/model.d.ts +32 -0
  33. package/lib/packlets/hints/baseHintProvider.d.ts +103 -0
  34. package/lib/packlets/hints/baseHintProvider.js +203 -0
  35. package/lib/packlets/hints/explanations.d.ts +105 -0
  36. package/lib/packlets/hints/explanations.js +276 -0
  37. package/lib/packlets/hints/hiddenSingles.d.ts +81 -0
  38. package/lib/packlets/hints/hiddenSingles.js +237 -0
  39. package/lib/packlets/hints/hintRegistry.d.ts +95 -0
  40. package/lib/packlets/hints/hintRegistry.js +226 -0
  41. package/lib/packlets/hints/hints.d.ts +144 -0
  42. package/lib/packlets/hints/hints.js +316 -0
  43. package/lib/packlets/hints/index.d.ts +10 -0
  44. package/lib/packlets/hints/index.js +55 -0
  45. package/lib/packlets/hints/interfaces.d.ts +121 -0
  46. package/lib/packlets/{file/model.js → hints/interfaces.js} +1 -1
  47. package/lib/packlets/hints/nakedSingles.d.ts +57 -0
  48. package/lib/packlets/hints/nakedSingles.js +202 -0
  49. package/lib/packlets/hints/puzzleSessionHints.d.ts +306 -0
  50. package/lib/packlets/hints/puzzleSessionHints.js +499 -0
  51. package/lib/packlets/hints/types.d.ts +102 -0
  52. package/lib/packlets/hints/types.js +46 -0
  53. package/lib/packlets/puzzles/anyPuzzle.d.ts +3 -3
  54. package/lib/packlets/puzzles/anyPuzzle.js +1 -1
  55. package/lib/packlets/puzzles/index.d.ts +4 -2
  56. package/lib/packlets/puzzles/index.js +20 -3
  57. package/lib/packlets/puzzles/internal/combinationCache.d.ts +29 -0
  58. package/lib/packlets/puzzles/internal/combinationCache.js +79 -0
  59. package/lib/packlets/puzzles/internal/combinationGenerator.d.ts +34 -0
  60. package/lib/packlets/puzzles/internal/combinationGenerator.js +146 -0
  61. package/lib/packlets/puzzles/internal/possibilityAnalyzer.d.ts +25 -0
  62. package/lib/packlets/puzzles/internal/possibilityAnalyzer.js +125 -0
  63. package/lib/packlets/puzzles/killerCombinations.d.ts +90 -0
  64. package/lib/packlets/puzzles/killerCombinations.js +226 -0
  65. package/lib/packlets/puzzles/killerCombinationsTypes.d.ts +17 -0
  66. package/lib/packlets/puzzles/killerCombinationsTypes.js +26 -0
  67. package/lib/packlets/puzzles/killerSudokuPuzzle.d.ts +2 -2
  68. package/lib/packlets/puzzles/killerSudokuPuzzle.js +15 -11
  69. package/lib/packlets/puzzles/sudokuPuzzle.d.ts +2 -2
  70. package/lib/packlets/puzzles/sudokuXPuzzle.d.ts +2 -2
  71. package/lib/packlets/puzzles/sudokuXPuzzle.js +8 -4
  72. package/package.json +5 -5
  73. package/sample-12x12-puzzle.js +72 -0
  74. package/sample-4x4-puzzles.js +68 -0
  75. package/sample-6x6-puzzle.js +61 -0
  76. package/temp_12x12_cells.txt +12 -0
  77. package/temp_correct.txt +12 -0
  78. package/test-grid-sizes.js +132 -0
  79. package/lib/packlets/common/model.d.ts +0 -15
  80. package/lib/packlets/file/index.d.ts +0 -4
  81. package/lib/packlets/file/model.d.ts +0 -9
  82. /package/lib/packlets/{common → files}/model.js +0 -0
@@ -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