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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +221 -0
  3. package/dist/Clue.d.ts +81 -0
  4. package/dist/Clue.js +37 -0
  5. package/dist/Generator.d.ts +58 -0
  6. package/dist/Generator.js +433 -0
  7. package/dist/LogicGrid.d.ts +70 -0
  8. package/dist/LogicGrid.js +188 -0
  9. package/dist/Solver.d.ts +29 -0
  10. package/dist/Solver.js +242 -0
  11. package/dist/examples/benchmark.d.ts +1 -0
  12. package/dist/examples/benchmark.js +129 -0
  13. package/dist/examples/cli.d.ts +1 -0
  14. package/dist/examples/cli.js +84 -0
  15. package/dist/index.d.ts +5 -0
  16. package/dist/index.js +21 -0
  17. package/dist/run_generator.d.ts +1 -0
  18. package/dist/run_generator.js +84 -0
  19. package/dist/src/defaults.d.ts +5 -0
  20. package/dist/src/defaults.js +24 -0
  21. package/dist/src/engine/BoundsCalculator.d.ts +6 -0
  22. package/dist/src/engine/BoundsCalculator.js +40 -0
  23. package/dist/src/engine/Clue.d.ts +75 -0
  24. package/dist/src/engine/Clue.js +10 -0
  25. package/dist/src/engine/DifficultyBounds.d.ts +12 -0
  26. package/dist/src/engine/DifficultyBounds.js +67 -0
  27. package/dist/src/engine/GenerativeSession.d.ts +32 -0
  28. package/dist/src/engine/GenerativeSession.js +109 -0
  29. package/dist/src/engine/Generator.d.ts +119 -0
  30. package/dist/src/engine/Generator.js +1058 -0
  31. package/dist/src/engine/LogicGrid.d.ts +70 -0
  32. package/dist/src/engine/LogicGrid.js +190 -0
  33. package/dist/src/engine/Solver.d.ts +30 -0
  34. package/dist/src/engine/Solver.js +613 -0
  35. package/dist/src/errors.d.ts +12 -0
  36. package/dist/src/errors.js +23 -0
  37. package/dist/src/index.d.ts +8 -0
  38. package/dist/src/index.js +24 -0
  39. package/dist/src/scripts/GenerateBoundsDeprecated.d.ts +1 -0
  40. package/dist/src/scripts/GenerateBoundsDeprecated.js +27 -0
  41. package/dist/src/scripts/StressTestBacktracking.d.ts +1 -0
  42. package/dist/src/scripts/StressTestBacktracking.js +49 -0
  43. package/dist/src/types.d.ts +86 -0
  44. package/dist/src/types.js +58 -0
  45. package/dist/types.d.ts +37 -0
  46. package/dist/types.js +13 -0
  47. package/package.json +40 -0
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BoundsCalculator = void 0;
4
+ const Generator_1 = require("./Generator");
5
+ const types_1 = require("../types");
6
+ class BoundsCalculator {
7
+ static calculate(numCats, numItems, iterations = 20) {
8
+ // Construct standard config
9
+ const categories = [];
10
+ for (let i = 0; i < numCats; i++) {
11
+ categories.push({
12
+ id: `C${i}`,
13
+ type: types_1.CategoryType.NOMINAL,
14
+ values: Array.from({ length: numItems }, (_, j) => `V${j}`)
15
+ });
16
+ }
17
+ const target = {
18
+ category1Id: 'C0',
19
+ value1: 'V0',
20
+ category2Id: 'C1'
21
+ };
22
+ let globalMin = Infinity;
23
+ let globalMax = 0;
24
+ // Run batch
25
+ for (let i = 0; i < iterations; i++) {
26
+ // Seed
27
+ const seed = Date.now() + (i * 9999);
28
+ const gen = new Generator_1.Generator(seed);
29
+ const bounds = gen.getClueCountBounds(categories, target, 1); // 1 iteration of internal logic
30
+ if (bounds.min > 0) {
31
+ globalMin = Math.min(globalMin, bounds.min);
32
+ globalMax = Math.max(globalMax, bounds.max);
33
+ }
34
+ }
35
+ if (globalMin === Infinity)
36
+ globalMin = 0;
37
+ return { min: globalMin, max: globalMax };
38
+ }
39
+ }
40
+ exports.BoundsCalculator = BoundsCalculator;
@@ -0,0 +1,75 @@
1
+ import { ValueLabel, ClueType, BinaryOperator, OrdinalOperator, SuperlativeOperator, UnaryFilter, CrossOrdinalOperator } from '../types';
2
+ export { ClueType, BinaryOperator, OrdinalOperator, SuperlativeOperator, UnaryFilter, CrossOrdinalOperator };
3
+ /**
4
+ * A clue that establishes a direct link or separation between two specific values.
5
+ * Example: "Alice likes Horror movies." (IS)
6
+ * Example: "Bob does not like Comedy." (IS_NOT)
7
+ */
8
+ export interface BinaryClue {
9
+ type: ClueType.BINARY;
10
+ operator: BinaryOperator;
11
+ cat1: string;
12
+ val1: ValueLabel;
13
+ cat2: string;
14
+ val2: ValueLabel;
15
+ }
16
+ /**
17
+ * A clue that compares two entities based on a third ordinal category.
18
+ * Example: "The person who likes Horror is older than Alice."
19
+ */
20
+ export interface OrdinalClue {
21
+ type: ClueType.ORDINAL;
22
+ operator: OrdinalOperator;
23
+ item1Cat: string;
24
+ item1Val: ValueLabel;
25
+ item2Cat: string;
26
+ item2Val: ValueLabel;
27
+ /** The ordinal category used for comparison (e.g., "Age"). */
28
+ ordinalCat: string;
29
+ }
30
+ /**
31
+ * A clue that identifies an entity as having an extreme value in an ordinal category.
32
+ * Example: "The person who likes Popcorn is the oldest."
33
+ */
34
+ export interface SuperlativeClue {
35
+ type: ClueType.SUPERLATIVE;
36
+ operator: SuperlativeOperator;
37
+ targetCat: string;
38
+ targetVal: ValueLabel;
39
+ /** The ordinal category (e.g., "Age") where the value is extreme. */
40
+ ordinalCat: string;
41
+ }
42
+ /**
43
+ * A clue that filters a value based on a property of its associated ordinal value.
44
+ * Example: "The person who likes Chips has an even age."
45
+ */
46
+ export interface UnaryClue {
47
+ type: ClueType.UNARY;
48
+ filter: UnaryFilter;
49
+ targetCat: string;
50
+ targetVal: ValueLabel;
51
+ ordinalCat: string;
52
+ }
53
+ /**
54
+ * A clue that links relative positions across two ordinal categories.
55
+ * Example: "The person immediately before [Item A] in [Cat 1] is the same as the person immediately after [Item B] in [Cat 2]."
56
+ */
57
+ export interface CrossOrdinalClue {
58
+ type: ClueType.CROSS_ORDINAL;
59
+ operator: CrossOrdinalOperator;
60
+ /** The first anchor entity */
61
+ item1Cat: string;
62
+ item1Val: ValueLabel;
63
+ /** The ordinal category for the first relation */
64
+ ordinal1: string;
65
+ /** The offset from the anchor (-1 = before, +1 = after) */
66
+ offset1: number;
67
+ /** The second anchor entity */
68
+ item2Cat: string;
69
+ item2Val: ValueLabel;
70
+ /** The ordinal category for the second relation */
71
+ ordinal2: string;
72
+ /** The offset from the anchor (-1 = before, +1 = after) */
73
+ offset2: number;
74
+ }
75
+ export type Clue = BinaryClue | OrdinalClue | SuperlativeClue | UnaryClue | CrossOrdinalClue;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CrossOrdinalOperator = exports.UnaryFilter = exports.SuperlativeOperator = exports.OrdinalOperator = exports.BinaryOperator = exports.ClueType = void 0;
4
+ const types_1 = require("../types");
5
+ Object.defineProperty(exports, "ClueType", { enumerable: true, get: function () { return types_1.ClueType; } });
6
+ Object.defineProperty(exports, "BinaryOperator", { enumerable: true, get: function () { return types_1.BinaryOperator; } });
7
+ Object.defineProperty(exports, "OrdinalOperator", { enumerable: true, get: function () { return types_1.OrdinalOperator; } });
8
+ Object.defineProperty(exports, "SuperlativeOperator", { enumerable: true, get: function () { return types_1.SuperlativeOperator; } });
9
+ Object.defineProperty(exports, "UnaryFilter", { enumerable: true, get: function () { return types_1.UnaryFilter; } });
10
+ Object.defineProperty(exports, "CrossOrdinalOperator", { enumerable: true, get: function () { return types_1.CrossOrdinalOperator; } });
@@ -0,0 +1,12 @@
1
+ export declare const DIFFICULTY_BOUNDS: Record<string, {
2
+ min: number;
3
+ max: number;
4
+ }>;
5
+ export declare const getRecommendedBounds: (numCats: number, numItems: number) => {
6
+ min: number;
7
+ max: number;
8
+ };
9
+ export declare const getRecommendedSweetSpot: (numCats: number, numItems: number) => {
10
+ min: number;
11
+ max: number;
12
+ };
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getRecommendedSweetSpot = exports.getRecommendedBounds = exports.DIFFICULTY_BOUNDS = void 0;
4
+ // Precomputed via Monte Carlo simulation (src/scripts/GenerateBounds.test.ts)
5
+ // Key format: "${numCats}x${numItems}"
6
+ // Updated after banning Direct Target Clues to ensure puzzle quality.
7
+ exports.DIFFICULTY_BOUNDS = {
8
+ // 2 Categories
9
+ "2x3": { "min": 2, "max": 2 },
10
+ "2x4": { "min": 3, "max": 10 },
11
+ "2x5": { "min": 4, "max": 17 },
12
+ "2x6": { "min": 5, "max": 26 },
13
+ "2x7": { "min": 6, "max": 37 },
14
+ "2x8": { "min": 7, "max": 50 },
15
+ "2x9": { "min": 8, "max": 65 },
16
+ "2x10": { "min": 9, "max": 82 },
17
+ // 3 Categories
18
+ "3x3": { "min": 3, "max": 9 },
19
+ "3x4": { "min": 5, "max": 26 },
20
+ "3x5": { "min": 7, "max": 47 },
21
+ "3x6": { "min": 9, "max": 72 },
22
+ "3x7": { "min": 11, "max": 100 },
23
+ "3x8": { "min": 13, "max": 130 },
24
+ "3x9": { "min": 15, "max": 160 },
25
+ "3x10": { "min": 17, "max": 200 },
26
+ // 4 Categories
27
+ "4x3": { "min": 5, "max": 16 },
28
+ "4x4": { "min": 8, "max": 44 },
29
+ "4x5": { "min": 11, "max": 87 },
30
+ "4x6": { "min": 14, "max": 140 },
31
+ "4x7": { "min": 17, "max": 200 },
32
+ "4x8": { "min": 20, "max": 260 },
33
+ "4x9": { "min": 23, "max": 330 },
34
+ "4x10": { "min": 26, "max": 400 },
35
+ // 5 Categories
36
+ "5x3": { "min": 7, "max": 25 },
37
+ "5x4": { "min": 11, "max": 70 },
38
+ "5x5": { "min": 15, "max": 130 },
39
+ "5x6": { "min": 19, "max": 200 },
40
+ "5x7": { "min": 23, "max": 280 },
41
+ "5x8": { "min": 27, "max": 370 },
42
+ "5x9": { "min": 31, "max": 470 },
43
+ "5x10": { "min": 35, "max": 580 }
44
+ };
45
+ const getRecommendedBounds = (numCats, numItems) => {
46
+ const key = `${numCats}x${numItems}`;
47
+ if (exports.DIFFICULTY_BOUNDS[key])
48
+ return exports.DIFFICULTY_BOUNDS[key];
49
+ // Dynamic Fallback for large grids
50
+ // Based on regression from smaller grids:
51
+ // Min ~ 0.8 * (Cats-1) * Items
52
+ // Max ~ 6.0 * (Cats-1) * Items (loose upper bound)
53
+ const factor = (numCats - 1) * numItems;
54
+ const min = Math.max(5, Math.floor(factor * 0.7));
55
+ const max = Math.floor(factor * 5.0);
56
+ return { min, max };
57
+ };
58
+ exports.getRecommendedBounds = getRecommendedBounds;
59
+ const getRecommendedSweetSpot = (numCats, numItems) => {
60
+ const bounds = (0, exports.getRecommendedBounds)(numCats, numItems);
61
+ // Sweet spot: 1.0x to 1.5x of minimum required clues for parity/solvability
62
+ return {
63
+ min: bounds.min,
64
+ max: Math.min(bounds.max, Math.ceil(bounds.min * 1.5))
65
+ };
66
+ };
67
+ exports.getRecommendedSweetSpot = getRecommendedSweetSpot;
@@ -0,0 +1,32 @@
1
+ import { CategoryConfig, ClueGenerationConstraints, Solution, TargetFact, ValueLabel } from '../types';
2
+ import { Clue } from './Clue';
3
+ import { LogicGrid } from './LogicGrid';
4
+ import type { Generator } from './Generator';
5
+ export declare class GenerativeSession {
6
+ private generator;
7
+ private categories;
8
+ private solution;
9
+ private reverseSolution;
10
+ private valueMap;
11
+ targetFact: TargetFact;
12
+ private grid;
13
+ private availableClues;
14
+ private proofChain;
15
+ private solver;
16
+ private historyStack;
17
+ constructor(generator: Generator, categories: CategoryConfig[], solution: Solution, reverseSolution: Map<string, Map<ValueLabel, ValueLabel>>, valueMap: Map<ValueLabel, Record<string, ValueLabel>>, targetFact: TargetFact);
18
+ getNextClue(constraints?: ClueGenerationConstraints): {
19
+ clue: Clue | null;
20
+ remaining: number;
21
+ solved: boolean;
22
+ };
23
+ rollbackLastClue(): {
24
+ success: boolean;
25
+ clue: Clue | null;
26
+ };
27
+ private isUseful;
28
+ getGrid(): LogicGrid;
29
+ getProofChain(): Clue[];
30
+ getSolution(): Solution;
31
+ getValueMap(): Map<ValueLabel, Record<string, ValueLabel>>;
32
+ }
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GenerativeSession = void 0;
4
+ const LogicGrid_1 = require("./LogicGrid");
5
+ const Solver_1 = require("./Solver");
6
+ class GenerativeSession {
7
+ constructor(generator, categories, solution, reverseSolution, valueMap, targetFact) {
8
+ this.generator = generator;
9
+ this.categories = categories;
10
+ this.solution = solution;
11
+ this.reverseSolution = reverseSolution;
12
+ this.valueMap = valueMap;
13
+ this.targetFact = targetFact;
14
+ this.availableClues = [];
15
+ this.proofChain = [];
16
+ this.historyStack = [];
17
+ this.grid = new LogicGrid_1.LogicGrid(categories);
18
+ this.solver = new Solver_1.Solver();
19
+ // Initial Generation of ALL possibilities (we filter later)
20
+ // We need access to the generator's clue generation logic.
21
+ // We will call a public method on generator.
22
+ this.availableClues = this.generator.generateAllPossibleClues(categories, undefined, reverseSolution, valueMap);
23
+ }
24
+ getNextClue(constraints) {
25
+ // Filter available clues based on constraints & current relevance
26
+ // Use the same heuristic logic as Generator?
27
+ const validClues = this.availableClues.filter(clue => {
28
+ // 1. Must not define something already known (Solver.applyClue check basically)
29
+ // Actually, redundancy check happens in scoring.
30
+ // 2. Constraints
31
+ if (constraints?.allowedClueTypes && !constraints.allowedClueTypes.includes(clue.type))
32
+ return false;
33
+ return true;
34
+ });
35
+ // Score them
36
+ let bestClue = null;
37
+ let bestScore = -Infinity;
38
+ // We should shuffle validClues to limit search space or just search all?
39
+ // For interactive session, speed is less critical than quality, but we want variety.
40
+ // Shuffle validClues first.
41
+ // In-place shuffle copy
42
+ const candidates = [...validClues].sort(() => Math.random() - 0.5);
43
+ // Take top N candidates?
44
+ const searchLimit = 50;
45
+ let checked = 0;
46
+ for (const clue of candidates) {
47
+ checked++;
48
+ if (checked > searchLimit && bestClue)
49
+ break; // found something good enough?
50
+ const score = this.generator.calculateClueScore(this.grid, this.targetFact, 0, clue, this.proofChain, this.solution, this.reverseSolution
51
+ // Note: we need to actually apply it to get deductions count for scoring to work well
52
+ );
53
+ // Wait, publicCalculateScore needs `deductions`.
54
+ // So we must clone grid and apply.
55
+ const tempGrid = this.grid.clone();
56
+ const { deductions } = this.solver.applyClue(tempGrid, clue);
57
+ if (deductions === 0 && !this.isUseful(tempGrid)) {
58
+ // Useless clue
59
+ continue;
60
+ }
61
+ // Re-calc score with deductions
62
+ const realScore = this.generator.calculateClueScore(this.grid, this.targetFact, deductions, clue, this.proofChain, this.solution, this.reverseSolution);
63
+ if (realScore > bestScore) {
64
+ bestScore = realScore;
65
+ bestClue = clue;
66
+ }
67
+ }
68
+ if (bestClue) {
69
+ // Save state
70
+ this.historyStack.push(this.grid.clone());
71
+ // Apply it
72
+ this.solver.applyClue(this.grid, bestClue);
73
+ this.proofChain.push(bestClue);
74
+ // Remove from available
75
+ const idx = this.availableClues.indexOf(bestClue);
76
+ if (idx > -1)
77
+ this.availableClues.splice(idx, 1);
78
+ return { clue: bestClue, remaining: this.availableClues.length, solved: this.generator.isPuzzleSolved(this.grid, this.solution, this.reverseSolution) };
79
+ }
80
+ return { clue: null, remaining: validClues.length, solved: this.generator.isPuzzleSolved(this.grid, this.solution, this.reverseSolution) };
81
+ }
82
+ rollbackLastClue() {
83
+ if (this.historyStack.length === 0)
84
+ return { success: false, clue: null };
85
+ const prevGrid = this.historyStack.pop();
86
+ if (prevGrid)
87
+ this.grid = prevGrid;
88
+ const lastClue = this.proofChain.pop();
89
+ if (lastClue) {
90
+ this.availableClues.push(lastClue);
91
+ return { success: true, clue: lastClue };
92
+ }
93
+ return { success: false, clue: null };
94
+ }
95
+ isUseful(grid) {
96
+ // Did it eliminate anything?
97
+ // LogicGrid could report this.
98
+ // We compare clone vs original?
99
+ // Actually Solver returns deductions count.
100
+ // If deductions > 0, it's useful.
101
+ return true;
102
+ }
103
+ // Getters for UI
104
+ getGrid() { return this.grid; }
105
+ getProofChain() { return this.proofChain; }
106
+ getSolution() { return this.solution; }
107
+ getValueMap() { return this.valueMap; }
108
+ }
109
+ exports.GenerativeSession = GenerativeSession;
@@ -0,0 +1,119 @@
1
+ import { CategoryConfig, ValueLabel, Solution, TargetFact, ClueGenerationConstraints } from '../types';
2
+ import { Clue } from './Clue';
3
+ import { LogicGrid } from './LogicGrid';
4
+ import { GenerativeSession } from './GenerativeSession';
5
+ /**
6
+ * Represents a single step in the logical deduction path.
7
+ */
8
+ export interface ProofStep {
9
+ /** The clue applied at this step. */
10
+ clue: Clue;
11
+ /** The number of logical eliminations that resulted immediately from this clue. */
12
+ deductions: number;
13
+ }
14
+ /**
15
+ * The complete result of the puzzle generation process.
16
+ */
17
+ export interface Puzzle {
18
+ /** The solution grid (Category -> Value -> Corresponding Value). */
19
+ solution: Solution;
20
+ /** The list of clues needed to solve the puzzle, in no particular order. */
21
+ clues: Clue[];
22
+ /** An ordered list of clues that demonstrates a step-by-step logical solution. */
23
+ proofChain: ProofStep[];
24
+ /** The configuration used to generate this puzzle. */
25
+ categories: CategoryConfig[];
26
+ /** The specific fact that the puzzle is designed to reveal at the end. */
27
+ targetFact: TargetFact;
28
+ }
29
+ /**
30
+ * Configuration options for the puzzle generation process.
31
+ */
32
+ export interface GeneratorOptions {
33
+ /**
34
+ * The maximum number of candidate clues to evaluate at each step.
35
+ * Lower values improve performance for large grids but may slightly reduce puzzle quality.
36
+ * Default: Infinity (Exhaustive search).
37
+ */
38
+ maxCandidates?: number;
39
+ /**
40
+ * Attempt to generate a puzzle with exactly this many clues.
41
+ * The generator will use backtracking to find a path of this length.
42
+ * May throw an error if the count is infeasible.
43
+ */
44
+ targetClueCount?: number;
45
+ /**
46
+ * Timeout in milliseconds for the generation process.
47
+ * Default: 10000ms (10s).
48
+ */
49
+ timeoutMs?: number;
50
+ /**
51
+ * Constraints to filter the types of clues generated.
52
+ */
53
+ constraints?: ClueGenerationConstraints;
54
+ /**
55
+ * Callback for trace logs execution details.
56
+ */
57
+ onTrace?: (message: string) => void;
58
+ }
59
+ /**
60
+ * The main class responsible for generating logic puzzles.
61
+ *
62
+ * It handles the creation of a consistent solution, the generation of all possible clues,
63
+ * and the selection of an optimal set of clues to form a solvable puzzle with a specific target.
64
+ */
65
+ export declare class Generator {
66
+ private seed;
67
+ private random;
68
+ private solver;
69
+ private solution;
70
+ private valueMap;
71
+ private reverseSolution;
72
+ /**
73
+ * Creates a new Generator instance.
74
+ *
75
+ * @param seed - A numeric seed for the random number generator to ensure reproducibility.
76
+ */
77
+ constructor(seed: number);
78
+ /**
79
+ * Generates a fully solvable logic puzzle based on the provided configuration.
80
+ * Estimates the minimum and maximum number of clues required to solve a puzzle
81
+ * for the given configuration and target, by running several simulations.
82
+ * This is a computationally intensive operation.
83
+ *
84
+ * @param categories - The categories and values to include in the puzzle.
85
+ * @param target - The specific fact that should be the final deduction of the puzzle.
86
+ * @returns A promise resolving to an object containing the estimated min and max clue counts.
87
+ */
88
+ getClueCountBounds(categories: CategoryConfig[], target: TargetFact, maxIterations?: number): {
89
+ min: number;
90
+ max: number;
91
+ };
92
+ /**
93
+ * Generates a fully solvable logic puzzle.
94
+ * @param categories - The categories config.
95
+ * @param target - Optional target fact. If missing, a random one is synthesized.
96
+ * @param config - Generation options.
97
+ */
98
+ generatePuzzle(categories: CategoryConfig[], target?: TargetFact, config?: GeneratorOptions): Puzzle;
99
+ /**
100
+ * Starts an interactive generative session.
101
+ * @param categories
102
+ * @param target Optional target fact.
103
+ */
104
+ startSession(categories: CategoryConfig[], target?: TargetFact): GenerativeSession;
105
+ /**
106
+ * Internal generation method exposed for simulations.
107
+ * @param strategy 'standard' | 'min' | 'max'
108
+ */
109
+ internalGenerate(categories: CategoryConfig[], target: TargetFact, strategy: 'standard' | 'min' | 'max', options?: GeneratorOptions): Puzzle;
110
+ private generateWithBacktracking;
111
+ private createSolution;
112
+ generateAllPossibleClues(categories: CategoryConfig[], constraints: ClueGenerationConstraints | undefined, reverseSolution: Map<string, Map<ValueLabel, ValueLabel>>, valueMap: Map<ValueLabel, Record<string, ValueLabel>>): Clue[];
113
+ /**
114
+ * Calculates the heuristic score for a candidate clue.
115
+ * Higher scores represent better clues according to the current strategy.
116
+ */
117
+ calculateClueScore(grid: LogicGrid, target: TargetFact, deductions: number, clue: Clue, previouslySelectedClues: Clue[], solution: Solution, reverseSolution: Map<string, Map<ValueLabel, ValueLabel>>): number;
118
+ isPuzzleSolved(grid: LogicGrid, solution: Solution, reverseSolution: Map<string, Map<ValueLabel, ValueLabel>>): boolean;
119
+ }