@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.
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,276 @@
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.EducationalContent = exports.ExplanationFormatter = exports.ExplanationRegistry = void 0;
27
+ const ts_utils_1 = require("@fgv/ts-utils");
28
+ const types_1 = require("./types");
29
+ /**
30
+ * Registry for managing hint explanation providers.
31
+ * @public
32
+ */
33
+ class ExplanationRegistry {
34
+ /**
35
+ * Creates a new ExplanationRegistry instance.
36
+ */
37
+ constructor() {
38
+ this._providers = new Map();
39
+ }
40
+ /**
41
+ * Registers a new explanation provider.
42
+ * @param provider - The provider to register
43
+ * @returns Result indicating success or failure of registration
44
+ */
45
+ registerProvider(provider) {
46
+ if (this._providers.has(provider.techniqueId)) {
47
+ return (0, ts_utils_1.fail)(`Explanation provider for technique ${provider.techniqueId} is already registered`);
48
+ }
49
+ this._providers.set(provider.techniqueId, provider);
50
+ return (0, ts_utils_1.succeed)(undefined);
51
+ }
52
+ /**
53
+ * Gets explanations for a specific hint.
54
+ * @param hint - The hint to explain
55
+ * @param puzzle - The puzzle structure containing constraints
56
+ * @param state - The puzzle state context
57
+ * @returns Result containing the explanations
58
+ */
59
+ getExplanations(hint, puzzle, state) {
60
+ const provider = this._providers.get(hint.techniqueId);
61
+ if (!provider) {
62
+ return (0, ts_utils_1.fail)(`No explanation provider found for technique ${hint.techniqueId}`);
63
+ }
64
+ return provider.generateExplanations(hint, puzzle, state);
65
+ }
66
+ /**
67
+ * Gets a specific explanation at the requested level.
68
+ * @param hint - The hint to explain
69
+ * @param level - The desired explanation level
70
+ * @param state - The puzzle state context
71
+ * @returns Result containing the explanation at the specified level
72
+ */
73
+ getExplanationAtLevel(hint, level, puzzle, state) {
74
+ return this.getExplanations(hint, puzzle, state).onSuccess((explanations) => {
75
+ const explanation = explanations.find((exp) => exp.level === level);
76
+ return explanation
77
+ ? (0, ts_utils_1.succeed)(explanation)
78
+ : (0, ts_utils_1.fail)(`No explanation available at level ${level} for technique ${hint.techniqueId}`);
79
+ });
80
+ }
81
+ }
82
+ exports.ExplanationRegistry = ExplanationRegistry;
83
+ /**
84
+ * Utility class for formatting and displaying hint explanations.
85
+ * @public
86
+ */
87
+ class ExplanationFormatter {
88
+ /**
89
+ * Formats a hint explanation as a readable string.
90
+ * @param explanation - The explanation to format
91
+ * @param includeSteps - Whether to include step-by-step instructions
92
+ * @param includeTips - Whether to include tips
93
+ * @returns Formatted explanation string
94
+ */
95
+ static formatExplanation(explanation, includeSteps = true, includeTips = true) {
96
+ const sections = [];
97
+ // Title and description
98
+ sections.push(`${explanation.title}`);
99
+ sections.push(`${explanation.description}`);
100
+ // Steps
101
+ if (includeSteps && explanation.steps && explanation.steps.length > 0) {
102
+ sections.push('\nSteps:');
103
+ explanation.steps.forEach((step, index) => {
104
+ sections.push(`${index + 1}. ${step}`);
105
+ });
106
+ }
107
+ // Tips
108
+ if (includeTips && explanation.tips && explanation.tips.length > 0) {
109
+ sections.push('\nTips:');
110
+ explanation.tips.forEach((tip) => {
111
+ sections.push(`• ${tip}`);
112
+ });
113
+ }
114
+ return sections.join('\n');
115
+ }
116
+ /**
117
+ * Formats all explanations for a hint as a structured string.
118
+ * @param explanations - The explanations to format
119
+ * @returns Formatted explanations string
120
+ */
121
+ static formatAllExplanations(explanations) {
122
+ const sections = [];
123
+ for (const explanation of explanations) {
124
+ sections.push(`--- ${explanation.level.toUpperCase()} ---`);
125
+ sections.push(this.formatExplanation(explanation));
126
+ sections.push('');
127
+ }
128
+ return sections.join('\n');
129
+ }
130
+ /**
131
+ * Creates a summary of available explanation levels.
132
+ * @param explanations - The explanations to summarize
133
+ * @returns Summary string
134
+ */
135
+ static createLevelSummary(explanations) {
136
+ const levels = explanations.map((exp) => exp.level).join(', ');
137
+ return `Available explanation levels: ${levels}`;
138
+ }
139
+ }
140
+ exports.ExplanationFormatter = ExplanationFormatter;
141
+ /**
142
+ * Educational content manager for Sudoku solving techniques.
143
+ * @public
144
+ */
145
+ class EducationalContent {
146
+ /**
147
+ * Gets an introduction to a specific technique.
148
+ * @param techniqueId - The technique to introduce
149
+ * @returns Result containing the introduction text
150
+ */
151
+ static getTechniqueIntroduction(techniqueId) {
152
+ const introduction = this._techniqueIntroductions.get(techniqueId);
153
+ return introduction
154
+ ? (0, ts_utils_1.succeed)(introduction)
155
+ : (0, ts_utils_1.fail)(`No introduction available for technique ${techniqueId}`);
156
+ }
157
+ /**
158
+ * Gets relationship information for a technique.
159
+ * @param techniqueId - The technique to get relationships for
160
+ * @returns Result containing the relationship information
161
+ */
162
+ static getTechniqueRelationships(techniqueId) {
163
+ const relationships = this._techniqueRelationships.get(techniqueId);
164
+ return relationships
165
+ ? (0, ts_utils_1.succeed)(relationships)
166
+ : (0, ts_utils_1.fail)(`No relationship information available for technique ${techniqueId}`);
167
+ }
168
+ /**
169
+ * Gets a complete educational overview for a technique.
170
+ * @param techniqueId - The technique to describe
171
+ * @returns Result containing the complete overview
172
+ */
173
+ static getTechniqueOverview(techniqueId) {
174
+ const introduction = this.getTechniqueIntroduction(techniqueId);
175
+ const relationships = this.getTechniqueRelationships(techniqueId);
176
+ return introduction.onSuccess((intro) => {
177
+ return relationships.onSuccess((rels) => {
178
+ const sections = [intro, '', 'Technique Relationships:', ...rels.map((rel) => `• ${rel}`)];
179
+ return (0, ts_utils_1.succeed)(sections.join('\n'));
180
+ });
181
+ });
182
+ }
183
+ /**
184
+ * Gets general Sudoku solving advice.
185
+ * @returns Array of general solving tips
186
+ */
187
+ static getGeneralSolvingTips() {
188
+ return [
189
+ 'Start with naked singles - they are always correct when found',
190
+ 'After placing numbers, scan for new naked singles immediately',
191
+ 'Check for hidden singles systematically in rows, columns, and boxes',
192
+ 'Focus on units with many filled cells first',
193
+ 'Keep track of candidates mentally or with pencil marks',
194
+ 'Take breaks when stuck - fresh eyes often spot overlooked patterns',
195
+ 'Practice pattern recognition to spot techniques more quickly'
196
+ ];
197
+ }
198
+ /**
199
+ * Gets difficulty progression advice.
200
+ * @returns Advice for progressing through difficulty levels
201
+ */
202
+ static getDifficultyProgression() {
203
+ return [
204
+ 'Master naked singles and hidden singles completely before advancing',
205
+ 'Beginner puzzles can often be solved with just these two techniques',
206
+ 'Intermediate puzzles introduce pointing pairs and box/line interactions',
207
+ 'Advanced puzzles require naked and hidden pairs/triples',
208
+ 'Expert puzzles may need advanced techniques like X-wing and swordfish',
209
+ 'Each technique builds on the foundation of simpler techniques'
210
+ ];
211
+ }
212
+ /**
213
+ * Creates a complete educational guide for beginners.
214
+ * @returns Comprehensive beginner guide
215
+ */
216
+ static createBeginnerGuide() {
217
+ const sections = [
218
+ 'SUDOKU SOLVING GUIDE FOR BEGINNERS',
219
+ '='.repeat(35),
220
+ '',
221
+ 'Sudoku is a logic puzzle where you fill a 9x9 grid with digits 1-9.',
222
+ 'Each row, column, and 3x3 box must contain each digit exactly once.',
223
+ '',
224
+ 'BASIC TECHNIQUES:',
225
+ '',
226
+ '1. Naked Singles',
227
+ this._techniqueIntroductions.get(types_1.TechniqueIds.NAKED_SINGLES),
228
+ '',
229
+ '2. Hidden Singles',
230
+ this._techniqueIntroductions.get(types_1.TechniqueIds.HIDDEN_SINGLES),
231
+ '',
232
+ 'SOLVING STRATEGY:',
233
+ ...this.getGeneralSolvingTips().map((tip) => `• ${tip}`),
234
+ '',
235
+ 'PROGRESSION:',
236
+ ...this.getDifficultyProgression().map((tip) => `• ${tip}`)
237
+ ];
238
+ return sections.join('\n');
239
+ }
240
+ }
241
+ exports.EducationalContent = EducationalContent;
242
+ EducationalContent._techniqueIntroductions = new Map([
243
+ [
244
+ types_1.TechniqueIds.NAKED_SINGLES,
245
+ 'Naked Singles are the most fundamental Sudoku technique. When a cell has only one possible ' +
246
+ 'value due to the numbers already placed in its row, column, and 3x3 box, that cell contains ' +
247
+ 'a "naked single." This technique is always safe to apply and forms the foundation for more ' +
248
+ 'advanced solving methods.'
249
+ ],
250
+ [
251
+ types_1.TechniqueIds.HIDDEN_SINGLES,
252
+ 'Hidden Singles focus on finding where specific values must be placed within a unit (row, ' +
253
+ 'column, or 3x3 box). When a particular number can only be placed in one cell within a unit, ' +
254
+ 'that placement is a "hidden single." This technique complements naked singles and together ' +
255
+ 'they can solve many easier puzzles completely.'
256
+ ]
257
+ ]);
258
+ EducationalContent._techniqueRelationships = new Map([
259
+ [
260
+ types_1.TechniqueIds.NAKED_SINGLES,
261
+ [
262
+ 'Apply naked singles first - they often reveal new naked singles',
263
+ 'Naked singles may create hidden singles in related units',
264
+ 'Use with hidden singles to solve beginner-level puzzles'
265
+ ]
266
+ ],
267
+ [
268
+ types_1.TechniqueIds.HIDDEN_SINGLES,
269
+ [
270
+ 'Check for hidden singles after applying naked singles',
271
+ 'Hidden singles can create new naked singles',
272
+ 'Consider all three unit types: rows, columns, and boxes'
273
+ ]
274
+ ]
275
+ ]);
276
+ //# sourceMappingURL=explanations.js.map
@@ -0,0 +1,81 @@
1
+ import { Result } from '@fgv/ts-utils';
2
+ import { Puzzle, PuzzleState } from '../common';
3
+ import { BaseHintProvider } from './baseHintProvider';
4
+ import { IHint, IHintGenerationOptions } from './types';
5
+ /**
6
+ * Hint provider for the Hidden Singles technique.
7
+ *
8
+ * A Hidden Single occurs when a value can only be placed in one cell within
9
+ * a row, column, or 3x3 box, even though that cell may have multiple candidates.
10
+ *
11
+ * @public
12
+ */
13
+ export declare class HiddenSinglesProvider extends BaseHintProvider {
14
+ /**
15
+ * Creates a new HiddenSinglesProvider instance.
16
+ */
17
+ constructor();
18
+ /**
19
+ * Determines if this provider can potentially generate hints for the given puzzle.
20
+ * @param puzzle - The puzzle structure containing constraints
21
+ * @param state - The current puzzle state
22
+ * @returns true if there are empty cells that might have hidden singles
23
+ */
24
+ canProvideHints(puzzle: Puzzle, state: PuzzleState): boolean;
25
+ /**
26
+ * Generates all hidden single hints for the given puzzle.
27
+ * @param puzzle - The puzzle structure containing constraints
28
+ * @param state - The current puzzle state
29
+ * @param options - Optional generation options
30
+ * @returns Result containing array of hidden single hints
31
+ */
32
+ generateHints(puzzle: Puzzle, state: PuzzleState, options?: IHintGenerationOptions): Result<readonly IHint[]>;
33
+ /**
34
+ * Finds all hidden singles in the puzzle using cage-based analysis.
35
+ * @param puzzle - The puzzle structure containing constraints
36
+ * @param state - The current puzzle state
37
+ * @returns Array of hidden single information
38
+ */
39
+ private _findAllHiddenSingles;
40
+ /**
41
+ * Finds hidden singles in a specific cage using dynamic value analysis.
42
+ * @param cage - The cage to analyze
43
+ * @param puzzle - The puzzle structure containing constraints
44
+ * @param state - The current puzzle state
45
+ * @returns Array of hidden singles in this cage
46
+ */
47
+ private _findHiddenSinglesInCage;
48
+ /**
49
+ * Gets a display-friendly index for a cage.
50
+ * @param cage - The cage to get index for
51
+ * @returns Display index for the cage
52
+ */
53
+ private _getCageDisplayIndex;
54
+ /**
55
+ * Removes duplicate hidden singles (same cell and value).
56
+ * @param hiddenSingles - Array of potentially duplicate hidden singles
57
+ * @returns Array with duplicates removed
58
+ */
59
+ private _removeDuplicateHiddenSingles;
60
+ /**
61
+ * Creates a hint for a specific hidden single.
62
+ * @param hiddenSingle - The hidden single information
63
+ * @param puzzle - The puzzle structure containing constraints
64
+ * @param state - The current puzzle state
65
+ * @returns A complete hint for this hidden single
66
+ */
67
+ private _createHiddenSingleHint;
68
+ /**
69
+ * Gets a human-readable name for a unit.
70
+ * @param unitType - The type of unit
71
+ * @param unitIndex - The index of the unit
72
+ * @returns Human-readable unit name
73
+ */
74
+ private _getUnitName;
75
+ /**
76
+ * Static factory method to create a new HiddenSinglesProvider.
77
+ * @returns Result containing the new provider
78
+ */
79
+ static create(): Result<HiddenSinglesProvider>;
80
+ }
81
+ //# sourceMappingURL=hiddenSingles.d.ts.map
@@ -0,0 +1,237 @@
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.HiddenSinglesProvider = void 0;
27
+ const ts_utils_1 = require("@fgv/ts-utils");
28
+ const baseHintProvider_1 = require("./baseHintProvider");
29
+ const types_1 = require("./types");
30
+ /**
31
+ * Hint provider for the Hidden Singles technique.
32
+ *
33
+ * A Hidden Single occurs when a value can only be placed in one cell within
34
+ * a row, column, or 3x3 box, even though that cell may have multiple candidates.
35
+ *
36
+ * @public
37
+ */
38
+ class HiddenSinglesProvider extends baseHintProvider_1.BaseHintProvider {
39
+ /**
40
+ * Creates a new HiddenSinglesProvider instance.
41
+ */
42
+ constructor() {
43
+ super({
44
+ techniqueId: types_1.TechniqueIds.HIDDEN_SINGLES,
45
+ techniqueName: 'Hidden Singles',
46
+ difficulty: 'beginner',
47
+ priority: 2, // Second priority after naked singles
48
+ defaultConfidence: types_1.ConfidenceLevels.HIGH
49
+ });
50
+ }
51
+ /**
52
+ * Determines if this provider can potentially generate hints for the given puzzle.
53
+ * @param puzzle - The puzzle structure containing constraints
54
+ * @param state - The current puzzle state
55
+ * @returns true if there are empty cells that might have hidden singles
56
+ */
57
+ canProvideHints(puzzle, state) {
58
+ const emptyCells = this.getEmptyCells(puzzle, state);
59
+ return emptyCells.length > 0;
60
+ }
61
+ /**
62
+ * Generates all hidden single hints for the given puzzle.
63
+ * @param puzzle - The puzzle structure containing constraints
64
+ * @param state - The current puzzle state
65
+ * @param options - Optional generation options
66
+ * @returns Result containing array of hidden single hints
67
+ */
68
+ generateHints(puzzle, state, options) {
69
+ return this.validateOptions(options).onSuccess(() => {
70
+ const hints = [];
71
+ const hiddenSingles = this._findAllHiddenSingles(puzzle, state);
72
+ for (const hiddenSingle of hiddenSingles) {
73
+ const hint = this._createHiddenSingleHint(hiddenSingle, puzzle, state);
74
+ hints.push(hint);
75
+ }
76
+ return (0, ts_utils_1.succeed)(this.filterHints(hints, options));
77
+ });
78
+ }
79
+ /**
80
+ * Finds all hidden singles in the puzzle using cage-based analysis.
81
+ * @param puzzle - The puzzle structure containing constraints
82
+ * @param state - The current puzzle state
83
+ * @returns Array of hidden single information
84
+ */
85
+ _findAllHiddenSingles(puzzle, state) {
86
+ const hiddenSingles = [];
87
+ // Check all cages for hidden singles
88
+ for (const cage of puzzle.cages) {
89
+ hiddenSingles.push(...this._findHiddenSinglesInCage(cage, puzzle, state));
90
+ }
91
+ // Remove duplicates (a cell might be found as hidden single in multiple cages)
92
+ return this._removeDuplicateHiddenSingles(hiddenSingles);
93
+ }
94
+ /**
95
+ * Finds hidden singles in a specific cage using dynamic value analysis.
96
+ * @param cage - The cage to analyze
97
+ * @param puzzle - The puzzle structure containing constraints
98
+ * @param state - The current puzzle state
99
+ * @returns Array of hidden singles in this cage
100
+ */
101
+ _findHiddenSinglesInCage(cage, puzzle, state) {
102
+ const hiddenSingles = [];
103
+ const maxValue = Math.sqrt(puzzle.numRows * puzzle.numColumns);
104
+ // For each possible value, check if it can only go in one cell in this cage
105
+ for (let value = 1; value <= maxValue; value++) {
106
+ const possibleCells = [];
107
+ for (const cellId of cage.cellIds) {
108
+ const candidates = this.getCandidates(cellId, puzzle, state);
109
+ if (candidates.includes(value)) {
110
+ possibleCells.push(cellId);
111
+ }
112
+ }
113
+ // Hidden single: exactly one cell can contain this value
114
+ if (possibleCells.length === 1) {
115
+ const cellId = possibleCells[0];
116
+ const otherCandidateCells = cage.cellIds.filter((id) => id !== cellId && !state.hasValue(id));
117
+ hiddenSingles.push({
118
+ cellId,
119
+ value,
120
+ unitType: cage.cageType,
121
+ unitIndex: this._getCageDisplayIndex(cage),
122
+ otherCandidateCells
123
+ });
124
+ }
125
+ }
126
+ return hiddenSingles;
127
+ }
128
+ /**
129
+ * Gets a display-friendly index for a cage.
130
+ * @param cage - The cage to get index for
131
+ * @returns Display index for the cage
132
+ */
133
+ _getCageDisplayIndex(cage) {
134
+ // Extract numeric part from cage ID if available
135
+ const match = cage.id.match(/\d+/);
136
+ return match ? parseInt(match[0], 10) : 0;
137
+ }
138
+ /**
139
+ * Removes duplicate hidden singles (same cell and value).
140
+ * @param hiddenSingles - Array of potentially duplicate hidden singles
141
+ * @returns Array with duplicates removed
142
+ */
143
+ _removeDuplicateHiddenSingles(hiddenSingles) {
144
+ const seen = new Set();
145
+ const unique = [];
146
+ for (const hiddenSingle of hiddenSingles) {
147
+ const key = `${hiddenSingle.cellId}-${hiddenSingle.value}`;
148
+ if (!seen.has(key)) {
149
+ seen.add(key);
150
+ unique.push(hiddenSingle);
151
+ }
152
+ }
153
+ return unique;
154
+ }
155
+ /**
156
+ * Creates a hint for a specific hidden single.
157
+ * @param hiddenSingle - The hidden single information
158
+ * @param puzzle - The puzzle structure containing constraints
159
+ * @param state - The current puzzle state
160
+ * @returns A complete hint for this hidden single
161
+ */
162
+ _createHiddenSingleHint(hiddenSingle, __puzzle, __state) {
163
+ const { cellId, value, unitType, unitIndex } = hiddenSingle;
164
+ // Create cell action to set the value
165
+ const cellAction = this.createCellAction(cellId, 'set-value', value, `Only cell in ${unitType} ${unitIndex} that can contain ${value}`);
166
+ // Create relevant cells grouping
167
+ const relevantCells = this.createRelevantCells([cellId], // Primary: the cell with the hidden single
168
+ hiddenSingle.otherCandidateCells, // Secondary: other empty cells in the unit
169
+ [] // Affected: none for hidden singles
170
+ );
171
+ // Create explanations at different levels
172
+ const unitName = this._getUnitName(unitType, unitIndex);
173
+ const explanations = [
174
+ this.createExplanation('brief', 'Hidden Single', `In ${unitName}, the value ${value} can only go in cell ${cellId}.`, [`Set ${cellId} = ${value}`], ['Look for values that can only go in one cell within a unit']),
175
+ this.createExplanation('detailed', 'Hidden Single Analysis', `In ${unitName}, the value ${value} can only be placed in cell ${cellId}. While this cell ` +
176
+ `may have other candidates, ${value} has no other valid positions in this ${unitType}.`, [
177
+ `Examine all empty cells in ${unitName}`,
178
+ `For each empty cell, determine which values are possible candidates`,
179
+ `Check where the value ${value} can be placed`,
180
+ `Identify that ${value} can only go in ${cellId}`,
181
+ `Set ${cellId} = ${value}`
182
+ ], [
183
+ 'Hidden singles focus on where a value can go, not what can go in a cell',
184
+ 'Check each value 1-9 systematically within each unit',
185
+ 'A value with only one possible position must go there'
186
+ ]),
187
+ this.createExplanation('educational', 'Understanding Hidden Singles', `A hidden single occurs when a particular value can only be placed in one cell within a ` +
188
+ `row, column, or 3x3 box. In ${unitName}, we need to place the value ${value} somewhere, ` +
189
+ `but due to constraints from other filled cells, it can only go in ${cellId}. This technique ` +
190
+ `is called "hidden" because the single placement might not be obvious when looking at the ` +
191
+ `cell's candidates alone - you need to consider the entire unit.`, [
192
+ `Focus on ${unitName}`,
193
+ `Identify that this unit needs the value ${value} to be placed somewhere`,
194
+ `Examine each empty cell in this ${unitType} to see if ${value} could be placed there`,
195
+ `Consider the constraints from the cell's row, column, and box`,
196
+ `Eliminate cells where ${value} cannot go due to conflicts`,
197
+ `Confirm that ${cellId} is the only remaining option for ${value}`,
198
+ `Place ${value} in cell ${cellId}`
199
+ ], [
200
+ 'Hidden singles complement naked singles - use both techniques together',
201
+ 'Systematically check each value 1-9 in each row, column, and box',
202
+ 'This technique often reveals itself after placing other numbers',
203
+ 'Hidden singles can exist even when a cell has many candidates'
204
+ ])
205
+ ];
206
+ return this.createHint([cellAction], relevantCells, explanations, types_1.ConfidenceLevels.HIGH);
207
+ }
208
+ /**
209
+ * Gets a human-readable name for a unit.
210
+ * @param unitType - The type of unit
211
+ * @param unitIndex - The index of the unit
212
+ * @returns Human-readable unit name
213
+ */
214
+ _getUnitName(unitType, unitIndex) {
215
+ switch (unitType) {
216
+ case 'row':
217
+ return `row ${unitIndex + 1}`;
218
+ case 'column':
219
+ return `column ${unitIndex + 1}`;
220
+ /* c8 ignore next 4 - unreachable: puzzles use 'section' cage type, not 'box' */
221
+ case 'box':
222
+ const boxRow = Math.floor(unitIndex / 3);
223
+ const boxCol = unitIndex % 3;
224
+ return `box (${boxRow + 1},${boxCol + 1})`;
225
+ }
226
+ return `${unitType} ${unitIndex}`;
227
+ }
228
+ /**
229
+ * Static factory method to create a new HiddenSinglesProvider.
230
+ * @returns Result containing the new provider
231
+ */
232
+ static create() {
233
+ return (0, ts_utils_1.succeed)(new HiddenSinglesProvider());
234
+ }
235
+ }
236
+ exports.HiddenSinglesProvider = HiddenSinglesProvider;
237
+ //# sourceMappingURL=hiddenSingles.js.map
@@ -0,0 +1,95 @@
1
+ import { Result } from '@fgv/ts-utils';
2
+ import { Puzzle, PuzzleState } from '../common';
3
+ import { IHintProvider, IHintRegistry } from './interfaces';
4
+ import { DifficultyLevel, IHint, IHintGenerationOptions, TechniqueId } from './types';
5
+ /**
6
+ * Implementation of the hint registry that manages and coordinates multiple hint providers.
7
+ * @public
8
+ */
9
+ export declare class HintRegistry implements IHintRegistry {
10
+ private readonly _providers;
11
+ /**
12
+ * Creates a new HintRegistry instance.
13
+ */
14
+ constructor();
15
+ /**
16
+ * Creates a new HintRegistry with the specified providers pre-registered.
17
+ * @param providers - The providers to register
18
+ * @returns Result containing the new registry or failure if registration fails
19
+ */
20
+ static create(providers?: readonly IHintProvider[]): Result<HintRegistry>;
21
+ /**
22
+ * Registers a new hint provider.
23
+ * @param provider - The provider to register
24
+ * @returns Result indicating success or failure of registration
25
+ */
26
+ registerProvider(provider: IHintProvider): Result<void>;
27
+ /**
28
+ * Unregisters a hint provider.
29
+ * @param techniqueId - The ID of the technique to unregister
30
+ * @returns Result indicating success or failure of unregistration
31
+ */
32
+ unregisterProvider(techniqueId: TechniqueId): Result<void>;
33
+ /**
34
+ * Gets a specific provider by technique ID.
35
+ * @param techniqueId - The ID of the technique
36
+ * @returns Result containing the provider, or failure if not found
37
+ */
38
+ getProvider(techniqueId: TechniqueId): Result<IHintProvider>;
39
+ /**
40
+ * Gets all registered providers, optionally filtered by criteria.
41
+ * @param options - Optional filtering options
42
+ * @returns Array of providers matching the criteria
43
+ */
44
+ getProviders(options?: IHintGenerationOptions): readonly IHintProvider[];
45
+ /**
46
+ * Generates hints using all applicable providers.
47
+ * @param puzzle - The puzzle structure containing constraints
48
+ * @param state - The current puzzle state
49
+ * @param options - Optional generation options
50
+ * @returns Result containing array of hints from all providers
51
+ */
52
+ generateAllHints(puzzle: Puzzle, state: PuzzleState, options?: IHintGenerationOptions): Result<readonly IHint[]>;
53
+ /**
54
+ * Gets the best available hint based on difficulty and confidence.
55
+ * @param puzzle - The puzzle structure containing constraints
56
+ * @param state - The current puzzle state
57
+ * @param options - Optional generation options
58
+ * @returns Result containing the best hint, or failure if no hints available
59
+ */
60
+ getBestHint(puzzle: Puzzle, state: PuzzleState, options?: IHintGenerationOptions): Result<IHint>;
61
+ /**
62
+ * Gets the number of registered providers.
63
+ * @returns The number of registered providers
64
+ */
65
+ get providerCount(): number;
66
+ /**
67
+ * Gets all registered technique IDs.
68
+ * @returns Array of technique IDs
69
+ */
70
+ getRegisteredTechniques(): readonly TechniqueId[];
71
+ /**
72
+ * Checks if a specific technique is registered.
73
+ * @param techniqueId - The technique ID to check
74
+ * @returns true if the technique is registered
75
+ */
76
+ hasProvider(techniqueId: TechniqueId): boolean;
77
+ /**
78
+ * Clears all registered providers.
79
+ * @returns Result indicating success
80
+ */
81
+ clear(): Result<void>;
82
+ /**
83
+ * Gets providers grouped by difficulty level.
84
+ * @returns Map of difficulty levels to providers
85
+ */
86
+ getProvidersByDifficulty(): Map<DifficultyLevel, readonly IHintProvider[]>;
87
+ /**
88
+ * Applies global filtering options to the collected hints.
89
+ * @param hints - The hints to filter
90
+ * @param options - The filtering options
91
+ * @returns Result containing the filtered hints
92
+ */
93
+ private _applyGlobalFiltering;
94
+ }
95
+ //# sourceMappingURL=hintRegistry.d.ts.map