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/dist/Solver.d.ts DELETED
@@ -1,29 +0,0 @@
1
- import { LogicGrid } from './LogicGrid';
2
- import { Clue } from './Clue';
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 applyUnaryClue;
25
- private applyBinaryClue;
26
- private runDeductionLoop;
27
- private applyOrdinalClue;
28
- private applySuperlativeClue;
29
- }
package/dist/Solver.js DELETED
@@ -1,242 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Solver = void 0;
4
- const Clue_1 = require("./Clue");
5
- const types_1 = require("./types");
6
- /**
7
- * The logical engine responsible for applying clues to a LogicGrid and deducing consequences.
8
- *
9
- * It implements various deduction strategies including basic elimination, uniqueness checks,
10
- * and transitive logic logic (if A=B and B=C, then A=C).
11
- */
12
- class Solver {
13
- /**
14
- * Applies a single clue to the grid and propagates logical deductions.
15
- *
16
- * This method runs a loop that explicitly applies the clue and then repeatedly
17
- * triggers the internal deduction engine until no further eliminations can be made.
18
- *
19
- * @param grid - The LogicGrid to modify.
20
- * @param clue - The Clue to apply.
21
- * @returns An object containing the modified grid and the total count of eliminations made.
22
- */
23
- applyClue(grid, clue) {
24
- let deductions = 0;
25
- switch (clue.type) {
26
- case Clue_1.ClueType.BINARY:
27
- deductions += this.applyBinaryClue(grid, clue);
28
- break;
29
- case Clue_1.ClueType.SUPERLATIVE:
30
- deductions += this.applySuperlativeClue(grid, clue);
31
- break;
32
- case Clue_1.ClueType.ORDINAL:
33
- deductions += this.applyOrdinalClue(grid, clue);
34
- break;
35
- case Clue_1.ClueType.UNARY:
36
- deductions += this.applyUnaryClue(grid, clue);
37
- break;
38
- }
39
- let newDeductions;
40
- do {
41
- newDeductions = this.runDeductionLoop(grid);
42
- deductions += newDeductions;
43
- } while (newDeductions > 0);
44
- return { grid, deductions };
45
- }
46
- applyUnaryClue(grid, clue) {
47
- let deductions = 0;
48
- const categories = grid.categories;
49
- const ordinalCatConfig = categories.find(c => c.id === clue.ordinalCat);
50
- if (!ordinalCatConfig || ordinalCatConfig.type !== types_1.CategoryType.ORDINAL)
51
- return 0;
52
- if (!ordinalCatConfig.values.every(v => typeof v === 'number'))
53
- return 0;
54
- const isEven = clue.filter === Clue_1.UnaryFilter.IS_EVEN;
55
- for (const ordVal of ordinalCatConfig.values) {
56
- const ordNum = ordVal;
57
- const shouldEliminate = isEven ? ordNum % 2 !== 0 : ordNum % 2 === 0;
58
- if (shouldEliminate) {
59
- if (grid.isPossible(clue.targetCat, clue.targetVal, clue.ordinalCat, ordVal)) {
60
- grid.setPossibility(clue.targetCat, clue.targetVal, clue.ordinalCat, ordVal, false);
61
- deductions++;
62
- }
63
- }
64
- }
65
- return deductions;
66
- }
67
- applyBinaryClue(grid, clue) {
68
- let deductions = 0;
69
- const categories = grid.categories;
70
- const cat1Config = categories.find(c => c.id === clue.cat1);
71
- const cat2Config = categories.find(c => c.id === clue.cat2);
72
- if (!cat1Config || !cat2Config)
73
- return 0;
74
- if (clue.operator === Clue_1.BinaryOperator.IS) {
75
- if (grid.isPossible(clue.cat1, clue.val1, clue.cat2, clue.val2)) {
76
- // This is not a deduction, but a fact application. Still, we need to eliminate other possibilities.
77
- }
78
- grid.setPossibility(clue.cat1, clue.val1, clue.cat2, clue.val2, true);
79
- for (const val of cat2Config.values) {
80
- if (val !== clue.val2) {
81
- if (grid.isPossible(clue.cat1, clue.val1, clue.cat2, val)) {
82
- grid.setPossibility(clue.cat1, clue.val1, clue.cat2, val, false);
83
- deductions++;
84
- }
85
- }
86
- }
87
- for (const val of cat1Config.values) {
88
- if (val !== clue.val1) {
89
- if (grid.isPossible(clue.cat1, val, clue.cat2, clue.val2)) {
90
- grid.setPossibility(clue.cat1, val, clue.cat2, clue.val2, false);
91
- deductions++;
92
- }
93
- }
94
- }
95
- }
96
- else { // IS_NOT
97
- if (grid.isPossible(clue.cat1, clue.val1, clue.cat2, clue.val2)) {
98
- grid.setPossibility(clue.cat1, clue.val1, clue.cat2, clue.val2, false);
99
- deductions++;
100
- }
101
- }
102
- return deductions;
103
- }
104
- runDeductionLoop(grid) {
105
- let deductions = 0;
106
- const categories = grid.categories;
107
- for (const cat1 of categories) {
108
- for (const val1 of cat1.values) {
109
- for (const cat2 of categories) {
110
- if (cat1.id === cat2.id)
111
- continue;
112
- // Uniqueness Check
113
- const possibleValues = cat2.values.filter(val2 => grid.isPossible(cat1.id, val1, cat2.id, val2));
114
- if (possibleValues.length === 1) {
115
- const val2 = possibleValues[0];
116
- // If val1 is uniquely associated with val2, then no other val from cat1 can be associated with val2
117
- for (const otherVal1 of cat1.values) {
118
- if (otherVal1 !== val1) {
119
- if (grid.isPossible(cat1.id, otherVal1, cat2.id, val2)) {
120
- grid.setPossibility(cat1.id, otherVal1, cat2.id, val2, false);
121
- deductions++;
122
- }
123
- }
124
- }
125
- }
126
- // Transitivity
127
- for (const cat3 of categories) {
128
- if (cat1.id === cat3.id || cat2.id === cat3.id)
129
- continue;
130
- // Positive Transitivity
131
- const definiteVal2 = possibleValues.length === 1 ? possibleValues[0] : null;
132
- if (definiteVal2) {
133
- const possibleVal3s = cat3.values.filter(val3 => grid.isPossible(cat2.id, definiteVal2, cat3.id, val3));
134
- if (possibleVal3s.length === 1) {
135
- const definiteVal3 = possibleVal3s[0];
136
- if (grid.isPossible(cat1.id, val1, cat3.id, definiteVal3) === false) {
137
- // This indicates a contradiction, but for now we are just making deductions.
138
- }
139
- else if (grid.getPossibilitiesCount(cat1.id, val1, cat3.id) > 1) {
140
- grid.setPossibility(cat1.id, val1, cat3.id, definiteVal3, true);
141
- deductions++;
142
- }
143
- }
144
- }
145
- // Negative Transitivity
146
- for (const val3 of cat3.values) {
147
- if (grid.isPossible(cat1.id, val1, cat3.id, val3)) {
148
- const isPathPossible = cat2.values.some(val2 => grid.isPossible(cat1.id, val1, cat2.id, val2) && grid.isPossible(cat2.id, val2, cat3.id, val3));
149
- if (!isPathPossible) {
150
- grid.setPossibility(cat1.id, val1, cat3.id, val3, false);
151
- deductions++;
152
- }
153
- }
154
- }
155
- }
156
- }
157
- }
158
- }
159
- return deductions;
160
- }
161
- applyOrdinalClue(grid, constraint) {
162
- let deductions = 0;
163
- const categories = grid.categories;
164
- const ordCatConfig = categories.find(c => c.id === constraint.ordinalCat);
165
- if (!ordCatConfig || ordCatConfig.type !== types_1.CategoryType.ORDINAL)
166
- return 0;
167
- const possibleVals1 = ordCatConfig.values
168
- .map((v, i) => ({ val: v, idx: i }))
169
- .filter(v => grid.isPossible(constraint.item1Cat, constraint.item1Val, constraint.ordinalCat, v.val));
170
- const possibleVals2 = ordCatConfig.values
171
- .map((v, i) => ({ val: v, idx: i }))
172
- .filter(v => grid.isPossible(constraint.item2Cat, constraint.item2Val, constraint.ordinalCat, v.val));
173
- if (possibleVals1.length === 0 || possibleVals2.length === 0)
174
- return 0;
175
- if (constraint.operator === Clue_1.OrdinalOperator.GREATER_THAN) { // item1 > item2
176
- // Prune item1's possibilities
177
- for (const pval1 of possibleVals1) {
178
- const canBeGreaterThan = possibleVals2.some(pval2 => pval1.idx > pval2.idx);
179
- if (!canBeGreaterThan) {
180
- if (grid.isPossible(constraint.item1Cat, constraint.item1Val, constraint.ordinalCat, pval1.val)) {
181
- grid.setPossibility(constraint.item1Cat, constraint.item1Val, constraint.ordinalCat, pval1.val, false);
182
- deductions++;
183
- }
184
- }
185
- }
186
- // Prune item2's possibilities
187
- for (const pval2 of possibleVals2) {
188
- const canBeLessThan = possibleVals1.some(pval1 => pval1.idx > pval2.idx);
189
- if (!canBeLessThan) {
190
- if (grid.isPossible(constraint.item2Cat, constraint.item2Val, constraint.ordinalCat, pval2.val)) {
191
- grid.setPossibility(constraint.item2Cat, constraint.item2Val, constraint.ordinalCat, pval2.val, false);
192
- deductions++;
193
- }
194
- }
195
- }
196
- }
197
- else if (constraint.operator === Clue_1.OrdinalOperator.LESS_THAN) { // item1 < item2
198
- // Prune item1's possibilities
199
- for (const pval1 of possibleVals1) {
200
- const canBeLessThan = possibleVals2.some(pval2 => pval1.idx < pval2.idx);
201
- if (!canBeLessThan) {
202
- if (grid.isPossible(constraint.item1Cat, constraint.item1Val, constraint.ordinalCat, pval1.val)) {
203
- grid.setPossibility(constraint.item1Cat, constraint.item1Val, constraint.ordinalCat, pval1.val, false);
204
- deductions++;
205
- }
206
- }
207
- }
208
- // Prune item2's possibilities
209
- for (const pval2 of possibleVals2) {
210
- const canBeGreaterThan = possibleVals1.some(pval1 => pval1.idx < pval2.idx);
211
- if (!canBeGreaterThan) {
212
- if (grid.isPossible(constraint.item2Cat, constraint.item2Val, constraint.ordinalCat, pval2.val)) {
213
- grid.setPossibility(constraint.item2Cat, constraint.item2Val, constraint.ordinalCat, pval2.val, false);
214
- deductions++;
215
- }
216
- }
217
- }
218
- }
219
- return deductions;
220
- }
221
- applySuperlativeClue(grid, clue) {
222
- const categories = grid.categories;
223
- const ordinalCatConfig = categories.find(c => c.id === clue.ordinalCat);
224
- if (!ordinalCatConfig || ordinalCatConfig.type !== types_1.CategoryType.ORDINAL)
225
- return 0;
226
- // Assuming values are sorted in ascending order for ordinal categories
227
- const extremeValue = clue.operator === Clue_1.SuperlativeOperator.MAX
228
- ? ordinalCatConfig.values[ordinalCatConfig.values.length - 1]
229
- : ordinalCatConfig.values[0];
230
- // This clue is essentially a binary IS clue.
231
- const binaryClue = {
232
- type: Clue_1.ClueType.BINARY,
233
- cat1: clue.targetCat,
234
- val1: clue.targetVal,
235
- cat2: clue.ordinalCat,
236
- val2: extremeValue,
237
- operator: Clue_1.BinaryOperator.IS,
238
- };
239
- return this.applyBinaryClue(grid, binaryClue);
240
- }
241
- }
242
- exports.Solver = Solver;
package/dist/index.d.ts DELETED
@@ -1,5 +0,0 @@
1
- export * from './types';
2
- export * from './Clue';
3
- export * from './LogicGrid';
4
- export * from './Solver';
5
- export * from './Generator';
package/dist/index.js DELETED
@@ -1,21 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
- };
16
- Object.defineProperty(exports, "__esModule", { value: true });
17
- __exportStar(require("./types"), exports);
18
- __exportStar(require("./Clue"), exports);
19
- __exportStar(require("./LogicGrid"), exports);
20
- __exportStar(require("./Solver"), exports);
21
- __exportStar(require("./Generator"), exports);
@@ -1 +0,0 @@
1
- export {};
@@ -1,84 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const types_1 = require("./types");
4
- const Generator_1 = require("./Generator");
5
- const Solver_1 = require("./Solver");
6
- const LogicGrid_1 = require("./LogicGrid");
7
- const Clue_1 = require("./Clue");
8
- const categories = [
9
- { id: 'Name', type: types_1.CategoryType.NOMINAL, values: ['Alice', 'Bob', 'Charlie', 'David'] },
10
- { id: 'Genre', type: types_1.CategoryType.NOMINAL, values: ['Horror', 'Sci-Fi', 'Comedy', 'Drama'] },
11
- { id: 'Snack', type: types_1.CategoryType.NOMINAL, values: ['Chips', 'Popcorn', 'Candy', 'Chocolate'] },
12
- { id: 'Age', type: types_1.CategoryType.ORDINAL, values: [20, 30, 40, 50] },
13
- ];
14
- const targetFact = {
15
- category1Id: 'Name',
16
- value1: 'David',
17
- category2Id: 'Snack',
18
- };
19
- const seed = 1234;
20
- const generator = new Generator_1.Generator(seed);
21
- const puzzle = generator.generatePuzzle(categories, targetFact);
22
- console.log(`## ✨ Generated Puzzle (Seed: ${seed})`);
23
- console.log('\n---\n');
24
- console.log('### The Answer Key (For Verification Only)');
25
- const baseCat = puzzle.categories[0];
26
- for (const val of baseCat.values) {
27
- let line = '';
28
- for (const cat of puzzle.categories) {
29
- line += `${cat.id}: ${puzzle.solution[cat.id][val]} | `;
30
- }
31
- console.log(line.slice(0, -3));
32
- }
33
- console.log('\n### Goal');
34
- console.log(`What ${puzzle.targetFact.category2Id} does [Name: ${puzzle.targetFact.value1}] have?`);
35
- console.log('\n---\n');
36
- console.log('### 🧩 Clue-by-Clue Proof Chain\n');
37
- // The new API provides the proof chain directly, so we don't need to re-run the solver.
38
- // We still create a grid to show the ambiguity snapshot at each step.
39
- const logicGrid = new LogicGrid_1.LogicGrid(categories);
40
- const solver = new Solver_1.Solver();
41
- puzzle.proofChain.forEach((step, index) => {
42
- console.log(`STEP ${index + 1}: Clue: ${clueToString(step.clue)}`);
43
- console.log(`Deductions: ${step.deductions} eliminations made.`);
44
- // Apply the clue to our local grid to show the state *after* this clue is applied
45
- solver.applyClue(logicGrid, step.clue);
46
- console.log('\nAmbiguity Snapshot:');
47
- // Focused snapshot for the target fact
48
- const targetCat2 = categories.find(c => c.id === puzzle.targetFact.category2Id);
49
- if (targetCat2) {
50
- const possibleValues = targetCat2.values.filter(v => logicGrid.isPossible(puzzle.targetFact.category1Id, puzzle.targetFact.value1, puzzle.targetFact.category2Id, v));
51
- console.log(`- Target: [${puzzle.targetFact.category1Id}: ${puzzle.targetFact.value1}] vs [${puzzle.targetFact.category2Id}]: ${possibleValues.length} possibilities remain -> [${possibleValues.join(', ')}]`);
52
- }
53
- // Overall grid completion
54
- const { totalPossible, currentPossible, solutionPossible } = logicGrid.getGridStats();
55
- const totalEliminatable = totalPossible - solutionPossible;
56
- const eliminatedSoFar = totalPossible - currentPossible;
57
- const percentageSolved = totalEliminatable > 0 ? (eliminatedSoFar / totalEliminatable) * 100 : 0;
58
- console.log(`- Grid Solved: ${percentageSolved.toFixed(1)}%`);
59
- console.log('\n---\n');
60
- });
61
- const targetValue = puzzle.solution[puzzle.targetFact.category2Id][puzzle.targetFact.value1];
62
- console.log(`\n**Target Fact Solved:** [${puzzle.targetFact.category1Id}: ${puzzle.targetFact.value1}] is correlated with [${puzzle.targetFact.category2Id}: ${targetValue}]!`);
63
- function clueToString(clue) {
64
- switch (clue.type) {
65
- case Clue_1.ClueType.BINARY:
66
- const b = clue;
67
- const op = b.operator === Clue_1.BinaryOperator.IS ? 'is' : 'is not';
68
- return `[${b.cat1}: ${b.val1}] ${op} [${b.cat2}: ${b.val2}]`;
69
- case Clue_1.ClueType.ORDINAL:
70
- const o = clue;
71
- const ordOp = o.operator === Clue_1.OrdinalOperator.GREATER_THAN ? 'is greater than' : 'is less than';
72
- return `The one with [${o.item1Cat}: ${o.item1Val}] ${ordOp} the one with [${o.item2Cat}: ${o.item2Val}] (by ${o.ordinalCat})`;
73
- case Clue_1.ClueType.SUPERLATIVE:
74
- const s = clue;
75
- const supOp = s.operator === Clue_1.SuperlativeOperator.MAX ? 'is the oldest' : 'is the youngest';
76
- return `[${s.targetCat}: ${s.targetVal}] ${supOp}`;
77
- case Clue_1.ClueType.UNARY:
78
- const u = clue;
79
- const filterStr = u.filter === Clue_1.UnaryFilter.IS_EVEN ? 'even' : 'odd';
80
- return `[${u.targetCat}: ${u.targetVal}] is associated with an ${filterStr} ${u.ordinalCat}`;
81
- default:
82
- return 'Unknown Clue Type';
83
- }
84
- }
package/dist/types.d.ts DELETED
@@ -1,37 +0,0 @@
1
- /**
2
- * A primitive value that acts as a label for an entity (e.g., "Alice", 42).
3
- */
4
- export type ValueLabel = string | number;
5
- /**
6
- * Defines the nature of a category's values.
7
- */
8
- export declare enum CategoryType {
9
- /** Order is irrelevant (e.g., Name, Genre). */
10
- NOMINAL = 0,
11
- /** Order is crucial for comparisons (e.g., Age, Price). Values must be numbers. */
12
- ORDINAL = 1
13
- }
14
- /**
15
- * Configuration for a single category in the puzzle.
16
- */
17
- export interface CategoryConfig {
18
- /** Unique internal identifier for the category (e.g., 'Name'). */
19
- id: string;
20
- /** The type of data this category holds. */
21
- type: CategoryType;
22
- /** The list of possible values. Must be unique. If ORDINAL, they must be sorted. */
23
- values: ValueLabel[];
24
- }
25
- /**
26
- * Represents the complete solution to the puzzle (the "answer key").
27
- * Structure: { CategoryID -> { Value -> CorrectValueInOtherCategory } }
28
- */
29
- export type Solution = Record<string, Record<string, ValueLabel>>;
30
- /**
31
- * A specific correlation that the puzzle solver aims to deduce as the final answer.
32
- */
33
- export interface TargetFact {
34
- category1Id: string;
35
- value1: ValueLabel;
36
- category2Id: string;
37
- }
package/dist/types.js DELETED
@@ -1,13 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CategoryType = void 0;
4
- /**
5
- * Defines the nature of a category's values.
6
- */
7
- var CategoryType;
8
- (function (CategoryType) {
9
- /** Order is irrelevant (e.g., Name, Genre). */
10
- CategoryType[CategoryType["NOMINAL"] = 0] = "NOMINAL";
11
- /** Order is crucial for comparisons (e.g., Age, Price). Values must be numbers. */
12
- CategoryType[CategoryType["ORDINAL"] = 1] = "ORDINAL";
13
- })(CategoryType || (exports.CategoryType = CategoryType = {}));