logic-puzzle-generator 1.0.0 → 1.1.9
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/README.md +112 -8
- package/dist/src/engine/GenerativeSession.d.ts +9 -0
- package/dist/src/engine/GenerativeSession.js +17 -0
- package/dist/src/engine/Generator.d.ts +25 -0
- package/dist/src/engine/Generator.js +92 -27
- package/package.json +9 -1
- package/dist/Clue.d.ts +0 -81
- package/dist/Clue.js +0 -37
- package/dist/Generator.d.ts +0 -58
- package/dist/Generator.js +0 -433
- package/dist/LogicGrid.d.ts +0 -70
- package/dist/LogicGrid.js +0 -188
- package/dist/Solver.d.ts +0 -29
- package/dist/Solver.js +0 -242
- package/dist/index.d.ts +0 -5
- package/dist/index.js +0 -21
- package/dist/run_generator.d.ts +0 -1
- package/dist/run_generator.js +0 -84
- package/dist/types.d.ts +0 -37
- package/dist/types.js +0 -13
package/README.md
CHANGED
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|

|
|
6
6
|

|
|
7
7
|
|
|
8
|
+
[View the interactive demo](https://project.joshhills.dev/logic-puzzle-generator/)
|
|
9
|
+
|
|
10
|
+

|
|
11
|
+
|
|
8
12
|
|
|
9
13
|
A TypeScript library for generating, solving, and verifying "Zebra Puzzle" style logic grid puzzles.
|
|
10
14
|
|
|
@@ -183,24 +187,124 @@ The generator solves the puzzle as it builds it. The `puzzle.proofChain` array c
|
|
|
183
187
|
### `Generator`
|
|
184
188
|
The main class. Use `new Generator(seed)` to initialize.
|
|
185
189
|
|
|
186
|
-
- `generatePuzzle(categories, target
|
|
187
|
-
- `
|
|
188
|
-
- `options.
|
|
189
|
-
- `options.
|
|
190
|
+
- `generatePuzzle(categories, target?, options?)`: Returns a `Puzzle` object.
|
|
191
|
+
- `target` (Optional): The `TargetFact` to solve for. If omitted, a random target is selected.
|
|
192
|
+
- `options.targetClueCount`: Attempt to find exact solution length. Avoids early termination.
|
|
193
|
+
- `options.maxCandidates`: Performance tuning (default 50). Limits the heuristic search width.
|
|
194
|
+
- `options.timeoutMs`: Abort generation if it exceeds this limit (default 10000ms).
|
|
195
|
+
- `options.constraints`: Filter allowed clue types.
|
|
196
|
+
- `allowedClueTypes`: `ClueType[]` (e.g. `[ClueType.BINARY, ClueType.ORDINAL]`).
|
|
197
|
+
- `options.onTrace`: **(Debug)** Callback `(msg: string) => void`. Receives real-time logs about the generation process.
|
|
198
|
+
- `generatePuzzleAsync(...)`: **New in v1.1.0**. Non-blocking version of `generatePuzzle`. Returns `Promise<Puzzle>`.
|
|
190
199
|
- `getClueCountBounds(categories, target)`: Returns plausible Min/Max clue counts.
|
|
191
|
-
- `
|
|
200
|
+
- `getClueCountBoundsAsync(...)`: **New in v1.1.0**. Non-blocking version. Returns `Promise<{ min, max }>`.
|
|
201
|
+
- `startSession(categories, target?)`: [Beta] Starts a `GenerativeSession` for step-by-step interactive generation.
|
|
202
|
+
|
|
203
|
+
#### Advanced / Internal API
|
|
204
|
+
- `calculateClueScore(grid, target, deductions, clue, ...)`: **(Extensible)** detailed heuristic scoring for clue selection.
|
|
205
|
+
- `isPuzzleSolved(grid, solution, ...)`: Checks if the grid matches the unique solution.
|
|
206
|
+
- `generateAllPossibleClues(...)`: Generates every valid clue for the current configuration (unfiltered).
|
|
192
207
|
|
|
193
208
|
### Extensibility
|
|
194
209
|
The `Generator` class is designed to be extensible. Key methods like `calculateClueScore` are `public`, allowing you to extend the class and inject custom heuristics.
|
|
195
210
|
|
|
196
211
|
### `LogicGrid`
|
|
197
212
|
Manages the state of the puzzle grid (possibility matrix).
|
|
213
|
+
- `constructor(categories: CategoryConfig[])`: Initializes a new grid.
|
|
198
214
|
- `isPossible(cat1, val1, cat2, val2)`: Returns true if a connection is possible.
|
|
199
|
-
- `setPossibility(
|
|
215
|
+
- `setPossibility(cat1, val1, cat2, val2, state)`: Manually set connection state.
|
|
216
|
+
- `getPossibilitiesCount(cat1, val1, cat2)`: Returns the number of remaining possibilities for a value in a target category.
|
|
217
|
+
- `getGridStats()`: Returns `{ totalPossible, currentPossible, solutionPossible }` to track solving progress.
|
|
218
|
+
- `clone()`: Creates a deep copy of the grid.
|
|
200
219
|
|
|
201
220
|
### `Solver`
|
|
202
|
-
The logical engine.
|
|
203
|
-
- `applyClue(grid, clue)`: Applies a clue and cascades deductions.
|
|
221
|
+
The logical engine responsible for applying clues and performing deductions.
|
|
222
|
+
- `applyClue(grid, clue)`: Applies a clue and cascades deductions. Returns `{ deductions: number }`.
|
|
223
|
+
- `runDeductionLoop(grid)`: repeatedly applies elimination logic until the grid stabilizes.
|
|
224
|
+
|
|
225
|
+
#### Internal Deduction Methods
|
|
226
|
+
- `applyBinaryClue(grid, clue)`
|
|
227
|
+
- `applyOrdinalClue(grid, clue)`
|
|
228
|
+
- `applyCrossOrdinalClue(grid, clue)`
|
|
229
|
+
- `applySuperlativeClue(grid, clue)`
|
|
230
|
+
- `applyUnaryClue(grid, clue)`
|
|
231
|
+
|
|
232
|
+
### `GenerativeSession`
|
|
233
|
+
Manages a stateful, step-by-step puzzle generation process.
|
|
234
|
+
- `getNextClue(constraints?)`: Returns `{ clue: Clue | null, remaining: number, solved: boolean }`.
|
|
235
|
+
- Generates and selects the next best clue based on the current grid state.
|
|
236
|
+
- `constraints`: Optional `ClueGenerationConstraints`.
|
|
237
|
+
- `getNextClueAsync(constraints?)`: **New in v1.1.1**. Non-blocking version. Returns `Promise<{ clue, remaining, solved }>`.
|
|
238
|
+
- `rollbackLastClue()`: Returns `{ success: boolean, clue: Clue | null }`. Undoes the last step.
|
|
239
|
+
- `getGrid()`: Returns the current `LogicGrid` state.
|
|
240
|
+
- `getSolution()`: Returns the target `Solution` map.
|
|
241
|
+
- `getProofChain()`: Returns the list of `Clue`s applied so far.
|
|
242
|
+
- `getValueMap()`: Returns the optimized internal value categorization map.
|
|
243
|
+
|
|
244
|
+
### Data Types
|
|
245
|
+
|
|
246
|
+
#### `CategoryConfig`
|
|
247
|
+
Configuration for a single category.
|
|
248
|
+
```typescript
|
|
249
|
+
interface CategoryConfig {
|
|
250
|
+
id: string; // e.g. "Suspect"
|
|
251
|
+
values: string[]; // e.g. ["Mustard", "Plum"...]
|
|
252
|
+
type: CategoryType; // NOMINAL | ORDINAL
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
#### `TargetFact`
|
|
257
|
+
Defines the goal of the puzzle (e.g., "Who killed Mr. Boddy?").
|
|
258
|
+
```typescript
|
|
259
|
+
interface TargetFact {
|
|
260
|
+
category1Id: string;
|
|
261
|
+
value1: string;
|
|
262
|
+
category2Id: string;
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
#### `ClueType`
|
|
267
|
+
Enum for available clue logic: `BINARY` (Direct), `ORDINAL` (Comparison), `CROSS_ORDINAL` (Relative), `SUPERLATIVE` (Min/Max), `UNARY` (Properties).
|
|
268
|
+
|
|
269
|
+
## Interactive Generation (Builder Mode)
|
|
270
|
+
|
|
271
|
+
For UIs where you want to watch the puzzle being built (or let the user manually pick the next clue type), use the `GenerativeSession`.
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
const session = generator.startSession(categories, target);
|
|
275
|
+
let solved = false;
|
|
276
|
+
|
|
277
|
+
while (!solved) {
|
|
278
|
+
// 1. Get the next best clue (optionally force a specific type)
|
|
279
|
+
const result = session.getNextClue({
|
|
280
|
+
allowedClueTypes: [ClueType.BINARY, ClueType.ORDINAL]
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
if (result.clue) {
|
|
284
|
+
console.log("Next Clue:", result.clue);
|
|
285
|
+
solved = result.solved;
|
|
286
|
+
} else {
|
|
287
|
+
console.warn("No more clues available.");
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
## Error Handling
|
|
294
|
+
|
|
295
|
+
The library uses specific error types to help you debug configuration issues.
|
|
296
|
+
|
|
297
|
+
| Method | Throws | Reason |
|
|
298
|
+
| :--- | :--- | :--- |
|
|
299
|
+
| Method | Throws | Reason |
|
|
300
|
+
| :--- | :--- | :--- |
|
|
301
|
+
| `new Generator()` | `Error` | If `seed` is invalid (NaN). |
|
|
302
|
+
| `generatePuzzle()` | `ConfigurationError` | **Configuration**: <br> - Less than 2 categories. <br> - `maxCandidates` < 1. <br> - `targetClueCount` < 1. <br> **Target Fact**: <br> Refers to non-existent category/value or uses same category twice. <br> **Constraints**: <br> - Ambiguous (Weak) types only. <br> - Requesting `ORDINAL` without Ordinal categories. <br> - Requesting `CROSS_ORDINAL` with < 2 Ordinal categories. <br> - Requesting `UNARY` (Even/Odd) without mixed numeric values. <br> **Data**: <br> - `ORDINAL` category contains non-numeric values. <br> **Runtime**: <br> - Could not find solution with exact `targetClueCount` within timeout. |
|
|
303
|
+
| `startSession()` | `ConfigurationError` | - Less than 2 categories. |
|
|
304
|
+
| `LogicGrid()` | `ConfigurationError` | - Duplicate Category IDs <br> - Duplicate Values within a category <br> - Mismatched value counts (all categories must be same size). |
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
|
|
204
308
|
|
|
205
309
|
## AI Disclosure & Liability Policy
|
|
206
310
|
|
|
@@ -20,6 +20,15 @@ export declare class GenerativeSession {
|
|
|
20
20
|
remaining: number;
|
|
21
21
|
solved: boolean;
|
|
22
22
|
};
|
|
23
|
+
/**
|
|
24
|
+
* Asynchronously gets the next clue (non-blocking wrapper).
|
|
25
|
+
* @param constraints
|
|
26
|
+
*/
|
|
27
|
+
getNextClueAsync(constraints?: ClueGenerationConstraints): Promise<{
|
|
28
|
+
clue: Clue | null;
|
|
29
|
+
remaining: number;
|
|
30
|
+
solved: boolean;
|
|
31
|
+
}>;
|
|
23
32
|
rollbackLastClue(): {
|
|
24
33
|
success: boolean;
|
|
25
34
|
clue: Clue | null;
|
|
@@ -79,6 +79,23 @@ class GenerativeSession {
|
|
|
79
79
|
}
|
|
80
80
|
return { clue: null, remaining: validClues.length, solved: this.generator.isPuzzleSolved(this.grid, this.solution, this.reverseSolution) };
|
|
81
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Asynchronously gets the next clue (non-blocking wrapper).
|
|
84
|
+
* @param constraints
|
|
85
|
+
*/
|
|
86
|
+
async getNextClueAsync(constraints) {
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
setTimeout(() => {
|
|
89
|
+
try {
|
|
90
|
+
const result = this.getNextClue(constraints);
|
|
91
|
+
resolve(result);
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
reject(e);
|
|
95
|
+
}
|
|
96
|
+
}, 0);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
82
99
|
rollbackLastClue() {
|
|
83
100
|
if (this.historyStack.length === 0)
|
|
84
101
|
return { success: false, clue: null };
|
|
@@ -96,6 +96,31 @@ export declare class Generator {
|
|
|
96
96
|
* @param config - Generation options.
|
|
97
97
|
*/
|
|
98
98
|
generatePuzzle(categories: CategoryConfig[], target?: TargetFact, config?: GeneratorOptions): Puzzle;
|
|
99
|
+
/**
|
|
100
|
+
* Helper to validate a target against categories
|
|
101
|
+
*/
|
|
102
|
+
private validateTarget;
|
|
103
|
+
/**
|
|
104
|
+
* Helper to generate a random target
|
|
105
|
+
*/
|
|
106
|
+
private generateRandomTarget;
|
|
107
|
+
/**
|
|
108
|
+
* Asynchronously generates a puzzle (non-blocking wrapper).
|
|
109
|
+
* @param categories
|
|
110
|
+
* @param target
|
|
111
|
+
* @param config
|
|
112
|
+
*/
|
|
113
|
+
generatePuzzleAsync(categories: CategoryConfig[], target?: TargetFact, config?: GeneratorOptions): Promise<Puzzle>;
|
|
114
|
+
/**
|
|
115
|
+
* Asynchronously estimates clue count bounds (non-blocking wrapper).
|
|
116
|
+
* @param categories
|
|
117
|
+
* @param target
|
|
118
|
+
* @param maxIterations
|
|
119
|
+
*/
|
|
120
|
+
getClueCountBoundsAsync(categories: CategoryConfig[], target: TargetFact, maxIterations?: number): Promise<{
|
|
121
|
+
min: number;
|
|
122
|
+
max: number;
|
|
123
|
+
}>;
|
|
99
124
|
/**
|
|
100
125
|
* Starts an interactive generative session.
|
|
101
126
|
* @param categories
|
|
@@ -81,41 +81,106 @@ class Generator {
|
|
|
81
81
|
* @param config - Generation options.
|
|
82
82
|
*/
|
|
83
83
|
generatePuzzle(categories, target, config = {}) {
|
|
84
|
+
// Validation:
|
|
85
|
+
// 1. Min Categories
|
|
86
|
+
if (categories.length < 2) {
|
|
87
|
+
throw new errors_1.ConfigurationError('Puzzle must have at least 2 categories.');
|
|
88
|
+
}
|
|
84
89
|
const { targetClueCount, maxCandidates = 50, timeoutMs = 10000 } = config;
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
90
|
+
// 2. Validate Target
|
|
91
|
+
const finalTarget = target || this.generateRandomTarget(categories);
|
|
92
|
+
this.validateTarget(categories, finalTarget);
|
|
93
|
+
// 3. Constraints
|
|
94
|
+
// Check for impossible requests
|
|
95
|
+
const constraints = config.constraints;
|
|
96
|
+
if (constraints?.allowedClueTypes) {
|
|
97
|
+
const types = constraints.allowedClueTypes;
|
|
98
|
+
const hasOrdinalCategory = categories.some(c => c.type === types_1.CategoryType.ORDINAL);
|
|
99
|
+
const requestedOrdinal = types.includes(types_1.ClueType.ORDINAL);
|
|
100
|
+
const requestedCrossOrdinal = types.includes(types_1.ClueType.CROSS_ORDINAL);
|
|
101
|
+
if (requestedOrdinal && !hasOrdinalCategory) {
|
|
102
|
+
// If Binary is allowed, we can fallback to just Binary.
|
|
103
|
+
// Only throw if we strictly CANNOT satisfy this without Ordinal categories.
|
|
104
|
+
if (!types.includes(types_1.ClueType.BINARY)) {
|
|
105
|
+
throw new errors_1.ConfigurationError('Invalid Constraints: Ordinal-based clue types were requested, but no Ordinal categories exist. Please add an ordinal category or allow Binary clues.');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (requestedCrossOrdinal) {
|
|
109
|
+
const ordinalCount = categories.filter(c => c.type === types_1.CategoryType.ORDINAL).length;
|
|
110
|
+
if (ordinalCount < 2) {
|
|
111
|
+
throw new errors_1.ConfigurationError('Invalid Constraints: Cross-Ordinal clues require at least 2 Ordinal Categories.');
|
|
112
|
+
}
|
|
100
113
|
}
|
|
101
|
-
const c1 = categories[cat1Idx];
|
|
102
|
-
const c2 = categories[cat2Idx];
|
|
103
|
-
const valIdx = Math.floor(this.random() * c1.values.length);
|
|
104
|
-
finalTarget = {
|
|
105
|
-
category1Id: c1.id,
|
|
106
|
-
value1: c1.values[valIdx],
|
|
107
|
-
category2Id: c2.id
|
|
108
|
-
};
|
|
109
114
|
}
|
|
110
|
-
|
|
115
|
+
return this.internalGenerate(categories, finalTarget, 'standard', { maxCandidates, targetClueCount, timeoutMs, constraints: config.constraints, onTrace: config.onTrace });
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Helper to validate a target against categories
|
|
119
|
+
*/
|
|
120
|
+
validateTarget(categories, target) {
|
|
111
121
|
const catIds = new Set(categories.map(c => c.id));
|
|
112
|
-
if (!catIds.has(
|
|
122
|
+
if (!catIds.has(target.category1Id) || !catIds.has(target.category2Id)) {
|
|
113
123
|
throw new errors_1.ConfigurationError('Target fact refers to non-existent categories.');
|
|
114
124
|
}
|
|
115
|
-
if (
|
|
125
|
+
if (target.category1Id === target.category2Id) {
|
|
116
126
|
throw new errors_1.ConfigurationError('Target fact must refer to two different categories.');
|
|
117
127
|
}
|
|
118
|
-
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Helper to generate a random target
|
|
131
|
+
*/
|
|
132
|
+
generateRandomTarget(categories) {
|
|
133
|
+
const cat1Idx = Math.floor(this.random() * categories.length);
|
|
134
|
+
let cat2Idx = Math.floor(this.random() * categories.length);
|
|
135
|
+
while (cat2Idx === cat1Idx) {
|
|
136
|
+
cat2Idx = Math.floor(this.random() * categories.length);
|
|
137
|
+
}
|
|
138
|
+
const c1 = categories[cat1Idx];
|
|
139
|
+
const c2 = categories[cat2Idx];
|
|
140
|
+
const valIdx = Math.floor(this.random() * c1.values.length);
|
|
141
|
+
return {
|
|
142
|
+
category1Id: c1.id,
|
|
143
|
+
value1: c1.values[valIdx],
|
|
144
|
+
category2Id: c2.id
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Asynchronously generates a puzzle (non-blocking wrapper).
|
|
149
|
+
* @param categories
|
|
150
|
+
* @param target
|
|
151
|
+
* @param config
|
|
152
|
+
*/
|
|
153
|
+
async generatePuzzleAsync(categories, target, config = {}) {
|
|
154
|
+
return new Promise((resolve, reject) => {
|
|
155
|
+
setTimeout(() => {
|
|
156
|
+
try {
|
|
157
|
+
const result = this.generatePuzzle(categories, target, config);
|
|
158
|
+
resolve(result);
|
|
159
|
+
}
|
|
160
|
+
catch (e) {
|
|
161
|
+
reject(e);
|
|
162
|
+
}
|
|
163
|
+
}, 0);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Asynchronously estimates clue count bounds (non-blocking wrapper).
|
|
168
|
+
* @param categories
|
|
169
|
+
* @param target
|
|
170
|
+
* @param maxIterations
|
|
171
|
+
*/
|
|
172
|
+
async getClueCountBoundsAsync(categories, target, maxIterations = 10) {
|
|
173
|
+
return new Promise((resolve, reject) => {
|
|
174
|
+
setTimeout(() => {
|
|
175
|
+
try {
|
|
176
|
+
const result = this.getClueCountBounds(categories, target, maxIterations);
|
|
177
|
+
resolve(result);
|
|
178
|
+
}
|
|
179
|
+
catch (e) {
|
|
180
|
+
reject(e);
|
|
181
|
+
}
|
|
182
|
+
}, 0);
|
|
183
|
+
});
|
|
119
184
|
}
|
|
120
185
|
/**
|
|
121
186
|
* Starts an interactive generative session.
|
package/package.json
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "logic-puzzle-generator",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.9",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public",
|
|
6
|
+
"registry": "https://registry.npmjs.org/"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/joshhills/logic-puzzle-generator.git"
|
|
11
|
+
},
|
|
4
12
|
"description": "A headless, TypeScript-based Logic Grid Puzzle Engine.",
|
|
5
13
|
"main": "dist/src/index.js",
|
|
6
14
|
"types": "dist/src/index.d.ts",
|
package/dist/Clue.d.ts
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { ValueLabel } from './types';
|
|
2
|
-
/**
|
|
3
|
-
* Enumeration of all supported clue types.
|
|
4
|
-
*/
|
|
5
|
-
export declare enum ClueType {
|
|
6
|
-
/** Expresses a direct relationship (IS or IS NOT) between two values. */
|
|
7
|
-
BINARY = 0,
|
|
8
|
-
/** Expresses a comparison (GREATER THAN or LESS THAN) between two values based on an ordinal category. */
|
|
9
|
-
ORDINAL = 1,
|
|
10
|
-
/** Expresses an extreme value relationship (MIN or MAX) within an ordinal category. */
|
|
11
|
-
SUPERLATIVE = 2,
|
|
12
|
-
/** Expresses a property of a single value (e.g., IS EVEN) relative to an ordinal category. */
|
|
13
|
-
UNARY = 3
|
|
14
|
-
}
|
|
15
|
-
export declare enum BinaryOperator {
|
|
16
|
-
IS = 0,
|
|
17
|
-
IS_NOT = 1
|
|
18
|
-
}
|
|
19
|
-
export declare enum OrdinalOperator {
|
|
20
|
-
GREATER_THAN = 0,
|
|
21
|
-
LESS_THAN = 1
|
|
22
|
-
}
|
|
23
|
-
export declare enum SuperlativeOperator {
|
|
24
|
-
MIN = 0,
|
|
25
|
-
MAX = 1
|
|
26
|
-
}
|
|
27
|
-
export declare enum UnaryFilter {
|
|
28
|
-
IS_ODD = 0,
|
|
29
|
-
IS_EVEN = 1
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* A clue that establishes a direct link or separation between two specific values.
|
|
33
|
-
* Example: "Alice likes Horror movies." (IS)
|
|
34
|
-
* Example: "Bob does not like Comedy." (IS_NOT)
|
|
35
|
-
*/
|
|
36
|
-
export interface BinaryClue {
|
|
37
|
-
type: ClueType.BINARY;
|
|
38
|
-
operator: BinaryOperator;
|
|
39
|
-
cat1: string;
|
|
40
|
-
val1: ValueLabel;
|
|
41
|
-
cat2: string;
|
|
42
|
-
val2: ValueLabel;
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* A clue that compares two entities based on a third ordinal category.
|
|
46
|
-
* Example: "The person who likes Horror is older than Alice."
|
|
47
|
-
*/
|
|
48
|
-
export interface OrdinalClue {
|
|
49
|
-
type: ClueType.ORDINAL;
|
|
50
|
-
operator: OrdinalOperator;
|
|
51
|
-
item1Cat: string;
|
|
52
|
-
item1Val: ValueLabel;
|
|
53
|
-
item2Cat: string;
|
|
54
|
-
item2Val: ValueLabel;
|
|
55
|
-
/** The ordinal category used for comparison (e.g., "Age"). */
|
|
56
|
-
ordinalCat: string;
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* A clue that identifies an entity as having an extreme value in an ordinal category.
|
|
60
|
-
* Example: "The person who likes Popcorn is the oldest."
|
|
61
|
-
*/
|
|
62
|
-
export interface SuperlativeClue {
|
|
63
|
-
type: ClueType.SUPERLATIVE;
|
|
64
|
-
operator: SuperlativeOperator;
|
|
65
|
-
targetCat: string;
|
|
66
|
-
targetVal: ValueLabel;
|
|
67
|
-
/** The ordinal category (e.g., "Age") where the value is extreme. */
|
|
68
|
-
ordinalCat: string;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* A clue that filters a value based on a property of its associated ordinal value.
|
|
72
|
-
* Example: "The person who likes Chips has an even age."
|
|
73
|
-
*/
|
|
74
|
-
export interface UnaryClue {
|
|
75
|
-
type: ClueType.UNARY;
|
|
76
|
-
filter: UnaryFilter;
|
|
77
|
-
targetCat: string;
|
|
78
|
-
targetVal: ValueLabel;
|
|
79
|
-
ordinalCat: string;
|
|
80
|
-
}
|
|
81
|
-
export type Clue = BinaryClue | OrdinalClue | SuperlativeClue | UnaryClue;
|
package/dist/Clue.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.UnaryFilter = exports.SuperlativeOperator = exports.OrdinalOperator = exports.BinaryOperator = exports.ClueType = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* Enumeration of all supported clue types.
|
|
6
|
-
*/
|
|
7
|
-
var ClueType;
|
|
8
|
-
(function (ClueType) {
|
|
9
|
-
/** Expresses a direct relationship (IS or IS NOT) between two values. */
|
|
10
|
-
ClueType[ClueType["BINARY"] = 0] = "BINARY";
|
|
11
|
-
/** Expresses a comparison (GREATER THAN or LESS THAN) between two values based on an ordinal category. */
|
|
12
|
-
ClueType[ClueType["ORDINAL"] = 1] = "ORDINAL";
|
|
13
|
-
/** Expresses an extreme value relationship (MIN or MAX) within an ordinal category. */
|
|
14
|
-
ClueType[ClueType["SUPERLATIVE"] = 2] = "SUPERLATIVE";
|
|
15
|
-
/** Expresses a property of a single value (e.g., IS EVEN) relative to an ordinal category. */
|
|
16
|
-
ClueType[ClueType["UNARY"] = 3] = "UNARY";
|
|
17
|
-
})(ClueType || (exports.ClueType = ClueType = {}));
|
|
18
|
-
var BinaryOperator;
|
|
19
|
-
(function (BinaryOperator) {
|
|
20
|
-
BinaryOperator[BinaryOperator["IS"] = 0] = "IS";
|
|
21
|
-
BinaryOperator[BinaryOperator["IS_NOT"] = 1] = "IS_NOT";
|
|
22
|
-
})(BinaryOperator || (exports.BinaryOperator = BinaryOperator = {}));
|
|
23
|
-
var OrdinalOperator;
|
|
24
|
-
(function (OrdinalOperator) {
|
|
25
|
-
OrdinalOperator[OrdinalOperator["GREATER_THAN"] = 0] = "GREATER_THAN";
|
|
26
|
-
OrdinalOperator[OrdinalOperator["LESS_THAN"] = 1] = "LESS_THAN";
|
|
27
|
-
})(OrdinalOperator || (exports.OrdinalOperator = OrdinalOperator = {}));
|
|
28
|
-
var SuperlativeOperator;
|
|
29
|
-
(function (SuperlativeOperator) {
|
|
30
|
-
SuperlativeOperator[SuperlativeOperator["MIN"] = 0] = "MIN";
|
|
31
|
-
SuperlativeOperator[SuperlativeOperator["MAX"] = 1] = "MAX";
|
|
32
|
-
})(SuperlativeOperator || (exports.SuperlativeOperator = SuperlativeOperator = {}));
|
|
33
|
-
var UnaryFilter;
|
|
34
|
-
(function (UnaryFilter) {
|
|
35
|
-
UnaryFilter[UnaryFilter["IS_ODD"] = 0] = "IS_ODD";
|
|
36
|
-
UnaryFilter[UnaryFilter["IS_EVEN"] = 1] = "IS_EVEN";
|
|
37
|
-
})(UnaryFilter || (exports.UnaryFilter = UnaryFilter = {}));
|
package/dist/Generator.d.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { CategoryConfig, Solution, TargetFact } from './types';
|
|
2
|
-
import { Clue } from './Clue';
|
|
3
|
-
/**
|
|
4
|
-
* Represents a single step in the logical deduction path.
|
|
5
|
-
*/
|
|
6
|
-
export interface ProofStep {
|
|
7
|
-
/** The clue applied at this step. */
|
|
8
|
-
clue: Clue;
|
|
9
|
-
/** The number of logical eliminations that resulted immediately from this clue. */
|
|
10
|
-
deductions: number;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* The complete result of the puzzle generation process.
|
|
14
|
-
*/
|
|
15
|
-
export interface Puzzle {
|
|
16
|
-
/** The solution grid (Category -> Value -> Corresponding Value). */
|
|
17
|
-
solution: Solution;
|
|
18
|
-
/** The list of clues needed to solve the puzzle, in no particular order. */
|
|
19
|
-
clues: Clue[];
|
|
20
|
-
/** An ordered list of clues that demonstrates a step-by-step logical solution. */
|
|
21
|
-
proofChain: ProofStep[];
|
|
22
|
-
/** The configuration used to generate this puzzle. */
|
|
23
|
-
categories: CategoryConfig[];
|
|
24
|
-
/** The specific fact that the puzzle is designed to reveal at the end. */
|
|
25
|
-
targetFact: TargetFact;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* The main class responsible for generating logic puzzles.
|
|
29
|
-
*
|
|
30
|
-
* It handles the creation of a consistent solution, the generation of all possible clues,
|
|
31
|
-
* and the selection of an optimal set of clues to form a solvable puzzle with a specific target.
|
|
32
|
-
*/
|
|
33
|
-
export declare class Generator {
|
|
34
|
-
private seed;
|
|
35
|
-
private random;
|
|
36
|
-
private solver;
|
|
37
|
-
private solution;
|
|
38
|
-
private valueMap;
|
|
39
|
-
private reverseSolution;
|
|
40
|
-
/**
|
|
41
|
-
* Creates a new Generator instance.
|
|
42
|
-
*
|
|
43
|
-
* @param seed - A numeric seed for the random number generator to ensure reproducibility.
|
|
44
|
-
*/
|
|
45
|
-
constructor(seed: number);
|
|
46
|
-
/**
|
|
47
|
-
* Generates a fully solvable logic puzzle based on the provided configuration.
|
|
48
|
-
*
|
|
49
|
-
* @param categories - The categories and values to include in the puzzle.
|
|
50
|
-
* @param target - The specific fact that should be the final deduction of the puzzle.
|
|
51
|
-
* @returns A complete Puzzle object containing the solution, clues, and proof chain.
|
|
52
|
-
*/
|
|
53
|
-
generatePuzzle(categories: CategoryConfig[], target: TargetFact): Puzzle;
|
|
54
|
-
private createSolution;
|
|
55
|
-
private generateAllPossibleClues;
|
|
56
|
-
private calculateScore;
|
|
57
|
-
private isPuzzleSolved;
|
|
58
|
-
}
|