@fgv/ts-sudoku-lib 5.0.1-9 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.browser.js +37 -0
- package/dist/index.js +30 -0
- package/dist/packlets/collections/collections.js +136 -0
- package/dist/packlets/collections/data/puzzles.json +56 -0
- package/dist/packlets/collections/index.js +25 -0
- package/dist/packlets/common/cage.js +83 -0
- package/dist/packlets/common/cell.js +88 -0
- package/dist/packlets/common/common.js +70 -0
- package/dist/packlets/common/converters.js +65 -0
- package/dist/packlets/common/ids.js +152 -0
- package/dist/packlets/common/index.js +36 -0
- package/dist/packlets/common/logging.js +32 -0
- package/dist/packlets/common/public.js +25 -0
- package/dist/packlets/common/puzzle.js +411 -0
- package/dist/packlets/common/puzzleDefinitions.js +247 -0
- package/dist/packlets/common/puzzleSession.js +369 -0
- package/dist/packlets/common/puzzleState.js +99 -0
- package/dist/packlets/files/converters.js +67 -0
- package/dist/packlets/files/fileTreeHelpers.js +37 -0
- package/dist/packlets/files/filesystem.js +37 -0
- package/dist/packlets/files/index.browser.js +32 -0
- package/dist/packlets/files/index.js +30 -0
- package/dist/packlets/files/model.js +25 -0
- package/dist/packlets/hints/baseHintProvider.js +199 -0
- package/dist/packlets/hints/explanations.js +270 -0
- package/dist/packlets/hints/hiddenSingles.js +233 -0
- package/dist/packlets/hints/hintRegistry.js +222 -0
- package/dist/packlets/hints/hints.js +311 -0
- package/dist/packlets/hints/index.js +39 -0
- package/dist/packlets/hints/interfaces.js +25 -0
- package/dist/packlets/hints/nakedSingles.js +198 -0
- package/dist/packlets/hints/puzzleSessionHints.js +495 -0
- package/dist/packlets/hints/types.js +43 -0
- package/dist/packlets/puzzles/anyPuzzle.js +47 -0
- package/dist/packlets/puzzles/index.js +31 -0
- package/dist/packlets/puzzles/internal/combinationCache.js +75 -0
- package/dist/packlets/puzzles/internal/combinationGenerator.js +142 -0
- package/dist/packlets/puzzles/internal/possibilityAnalyzer.js +121 -0
- package/dist/packlets/puzzles/killerCombinations.js +222 -0
- package/dist/packlets/puzzles/killerCombinationsTypes.js +25 -0
- package/dist/packlets/puzzles/killerSudokuPuzzle.js +136 -0
- package/dist/packlets/puzzles/sudokuPuzzle.js +43 -0
- package/dist/packlets/puzzles/sudokuXPuzzle.js +65 -0
- package/dist/test/unit/helpers/puzzleBuilders.js +149 -0
- package/dist/ts-sudoku-lib.d.ts +12 -7
- package/dist/tsdoc-metadata.json +1 -1
- package/lib/index.browser.d.ts +7 -0
- package/lib/index.browser.js +78 -0
- package/lib/packlets/files/fileTreeHelpers.d.ts +13 -0
- package/lib/packlets/files/fileTreeHelpers.js +40 -0
- package/lib/packlets/files/index.browser.d.ts +5 -0
- package/lib/packlets/files/index.browser.js +70 -0
- package/lib/packlets/files/index.d.ts +3 -2
- package/lib/packlets/files/index.js +7 -4
- package/package.json +19 -5
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* MIT License
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2023 Erik Fortune
|
|
5
|
+
*
|
|
6
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
* in the Software without restriction, including without limitation the rights
|
|
9
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
* furnished to do so, subject to the following conditions:
|
|
12
|
+
*
|
|
13
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
* copies or substantial portions of the Software.
|
|
15
|
+
*
|
|
16
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
* SOFTWARE.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Standard technique identifiers for built-in solving techniques.
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
export const TechniqueIds = {
|
|
29
|
+
NAKED_SINGLES: 'naked-singles',
|
|
30
|
+
HIDDEN_SINGLES: 'hidden-singles'
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Standard confidence levels as branded types.
|
|
34
|
+
* @public
|
|
35
|
+
*/
|
|
36
|
+
export const ConfidenceLevels = {
|
|
37
|
+
LOW: 1,
|
|
38
|
+
MEDIUM_LOW: 2,
|
|
39
|
+
MEDIUM: 3,
|
|
40
|
+
MEDIUM_HIGH: 4,
|
|
41
|
+
HIGH: 5
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* MIT License
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2023 Erik Fortune
|
|
5
|
+
*
|
|
6
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
* in the Software without restriction, including without limitation the rights
|
|
9
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
* furnished to do so, subject to the following conditions:
|
|
12
|
+
*
|
|
13
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
* copies or substantial portions of the Software.
|
|
15
|
+
*
|
|
16
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
* SOFTWARE.
|
|
23
|
+
*/
|
|
24
|
+
import { fail } from '@fgv/ts-utils';
|
|
25
|
+
import { KillerSudokuPuzzle } from './killerSudokuPuzzle';
|
|
26
|
+
import { SudokuPuzzle } from './sudokuPuzzle';
|
|
27
|
+
import { SudokuXPuzzle } from './sudokuXPuzzle';
|
|
28
|
+
/**
|
|
29
|
+
* Static class to instantiate any puzzle from a {@link IPuzzleDefinition | puzzle definition}.
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
32
|
+
export class AnyPuzzle {
|
|
33
|
+
static create(puzzle) {
|
|
34
|
+
switch (puzzle.type) {
|
|
35
|
+
case 'sudoku':
|
|
36
|
+
return SudokuPuzzle.create(puzzle);
|
|
37
|
+
case 'sudoku-x':
|
|
38
|
+
return SudokuXPuzzle.create(puzzle);
|
|
39
|
+
case 'killer-sudoku':
|
|
40
|
+
return KillerSudokuPuzzle.create(puzzle);
|
|
41
|
+
/* c8 ignore next 2 */
|
|
42
|
+
default:
|
|
43
|
+
return fail(`Puzzle '${puzzle.description}' unsupported type ${puzzle.type}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=anyPuzzle.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* MIT License
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2023 Erik Fortune
|
|
5
|
+
*
|
|
6
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
* in the Software without restriction, including without limitation the rights
|
|
9
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
* furnished to do so, subject to the following conditions:
|
|
12
|
+
*
|
|
13
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
* copies or substantial portions of the Software.
|
|
15
|
+
*
|
|
16
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
* SOFTWARE.
|
|
23
|
+
*/
|
|
24
|
+
import { KillerSudokuPuzzle as Killer } from './killerSudokuPuzzle';
|
|
25
|
+
import { SudokuPuzzle as Sudoku } from './sudokuPuzzle';
|
|
26
|
+
import { SudokuXPuzzle as SudokuX } from './sudokuXPuzzle';
|
|
27
|
+
import { KillerCombinations } from './killerCombinations';
|
|
28
|
+
import { AnyPuzzle as Any } from './anyPuzzle';
|
|
29
|
+
export * from './killerCombinationsTypes';
|
|
30
|
+
export { Any, Killer, KillerCombinations, Sudoku, SudokuX };
|
|
31
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* MIT License
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2023 Erik Fortune
|
|
5
|
+
*
|
|
6
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
* in the Software without restriction, including without limitation the rights
|
|
9
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
* furnished to do so, subject to the following conditions:
|
|
12
|
+
*
|
|
13
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
* copies or substantial portions of the Software.
|
|
15
|
+
*
|
|
16
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
* SOFTWARE.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export class CombinationCache {
|
|
28
|
+
/**
|
|
29
|
+
* Generate cache key for combination parameters.
|
|
30
|
+
*/
|
|
31
|
+
static generateKey(cageSize, total, constraints) {
|
|
32
|
+
var _a, _b, _c, _d;
|
|
33
|
+
const excluded = (_b = (_a = constraints === null || constraints === void 0 ? void 0 : constraints.excludedNumbers) === null || _a === void 0 ? void 0 : _a.slice().sort().join(',')) !== null && _b !== void 0 ? _b : '';
|
|
34
|
+
const required = (_d = (_c = constraints === null || constraints === void 0 ? void 0 : constraints.requiredNumbers) === null || _c === void 0 ? void 0 : _c.slice().sort().join(',')) !== null && _d !== void 0 ? _d : '';
|
|
35
|
+
return `${cageSize}:${total}:${excluded}:${required}`;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Get cached combination result.
|
|
39
|
+
*/
|
|
40
|
+
static get(key) {
|
|
41
|
+
const result = this._cache.get(key);
|
|
42
|
+
if (result) {
|
|
43
|
+
// Deep clone to prevent mutation of cached data
|
|
44
|
+
return result.map((combination) => [...combination]);
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Store combination result in cache.
|
|
50
|
+
*/
|
|
51
|
+
static set(key, combinations) {
|
|
52
|
+
// Implement simple LRU by clearing cache when it gets too large
|
|
53
|
+
if (this._cache.size >= this._maxCacheSize) {
|
|
54
|
+
this.clear();
|
|
55
|
+
}
|
|
56
|
+
// Deep clone to prevent mutation of cached data
|
|
57
|
+
const clonedCombinations = combinations.map((combination) => [...combination]);
|
|
58
|
+
this._cache.set(key, clonedCombinations);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Clear the entire cache.
|
|
62
|
+
*/
|
|
63
|
+
static clear() {
|
|
64
|
+
this._cache.clear();
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get current cache size (for testing/debugging).
|
|
68
|
+
*/
|
|
69
|
+
static size() {
|
|
70
|
+
return this._cache.size;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
CombinationCache._cache = new Map();
|
|
74
|
+
CombinationCache._maxCacheSize = 1000;
|
|
75
|
+
//# sourceMappingURL=combinationCache.js.map
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* MIT License
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2023 Erik Fortune
|
|
5
|
+
*
|
|
6
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
* in the Software without restriction, including without limitation the rights
|
|
9
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
* furnished to do so, subject to the following conditions:
|
|
12
|
+
*
|
|
13
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
* copies or substantial portions of the Software.
|
|
15
|
+
*
|
|
16
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
* SOFTWARE.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export class CombinationGenerator {
|
|
28
|
+
/**
|
|
29
|
+
* Generates all possible combinations of unique numbers that sum to the target total.
|
|
30
|
+
* @param cageSize - Number of cells in the cage
|
|
31
|
+
* @param total - Target sum
|
|
32
|
+
* @param constraints - Optional constraints on included/excluded numbers
|
|
33
|
+
* @param maxValue - Maximum value allowed in cells (defaults to 9)
|
|
34
|
+
* @returns Array of combinations, each sorted in ascending order
|
|
35
|
+
*/
|
|
36
|
+
static generate(cageSize, total, constraints, maxValue = 9) {
|
|
37
|
+
var _a, _b;
|
|
38
|
+
const results = [];
|
|
39
|
+
const excludedSet = new Set((_a = constraints === null || constraints === void 0 ? void 0 : constraints.excludedNumbers) !== null && _a !== void 0 ? _a : []);
|
|
40
|
+
const requiredNumbers = (_b = constraints === null || constraints === void 0 ? void 0 : constraints.requiredNumbers) !== null && _b !== void 0 ? _b : [];
|
|
41
|
+
const requiredSet = new Set(requiredNumbers);
|
|
42
|
+
// Validate that we can satisfy required numbers constraint
|
|
43
|
+
if (requiredNumbers.length > cageSize) {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
// Check if required numbers sum is already too high
|
|
47
|
+
const requiredSum = requiredNumbers.reduce((sum, num) => sum + num, 0);
|
|
48
|
+
if (requiredSum > total) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
// Calculate remaining constraints after accounting for required numbers
|
|
52
|
+
const remainingSize = cageSize - requiredNumbers.length;
|
|
53
|
+
const remainingTotal = total - requiredSum;
|
|
54
|
+
if (remainingSize === 0) {
|
|
55
|
+
// Only required numbers should be present
|
|
56
|
+
/* c8 ignore next 3 - edge case: required numbers fill cage but sum doesn't match (tested via KillerCombinations) */
|
|
57
|
+
if (remainingTotal === 0) {
|
|
58
|
+
return [Array.from(requiredNumbers).sort()];
|
|
59
|
+
}
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
// Generate combinations for the remaining slots
|
|
63
|
+
const availableNumbers = [];
|
|
64
|
+
for (let i = 1; i <= maxValue; i++) {
|
|
65
|
+
if (!excludedSet.has(i) && !requiredSet.has(i)) {
|
|
66
|
+
availableNumbers.push(i);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
this._generateCombinations(availableNumbers, remainingSize, remainingTotal, 0, [], results);
|
|
70
|
+
// Add required numbers to each result and sort
|
|
71
|
+
return results.map((combination) => [...combination, ...requiredNumbers].sort((a, b) => a - b));
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Recursive helper to generate combinations.
|
|
75
|
+
* @param availableNumbers - Numbers that can be used
|
|
76
|
+
* @param targetSize - Number of numbers needed
|
|
77
|
+
* @param targetSum - Target sum for the combination
|
|
78
|
+
* @param startIndex - Starting index in available numbers
|
|
79
|
+
* @param currentCombination - Current partial combination
|
|
80
|
+
* @param results - Array to collect results
|
|
81
|
+
*/
|
|
82
|
+
static _generateCombinations(availableNumbers, targetSize, targetSum, startIndex, currentCombination, results) {
|
|
83
|
+
// Base case: we have the right number of elements
|
|
84
|
+
if (currentCombination.length === targetSize) {
|
|
85
|
+
const sum = currentCombination.reduce((total, num) => total + num, 0);
|
|
86
|
+
if (sum === targetSum) {
|
|
87
|
+
results.push([...currentCombination]);
|
|
88
|
+
}
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// Early termination: impossible to reach target
|
|
92
|
+
const remainingSlots = targetSize - currentCombination.length;
|
|
93
|
+
const currentSum = currentCombination.reduce((total, num) => total + num, 0);
|
|
94
|
+
const remainingSum = targetSum - currentSum;
|
|
95
|
+
// Check minimum possible sum with remaining slots
|
|
96
|
+
const minPossible = this._calculateMinSum(availableNumbers, startIndex, remainingSlots);
|
|
97
|
+
if (remainingSum < minPossible) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
// Check maximum possible sum with remaining slots
|
|
101
|
+
const maxPossible = this._calculateMaxSum(availableNumbers, startIndex, remainingSlots);
|
|
102
|
+
if (remainingSum > maxPossible) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
// Try each available number
|
|
106
|
+
for (let i = startIndex; i < availableNumbers.length; i++) {
|
|
107
|
+
const number = availableNumbers[i];
|
|
108
|
+
// Skip if adding this number would exceed our target
|
|
109
|
+
if (currentSum + number > targetSum) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
currentCombination.push(number);
|
|
113
|
+
this._generateCombinations(availableNumbers, targetSize, targetSum, i + 1, currentCombination, results);
|
|
114
|
+
currentCombination.pop();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Calculate minimum possible sum for remaining slots.
|
|
119
|
+
*/
|
|
120
|
+
static _calculateMinSum(availableNumbers, startIndex, remainingSlots) {
|
|
121
|
+
let sum = 0;
|
|
122
|
+
let count = 0;
|
|
123
|
+
for (let i = startIndex; i < availableNumbers.length && count < remainingSlots; i++) {
|
|
124
|
+
sum += availableNumbers[i];
|
|
125
|
+
count++;
|
|
126
|
+
}
|
|
127
|
+
return sum;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Calculate maximum possible sum for remaining slots.
|
|
131
|
+
*/
|
|
132
|
+
static _calculateMaxSum(availableNumbers, startIndex, remainingSlots) {
|
|
133
|
+
let sum = 0;
|
|
134
|
+
let count = 0;
|
|
135
|
+
for (let i = availableNumbers.length - 1; i >= startIndex && count < remainingSlots; i--) {
|
|
136
|
+
sum += availableNumbers[i];
|
|
137
|
+
count++;
|
|
138
|
+
}
|
|
139
|
+
return sum;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=combinationGenerator.js.map
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* MIT License
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2023 Erik Fortune
|
|
5
|
+
*
|
|
6
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
* in the Software without restriction, including without limitation the rights
|
|
9
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
* furnished to do so, subject to the following conditions:
|
|
12
|
+
*
|
|
13
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
* copies or substantial portions of the Software.
|
|
15
|
+
*
|
|
16
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
* SOFTWARE.
|
|
23
|
+
*/
|
|
24
|
+
import { succeed } from '@fgv/ts-utils';
|
|
25
|
+
/**
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
export class PossibilityAnalyzer {
|
|
29
|
+
/**
|
|
30
|
+
* Analyze possible values for each cell in a killer cage based on current puzzle state.
|
|
31
|
+
* @param puzzle - The puzzle instance
|
|
32
|
+
* @param state - Current puzzle state
|
|
33
|
+
* @param cage - The killer cage to analyze
|
|
34
|
+
* @param validCombinations - Pre-computed valid combinations for the cage
|
|
35
|
+
* @returns Map of CellId to possible number arrays
|
|
36
|
+
*/
|
|
37
|
+
static analyze(puzzle, state, cage, validCombinations) {
|
|
38
|
+
const possibilities = new Map();
|
|
39
|
+
// Get current values in the cage
|
|
40
|
+
// We need the concrete Cage class for containedValues method
|
|
41
|
+
const concreteCage = cage;
|
|
42
|
+
const currentValues = concreteCage.containedValues(state);
|
|
43
|
+
const emptyCells = [];
|
|
44
|
+
// Identify empty cells and initialize possibilities
|
|
45
|
+
for (const cellId of cage.cellIds) {
|
|
46
|
+
const cellContents = state.getCellContents(cellId).orDefault();
|
|
47
|
+
/* c8 ignore next 1 - ? is defense in depth */
|
|
48
|
+
if ((cellContents === null || cellContents === void 0 ? void 0 : cellContents.value) !== undefined) {
|
|
49
|
+
// Cell already has a value - set empty possibilities array
|
|
50
|
+
possibilities.set(cellId, []);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
// Empty cell
|
|
54
|
+
emptyCells.push(cellId);
|
|
55
|
+
possibilities.set(cellId, []);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Filter combinations that are compatible with current state
|
|
59
|
+
const compatibleCombinations = validCombinations.filter((combination) => {
|
|
60
|
+
// Check if combination contains all current values
|
|
61
|
+
for (const currentValue of currentValues) {
|
|
62
|
+
if (!combination.includes(currentValue)) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Check if combination has the right number of remaining values
|
|
67
|
+
const remainingValues = combination.filter((value) => !currentValues.has(value));
|
|
68
|
+
return remainingValues.length === emptyCells.length;
|
|
69
|
+
});
|
|
70
|
+
// If no compatible combinations exist, return empty possibilities for all cells
|
|
71
|
+
if (compatibleCombinations.length === 0) {
|
|
72
|
+
return succeed(possibilities);
|
|
73
|
+
}
|
|
74
|
+
// For each empty cell, find which values are possible
|
|
75
|
+
for (const cellId of emptyCells) {
|
|
76
|
+
const cellPossibilities = new Set();
|
|
77
|
+
// Get the cell to check sudoku constraints
|
|
78
|
+
const cellResult = puzzle.getCell(cellId);
|
|
79
|
+
/* c8 ignore next 3 - defensive coding: protects against internal cage/puzzle state corruption */
|
|
80
|
+
if (cellResult.isFailure()) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const cell = cellResult.value;
|
|
84
|
+
// Check each compatible combination
|
|
85
|
+
for (const combination of compatibleCombinations) {
|
|
86
|
+
const remainingValues = combination.filter((value) => !currentValues.has(value));
|
|
87
|
+
// Check if each remaining value could go in this cell
|
|
88
|
+
for (const value of remainingValues) {
|
|
89
|
+
if (this._canPlaceValueInCell(cell, value, state)) {
|
|
90
|
+
cellPossibilities.add(value);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
possibilities.set(cellId, Array.from(cellPossibilities).sort());
|
|
95
|
+
}
|
|
96
|
+
return succeed(possibilities);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Check if a value can be placed in a cell considering sudoku constraints.
|
|
100
|
+
* @param cell - The cell to check
|
|
101
|
+
* @param value - The value to place
|
|
102
|
+
* @param state - Current puzzle state
|
|
103
|
+
* @returns True if the value can be placed
|
|
104
|
+
*/
|
|
105
|
+
static _canPlaceValueInCell(cell, value, state) {
|
|
106
|
+
// Check all cages this cell belongs to for sudoku constraints
|
|
107
|
+
for (const cage of cell.cages) {
|
|
108
|
+
if (cage.cageType === 'killer') {
|
|
109
|
+
// Skip killer cage check - we're already analyzing within killer constraints
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
// Check if value already exists in this constraint cage (row, column, section, etc.)
|
|
113
|
+
const concreteCage = cage;
|
|
114
|
+
if (concreteCage.containsValue(value, state, [cell.id])) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=possibilityAnalyzer.js.map
|