logic-puzzle-generator 1.0.0
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/LICENSE +21 -0
- package/README.md +221 -0
- package/dist/Clue.d.ts +81 -0
- package/dist/Clue.js +37 -0
- package/dist/Generator.d.ts +58 -0
- package/dist/Generator.js +433 -0
- package/dist/LogicGrid.d.ts +70 -0
- package/dist/LogicGrid.js +188 -0
- package/dist/Solver.d.ts +29 -0
- package/dist/Solver.js +242 -0
- package/dist/examples/benchmark.d.ts +1 -0
- package/dist/examples/benchmark.js +129 -0
- package/dist/examples/cli.d.ts +1 -0
- package/dist/examples/cli.js +84 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +21 -0
- package/dist/run_generator.d.ts +1 -0
- package/dist/run_generator.js +84 -0
- package/dist/src/defaults.d.ts +5 -0
- package/dist/src/defaults.js +24 -0
- package/dist/src/engine/BoundsCalculator.d.ts +6 -0
- package/dist/src/engine/BoundsCalculator.js +40 -0
- package/dist/src/engine/Clue.d.ts +75 -0
- package/dist/src/engine/Clue.js +10 -0
- package/dist/src/engine/DifficultyBounds.d.ts +12 -0
- package/dist/src/engine/DifficultyBounds.js +67 -0
- package/dist/src/engine/GenerativeSession.d.ts +32 -0
- package/dist/src/engine/GenerativeSession.js +109 -0
- package/dist/src/engine/Generator.d.ts +119 -0
- package/dist/src/engine/Generator.js +1058 -0
- package/dist/src/engine/LogicGrid.d.ts +70 -0
- package/dist/src/engine/LogicGrid.js +190 -0
- package/dist/src/engine/Solver.d.ts +30 -0
- package/dist/src/engine/Solver.js +613 -0
- package/dist/src/errors.d.ts +12 -0
- package/dist/src/errors.js +23 -0
- package/dist/src/index.d.ts +8 -0
- package/dist/src/index.js +24 -0
- package/dist/src/scripts/GenerateBoundsDeprecated.d.ts +1 -0
- package/dist/src/scripts/GenerateBoundsDeprecated.js +27 -0
- package/dist/src/scripts/StressTestBacktracking.d.ts +1 -0
- package/dist/src/scripts/StressTestBacktracking.js +49 -0
- package/dist/src/types.d.ts +86 -0
- package/dist/src/types.js +58 -0
- package/dist/types.d.ts +37 -0
- package/dist/types.js +13 -0
- package/package.json +40 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { CategoryConfig, ValueLabel } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Represents the state of the logic puzzle grid.
|
|
4
|
+
*
|
|
5
|
+
* It manages the possibilities between every pair of values across different categories.
|
|
6
|
+
* The grid is initialized where all connections are possible (true).
|
|
7
|
+
* As clues are applied, possibilities are eliminated (set to false).
|
|
8
|
+
*/
|
|
9
|
+
export declare class LogicGrid {
|
|
10
|
+
private grid;
|
|
11
|
+
private categories;
|
|
12
|
+
private valueMap;
|
|
13
|
+
/**
|
|
14
|
+
* Creates a new LogicGrid instance.
|
|
15
|
+
*
|
|
16
|
+
* @param categories - The configuration of categories and their values for the puzzle.
|
|
17
|
+
* @throws {Error} If the configuration is invalid (duplicate IDs, duplicate values, or mismatched value counts).
|
|
18
|
+
*/
|
|
19
|
+
constructor(categories: CategoryConfig[]);
|
|
20
|
+
private validateConfig;
|
|
21
|
+
/**
|
|
22
|
+
* Sets the possibility state between two values from different categories.
|
|
23
|
+
*
|
|
24
|
+
* @param cat1Id - The ID of the first category.
|
|
25
|
+
* @param val1 - The value from the first category.
|
|
26
|
+
* @param cat2Id - The ID of the second category.
|
|
27
|
+
* @param val2 - The value from the second category.
|
|
28
|
+
* @param state - true if the connection is possible, false if eliminated.
|
|
29
|
+
*/
|
|
30
|
+
setPossibility(cat1Id: string, val1: ValueLabel, cat2Id: string, val2: ValueLabel, state: boolean): void;
|
|
31
|
+
/**
|
|
32
|
+
* Checks if a connection between two values is currently possible.
|
|
33
|
+
*
|
|
34
|
+
* @param cat1Id - The ID of the first category.
|
|
35
|
+
* @param val1 - The value from the first category.
|
|
36
|
+
* @param cat2Id - The ID of the second category.
|
|
37
|
+
* @param val2 - The value from the second category.
|
|
38
|
+
* @returns true if the connection is possible, false otherwise.
|
|
39
|
+
*/
|
|
40
|
+
isPossible(cat1Id: string, val1: ValueLabel, cat2Id: string, val2: ValueLabel): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Gets the number of possible connections for a specific value in one category
|
|
43
|
+
* relative to another category.
|
|
44
|
+
*
|
|
45
|
+
* @param cat1Id - The ID of the starting category.
|
|
46
|
+
* @param val1 - The value from the starting category.
|
|
47
|
+
* @param cat2Id - The target category ID.
|
|
48
|
+
* @returns The number of values in cat2 that are still possible for val1.
|
|
49
|
+
*/
|
|
50
|
+
getPossibilitiesCount(cat1Id: string, val1: ValueLabel, cat2Id: string): number;
|
|
51
|
+
/**
|
|
52
|
+
* Calculates statistics about the current state of the grid.
|
|
53
|
+
*
|
|
54
|
+
* @returns An object containing:
|
|
55
|
+
* - totalPossible: The initial total logical connections.
|
|
56
|
+
* - currentPossible: The number of remaining possible connections.
|
|
57
|
+
* - solutionPossible: The target number of connections for a solved grid.
|
|
58
|
+
*/
|
|
59
|
+
getGridStats(): {
|
|
60
|
+
totalPossible: number;
|
|
61
|
+
currentPossible: number;
|
|
62
|
+
solutionPossible: number;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Creates a deep copy of the current LogicGrid.
|
|
66
|
+
*
|
|
67
|
+
* @returns A new LogicGrid instance with the exact same state.
|
|
68
|
+
*/
|
|
69
|
+
clone(): LogicGrid;
|
|
70
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LogicGrid = void 0;
|
|
4
|
+
const errors_1 = require("../errors");
|
|
5
|
+
/**
|
|
6
|
+
* Represents the state of the logic puzzle grid.
|
|
7
|
+
*
|
|
8
|
+
* It manages the possibilities between every pair of values across different categories.
|
|
9
|
+
* The grid is initialized where all connections are possible (true).
|
|
10
|
+
* As clues are applied, possibilities are eliminated (set to false).
|
|
11
|
+
*/
|
|
12
|
+
class LogicGrid {
|
|
13
|
+
/**
|
|
14
|
+
* Creates a new LogicGrid instance.
|
|
15
|
+
*
|
|
16
|
+
* @param categories - The configuration of categories and their values for the puzzle.
|
|
17
|
+
* @throws {Error} If the configuration is invalid (duplicate IDs, duplicate values, or mismatched value counts).
|
|
18
|
+
*/
|
|
19
|
+
constructor(categories) {
|
|
20
|
+
this.validateConfig(categories);
|
|
21
|
+
this.categories = categories;
|
|
22
|
+
this.valueMap = new Map(categories.map(c => [c.id, new Map(c.values.map((v, i) => [v, i]))]));
|
|
23
|
+
this.grid = new Map();
|
|
24
|
+
for (const cat1 of categories) {
|
|
25
|
+
const cat1Map = new Map();
|
|
26
|
+
for (const val1 of cat1.values) {
|
|
27
|
+
const cat2Map = new Map();
|
|
28
|
+
for (const cat2 of categories) {
|
|
29
|
+
if (cat1.id === cat2.id)
|
|
30
|
+
continue;
|
|
31
|
+
cat2Map.set(cat2.id, Array(cat2.values.length).fill(true));
|
|
32
|
+
}
|
|
33
|
+
cat1Map.set(val1, cat2Map);
|
|
34
|
+
}
|
|
35
|
+
this.grid.set(cat1.id, cat1Map);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
validateConfig(categories) {
|
|
39
|
+
const catIds = new Set();
|
|
40
|
+
let expectedSize = -1;
|
|
41
|
+
for (const cat of categories) {
|
|
42
|
+
if (catIds.has(cat.id)) {
|
|
43
|
+
throw new errors_1.ConfigurationError(`Duplicate category ID found: ${cat.id}`);
|
|
44
|
+
}
|
|
45
|
+
catIds.add(cat.id);
|
|
46
|
+
const uniqueValues = new Set(cat.values);
|
|
47
|
+
if (uniqueValues.size !== cat.values.length) {
|
|
48
|
+
throw new errors_1.ConfigurationError(`Category '${cat.id}' has duplicate values.`);
|
|
49
|
+
}
|
|
50
|
+
if (expectedSize === -1) {
|
|
51
|
+
expectedSize = cat.values.length;
|
|
52
|
+
}
|
|
53
|
+
else if (cat.values.length !== expectedSize) {
|
|
54
|
+
throw new errors_1.ConfigurationError(`Category '${cat.id}' has ${cat.values.length} values, expected ${expectedSize}. All categories must be the same size.`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Sets the possibility state between two values from different categories.
|
|
60
|
+
*
|
|
61
|
+
* @param cat1Id - The ID of the first category.
|
|
62
|
+
* @param val1 - The value from the first category.
|
|
63
|
+
* @param cat2Id - The ID of the second category.
|
|
64
|
+
* @param val2 - The value from the second category.
|
|
65
|
+
* @param state - true if the connection is possible, false if eliminated.
|
|
66
|
+
*/
|
|
67
|
+
setPossibility(cat1Id, val1, cat2Id, val2, state) {
|
|
68
|
+
const val2Index = this.valueMap.get(cat2Id)?.get(val2);
|
|
69
|
+
if (val2Index !== undefined) {
|
|
70
|
+
const cat1Map = this.grid.get(cat1Id);
|
|
71
|
+
if (cat1Map) {
|
|
72
|
+
const val1Map = cat1Map.get(val1);
|
|
73
|
+
if (val1Map) {
|
|
74
|
+
const cat2Arr = val1Map.get(cat2Id);
|
|
75
|
+
if (cat2Arr) {
|
|
76
|
+
cat2Arr[val2Index] = state;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const val1Index = this.valueMap.get(cat1Id)?.get(val1);
|
|
82
|
+
if (val1Index !== undefined) {
|
|
83
|
+
const cat2Map = this.grid.get(cat2Id);
|
|
84
|
+
if (cat2Map) {
|
|
85
|
+
const val2Map = cat2Map.get(val2);
|
|
86
|
+
if (val2Map) {
|
|
87
|
+
const cat1Arr = val2Map.get(cat1Id);
|
|
88
|
+
if (cat1Arr) {
|
|
89
|
+
cat1Arr[val1Index] = state;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Checks if a connection between two values is currently possible.
|
|
97
|
+
*
|
|
98
|
+
* @param cat1Id - The ID of the first category.
|
|
99
|
+
* @param val1 - The value from the first category.
|
|
100
|
+
* @param cat2Id - The ID of the second category.
|
|
101
|
+
* @param val2 - The value from the second category.
|
|
102
|
+
* @returns true if the connection is possible, false otherwise.
|
|
103
|
+
*/
|
|
104
|
+
isPossible(cat1Id, val1, cat2Id, val2) {
|
|
105
|
+
// Identity check
|
|
106
|
+
if (cat1Id === cat2Id) {
|
|
107
|
+
return val1 === val2;
|
|
108
|
+
}
|
|
109
|
+
const val2Index = this.valueMap.get(cat2Id)?.get(val2);
|
|
110
|
+
if (val2Index === undefined)
|
|
111
|
+
return false;
|
|
112
|
+
const cat1Grid = this.grid.get(cat1Id);
|
|
113
|
+
if (!cat1Grid)
|
|
114
|
+
return false;
|
|
115
|
+
const val1Grid = cat1Grid.get(val1);
|
|
116
|
+
if (!val1Grid)
|
|
117
|
+
return false;
|
|
118
|
+
const cat2Arr = val1Grid.get(cat2Id);
|
|
119
|
+
if (!cat2Arr)
|
|
120
|
+
return false;
|
|
121
|
+
return cat2Arr[val2Index];
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Gets the number of possible connections for a specific value in one category
|
|
125
|
+
* relative to another category.
|
|
126
|
+
*
|
|
127
|
+
* @param cat1Id - The ID of the starting category.
|
|
128
|
+
* @param val1 - The value from the starting category.
|
|
129
|
+
* @param cat2Id - The target category ID.
|
|
130
|
+
* @returns The number of values in cat2 that are still possible for val1.
|
|
131
|
+
*/
|
|
132
|
+
getPossibilitiesCount(cat1Id, val1, cat2Id) {
|
|
133
|
+
const possibilities = this.grid.get(cat1Id)?.get(val1)?.get(cat2Id);
|
|
134
|
+
if (!possibilities)
|
|
135
|
+
return 0;
|
|
136
|
+
return possibilities.filter(p => p).length;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Calculates statistics about the current state of the grid.
|
|
140
|
+
*
|
|
141
|
+
* @returns An object containing:
|
|
142
|
+
* - totalPossible: The initial total logical connections.
|
|
143
|
+
* - currentPossible: The number of remaining possible connections.
|
|
144
|
+
* - solutionPossible: The target number of connections for a solved grid.
|
|
145
|
+
*/
|
|
146
|
+
getGridStats() {
|
|
147
|
+
let currentPossible = 0;
|
|
148
|
+
const catCount = this.categories.length;
|
|
149
|
+
const valCount = this.categories[0]?.values.length || 0;
|
|
150
|
+
if (valCount === 0 || catCount < 2) {
|
|
151
|
+
return { totalPossible: 0, currentPossible: 0, solutionPossible: 0 };
|
|
152
|
+
}
|
|
153
|
+
const numPairs = catCount * (catCount - 1) / 2;
|
|
154
|
+
const totalPossible = numPairs * valCount * valCount;
|
|
155
|
+
const solutionPossible = numPairs * valCount;
|
|
156
|
+
for (const cat1 of this.categories) {
|
|
157
|
+
for (const val1 of cat1.values) {
|
|
158
|
+
for (const cat2 of this.categories) {
|
|
159
|
+
if (cat1.id >= cat2.id)
|
|
160
|
+
continue;
|
|
161
|
+
const possibilities = this.grid.get(cat1.id)?.get(val1)?.get(cat2.id);
|
|
162
|
+
if (possibilities) {
|
|
163
|
+
currentPossible += possibilities.filter(p => p).length;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return { totalPossible, currentPossible, solutionPossible };
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Creates a deep copy of the current LogicGrid.
|
|
172
|
+
*
|
|
173
|
+
* @returns A new LogicGrid instance with the exact same state.
|
|
174
|
+
*/
|
|
175
|
+
clone() {
|
|
176
|
+
const newGrid = new LogicGrid(this.categories);
|
|
177
|
+
newGrid.grid = new Map([...this.grid.entries()].map(([cat1Id, cat1Map]) => [
|
|
178
|
+
cat1Id,
|
|
179
|
+
new Map([...cat1Map.entries()].map(([val1, val1Map]) => [
|
|
180
|
+
val1,
|
|
181
|
+
new Map([...val1Map.entries()].map(([cat2Id, cat2Arr]) => [
|
|
182
|
+
cat2Id,
|
|
183
|
+
[...cat2Arr],
|
|
184
|
+
])),
|
|
185
|
+
])),
|
|
186
|
+
]));
|
|
187
|
+
return newGrid;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
exports.LogicGrid = LogicGrid;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Clue } from './Clue';
|
|
2
|
+
import { LogicGrid } from './LogicGrid';
|
|
3
|
+
/**
|
|
4
|
+
* The logical engine responsible for applying clues to a LogicGrid and deducing consequences.
|
|
5
|
+
*
|
|
6
|
+
* It implements various deduction strategies including basic elimination, uniqueness checks,
|
|
7
|
+
* and transitive logic logic (if A=B and B=C, then A=C).
|
|
8
|
+
*/
|
|
9
|
+
export declare class Solver {
|
|
10
|
+
/**
|
|
11
|
+
* Applies a single clue to the grid and propagates logical deductions.
|
|
12
|
+
*
|
|
13
|
+
* This method runs a loop that explicitly applies the clue and then repeatedly
|
|
14
|
+
* triggers the internal deduction engine until no further eliminations can be made.
|
|
15
|
+
*
|
|
16
|
+
* @param grid - The LogicGrid to modify.
|
|
17
|
+
* @param clue - The Clue to apply.
|
|
18
|
+
* @returns An object containing the modified grid and the total count of eliminations made.
|
|
19
|
+
*/
|
|
20
|
+
applyClue(grid: LogicGrid, clue: Clue): {
|
|
21
|
+
grid: LogicGrid;
|
|
22
|
+
deductions: number;
|
|
23
|
+
};
|
|
24
|
+
private applyCrossOrdinalClue;
|
|
25
|
+
private applyUnaryClue;
|
|
26
|
+
private applyBinaryClue;
|
|
27
|
+
private runDeductionLoop;
|
|
28
|
+
private applyOrdinalClue;
|
|
29
|
+
private applySuperlativeClue;
|
|
30
|
+
}
|