logic-puzzle-generator 1.2.4 → 1.3.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.
|
@@ -128,6 +128,7 @@ class GenerativeSession {
|
|
|
128
128
|
// Apply it
|
|
129
129
|
const result = this.solver.applyClue(this.grid, clue);
|
|
130
130
|
clue.deductions = result.deductions;
|
|
131
|
+
clue.reasons = result.reasons;
|
|
131
132
|
// Calculate % Complete
|
|
132
133
|
// Grid starts with 'totalPossible' and ends with 'solutionPossible'.
|
|
133
134
|
// % = (Total - Current) / (Total - Solution)
|
|
@@ -21,31 +21,32 @@ class Solver {
|
|
|
21
21
|
*/
|
|
22
22
|
applyClue(grid, clue) {
|
|
23
23
|
let deductions = 0;
|
|
24
|
+
const reasons = [];
|
|
24
25
|
switch (clue.type) {
|
|
25
26
|
case types_1.ClueType.BINARY:
|
|
26
|
-
deductions += this.applyBinaryClue(grid, clue);
|
|
27
|
+
deductions += this.applyBinaryClue(grid, clue, reasons);
|
|
27
28
|
break;
|
|
28
29
|
case types_1.ClueType.SUPERLATIVE:
|
|
29
|
-
deductions += this.applySuperlativeClue(grid, clue);
|
|
30
|
+
deductions += this.applySuperlativeClue(grid, clue, reasons);
|
|
30
31
|
break;
|
|
31
32
|
case types_1.ClueType.ORDINAL:
|
|
32
|
-
deductions += this.applyOrdinalClue(grid, clue);
|
|
33
|
+
deductions += this.applyOrdinalClue(grid, clue, reasons);
|
|
33
34
|
break;
|
|
34
35
|
case types_1.ClueType.UNARY:
|
|
35
|
-
deductions += this.applyUnaryClue(grid, clue);
|
|
36
|
+
deductions += this.applyUnaryClue(grid, clue, reasons);
|
|
36
37
|
break;
|
|
37
38
|
case types_1.ClueType.CROSS_ORDINAL:
|
|
38
|
-
deductions += this.applyCrossOrdinalClue(grid, clue);
|
|
39
|
+
deductions += this.applyCrossOrdinalClue(grid, clue, reasons);
|
|
39
40
|
break;
|
|
40
41
|
}
|
|
41
42
|
let newDeductions;
|
|
42
43
|
do {
|
|
43
|
-
newDeductions = this.runDeductionLoop(grid);
|
|
44
|
+
newDeductions = this.runDeductionLoop(grid, reasons);
|
|
44
45
|
deductions += newDeductions;
|
|
45
46
|
} while (newDeductions > 0);
|
|
46
|
-
return { grid, deductions };
|
|
47
|
+
return { grid, deductions, reasons };
|
|
47
48
|
}
|
|
48
|
-
applyCrossOrdinalClue(grid, clue) {
|
|
49
|
+
applyCrossOrdinalClue(grid, clue, reasons) {
|
|
49
50
|
let deductions = 0;
|
|
50
51
|
const categories = grid.categories;
|
|
51
52
|
const ord1Config = categories.find(c => c.id === clue.ordinal1);
|
|
@@ -71,6 +72,11 @@ class Solver {
|
|
|
71
72
|
if (targetVal1 === undefined) {
|
|
72
73
|
grid.setPossibility(clue.item1Cat, clue.item1Val, clue.ordinal1, cand1.val, false);
|
|
73
74
|
deductions++;
|
|
75
|
+
reasons.push({
|
|
76
|
+
type: 'cross_ordinal',
|
|
77
|
+
description: `Cross-Ordinal: ${clue.item1Val} cannot be ${cand1.val} because offset ${clue.offset1} goes out of bounds.`,
|
|
78
|
+
cells: [{ cat: clue.item1Cat, val: clue.item1Val }, { cat: clue.ordinal1, val: cand1.val }]
|
|
79
|
+
});
|
|
74
80
|
continue;
|
|
75
81
|
}
|
|
76
82
|
// Compatibility check
|
|
@@ -90,6 +96,11 @@ class Solver {
|
|
|
90
96
|
if (!supported) {
|
|
91
97
|
grid.setPossibility(clue.item1Cat, clue.item1Val, clue.ordinal1, cand1.val, false);
|
|
92
98
|
deductions++;
|
|
99
|
+
reasons.push({
|
|
100
|
+
type: 'cross_ordinal',
|
|
101
|
+
description: `Cross-Ordinal: ${clue.item1Val} as ${cand1.val} finds no compatible ${clue.item2Val} at offset pair.`,
|
|
102
|
+
cells: [{ cat: clue.item1Cat, val: clue.item1Val }, { cat: clue.ordinal1, val: cand1.val }]
|
|
103
|
+
});
|
|
93
104
|
}
|
|
94
105
|
}
|
|
95
106
|
// Filter 2 based on 1 (Symmetric)
|
|
@@ -99,6 +110,11 @@ class Solver {
|
|
|
99
110
|
if (targetVal2 === undefined) {
|
|
100
111
|
grid.setPossibility(clue.item2Cat, clue.item2Val, clue.ordinal2, cand2.val, false);
|
|
101
112
|
deductions++;
|
|
113
|
+
reasons.push({
|
|
114
|
+
type: 'cross_ordinal',
|
|
115
|
+
description: `Cross-Ordinal: ${clue.item2Val} cannot be ${cand2.val} because offset ${clue.offset2} goes out of bounds.`,
|
|
116
|
+
cells: [{ cat: clue.item2Cat, val: clue.item2Val }, { cat: clue.ordinal1, val: cand2.val }]
|
|
117
|
+
});
|
|
102
118
|
continue;
|
|
103
119
|
}
|
|
104
120
|
let supported = false;
|
|
@@ -115,6 +131,11 @@ class Solver {
|
|
|
115
131
|
if (!supported) {
|
|
116
132
|
grid.setPossibility(clue.item2Cat, clue.item2Val, clue.ordinal2, cand2.val, false);
|
|
117
133
|
deductions++;
|
|
134
|
+
reasons.push({
|
|
135
|
+
type: 'cross_ordinal',
|
|
136
|
+
description: `Cross-Ordinal: ${clue.item2Val} as ${cand2.val} finds no compatible ${clue.item1Val} at offset pair.`,
|
|
137
|
+
cells: [{ cat: clue.item2Cat, val: clue.item2Val }, { cat: clue.ordinal2, val: cand2.val }]
|
|
138
|
+
});
|
|
118
139
|
}
|
|
119
140
|
}
|
|
120
141
|
// Lock linkage if unique
|
|
@@ -130,6 +151,11 @@ class Solver {
|
|
|
130
151
|
if (grid.getPossibilitiesCount(clue.ordinal1, v1, clue.ordinal2) > 1) {
|
|
131
152
|
grid.setPossibility(clue.ordinal1, v1, clue.ordinal2, v2, true);
|
|
132
153
|
deductions++;
|
|
154
|
+
reasons.push({
|
|
155
|
+
type: 'cross_ordinal',
|
|
156
|
+
description: `Cross-Ordinal Link: ${v1} must be ${v2} based on forced offsets.`,
|
|
157
|
+
cells: [{ cat: clue.ordinal1, val: v1 }, { cat: clue.ordinal2, val: v2 }]
|
|
158
|
+
});
|
|
133
159
|
}
|
|
134
160
|
}
|
|
135
161
|
}
|
|
@@ -160,6 +186,11 @@ class Solver {
|
|
|
160
186
|
if (grid.isPossible(clue.ordinal1, v1, clue.ordinal2, v2)) {
|
|
161
187
|
grid.setPossibility(clue.ordinal1, v1, clue.ordinal2, v2, false);
|
|
162
188
|
deductions++;
|
|
189
|
+
reasons.push({
|
|
190
|
+
type: 'cross_ordinal',
|
|
191
|
+
description: `Cross-Ordinal NOT: ${v1} cannot be ${v2} because positions are forbidden.`,
|
|
192
|
+
cells: [{ cat: clue.ordinal1, val: v1 }, { cat: clue.ordinal2, val: v2 }]
|
|
193
|
+
});
|
|
163
194
|
}
|
|
164
195
|
}
|
|
165
196
|
}
|
|
@@ -328,6 +359,11 @@ class Solver {
|
|
|
328
359
|
if (grid.isPossible(clue.ordinal1, v1, clue.ordinal2, v2)) {
|
|
329
360
|
grid.setPossibility(clue.item2Cat, clue.item2Val, clue.ordinal2, cand2.val, false);
|
|
330
361
|
deductions++;
|
|
362
|
+
reasons.push({
|
|
363
|
+
type: 'cross_ordinal',
|
|
364
|
+
description: `Cross-Ordinal NOT: ${clue.item2Val} cannot be ${cand2.val} because it implies forbidden link to ${v1}.`,
|
|
365
|
+
cells: [{ cat: clue.item2Cat, val: clue.item2Val }, { cat: clue.ordinal2, val: cand2.val }]
|
|
366
|
+
});
|
|
331
367
|
}
|
|
332
368
|
}
|
|
333
369
|
}
|
|
@@ -345,6 +381,11 @@ class Solver {
|
|
|
345
381
|
if (grid.isPossible(clue.ordinal1, v1, clue.ordinal2, v2)) {
|
|
346
382
|
grid.setPossibility(clue.item1Cat, clue.item1Val, clue.ordinal1, cand1.val, false);
|
|
347
383
|
deductions++;
|
|
384
|
+
reasons.push({
|
|
385
|
+
type: 'cross_ordinal',
|
|
386
|
+
description: `Cross-Ordinal NOT: ${clue.item1Val} cannot be ${cand1.val} because it implies forbidden link to ${v2}.`,
|
|
387
|
+
cells: [{ cat: clue.item1Cat, val: clue.item1Val }, { cat: clue.ordinal1, val: cand1.val }]
|
|
388
|
+
});
|
|
348
389
|
}
|
|
349
390
|
}
|
|
350
391
|
}
|
|
@@ -353,7 +394,7 @@ class Solver {
|
|
|
353
394
|
}
|
|
354
395
|
return deductions;
|
|
355
396
|
}
|
|
356
|
-
applyUnaryClue(grid, clue) {
|
|
397
|
+
applyUnaryClue(grid, clue, reasons) {
|
|
357
398
|
let deductions = 0;
|
|
358
399
|
const categories = grid.categories;
|
|
359
400
|
const ordinalCatConfig = categories.find(c => c.id === clue.ordinalCat);
|
|
@@ -369,12 +410,17 @@ class Solver {
|
|
|
369
410
|
if (grid.isPossible(clue.targetCat, clue.targetVal, clue.ordinalCat, ordVal)) {
|
|
370
411
|
grid.setPossibility(clue.targetCat, clue.targetVal, clue.ordinalCat, ordVal, false);
|
|
371
412
|
deductions++;
|
|
413
|
+
reasons.push({
|
|
414
|
+
type: 'unary',
|
|
415
|
+
description: `Unary Rule: ${ordVal} eliminated for ${clue.targetVal} because it is ${isEven ? 'not even' : 'not odd'}.`,
|
|
416
|
+
cells: [{ cat: clue.targetCat, val: clue.targetVal }, { cat: clue.ordinalCat, val: String(ordVal) }]
|
|
417
|
+
});
|
|
372
418
|
}
|
|
373
419
|
}
|
|
374
420
|
}
|
|
375
421
|
return deductions;
|
|
376
422
|
}
|
|
377
|
-
applyBinaryClue(grid, clue) {
|
|
423
|
+
applyBinaryClue(grid, clue, reasons) {
|
|
378
424
|
let deductions = 0;
|
|
379
425
|
const categories = grid.categories;
|
|
380
426
|
const cat1Config = categories.find(c => c.id === clue.cat1);
|
|
@@ -385,6 +431,11 @@ class Solver {
|
|
|
385
431
|
// Count the "Positive Confirmation" (Green Check) as a deduction if it wasn't already known
|
|
386
432
|
if (grid.getPossibilitiesCount(clue.cat1, clue.val1, clue.cat2) > 1) {
|
|
387
433
|
deductions++;
|
|
434
|
+
reasons.push({
|
|
435
|
+
type: 'confirmation',
|
|
436
|
+
description: `Directly from clue: ${clue.val1} is ${clue.val2}.`,
|
|
437
|
+
cells: [{ cat: clue.cat1, val: clue.val1 }, { cat: clue.cat2, val: clue.val2 }]
|
|
438
|
+
});
|
|
388
439
|
}
|
|
389
440
|
if (grid.isPossible(clue.cat1, clue.val1, clue.cat2, clue.val2)) {
|
|
390
441
|
// This is not a deduction, but a fact application. Still, we need to eliminate other possibilities.
|
|
@@ -395,6 +446,11 @@ class Solver {
|
|
|
395
446
|
if (grid.isPossible(clue.cat1, clue.val1, clue.cat2, val)) {
|
|
396
447
|
grid.setPossibility(clue.cat1, clue.val1, clue.cat2, val, false);
|
|
397
448
|
deductions++;
|
|
449
|
+
reasons.push({
|
|
450
|
+
type: 'elimination',
|
|
451
|
+
description: `Since ${clue.val1} is ${clue.val2}, it cannot be ${val}.`,
|
|
452
|
+
cells: [{ cat: clue.cat1, val: clue.val1 }, { cat: clue.cat2, val: val }]
|
|
453
|
+
});
|
|
398
454
|
}
|
|
399
455
|
}
|
|
400
456
|
}
|
|
@@ -403,6 +459,11 @@ class Solver {
|
|
|
403
459
|
if (grid.isPossible(clue.cat1, val, clue.cat2, clue.val2)) {
|
|
404
460
|
grid.setPossibility(clue.cat1, val, clue.cat2, clue.val2, false);
|
|
405
461
|
deductions++;
|
|
462
|
+
reasons.push({
|
|
463
|
+
type: 'elimination',
|
|
464
|
+
description: `Since ${clue.val2} is ${clue.val1}, it cannot be ${val}.`,
|
|
465
|
+
cells: [{ cat: clue.cat1, val: val }, { cat: clue.cat2, val: clue.val2 }]
|
|
466
|
+
});
|
|
406
467
|
}
|
|
407
468
|
}
|
|
408
469
|
}
|
|
@@ -411,11 +472,16 @@ class Solver {
|
|
|
411
472
|
if (grid.isPossible(clue.cat1, clue.val1, clue.cat2, clue.val2)) {
|
|
412
473
|
grid.setPossibility(clue.cat1, clue.val1, clue.cat2, clue.val2, false);
|
|
413
474
|
deductions++;
|
|
475
|
+
reasons.push({
|
|
476
|
+
type: 'elimination',
|
|
477
|
+
description: `Directly from clue: ${clue.val1} is NOT ${clue.val2}.`,
|
|
478
|
+
cells: [{ cat: clue.cat1, val: clue.val1 }, { cat: clue.cat2, val: clue.val2 }]
|
|
479
|
+
});
|
|
414
480
|
}
|
|
415
481
|
}
|
|
416
482
|
return deductions;
|
|
417
483
|
}
|
|
418
|
-
runDeductionLoop(grid) {
|
|
484
|
+
runDeductionLoop(grid, reasons) {
|
|
419
485
|
let deductions = 0;
|
|
420
486
|
const categories = grid.categories;
|
|
421
487
|
for (const cat1 of categories) {
|
|
@@ -433,6 +499,11 @@ class Solver {
|
|
|
433
499
|
if (grid.isPossible(cat1.id, otherVal1, cat2.id, val2)) {
|
|
434
500
|
grid.setPossibility(cat1.id, otherVal1, cat2.id, val2, false);
|
|
435
501
|
deductions++;
|
|
502
|
+
reasons.push({
|
|
503
|
+
type: 'uniqueness',
|
|
504
|
+
description: `Since ${val2} is uniquely ${val1} in ${cat1.id}, it cannot be ${otherVal1}.`,
|
|
505
|
+
cells: [{ cat: cat1.id, val: val1 }, { cat: cat2.id, val: val2 }, { cat: cat1.id, val: otherVal1 }]
|
|
506
|
+
});
|
|
436
507
|
}
|
|
437
508
|
}
|
|
438
509
|
}
|
|
@@ -453,6 +524,11 @@ class Solver {
|
|
|
453
524
|
else if (grid.getPossibilitiesCount(cat1.id, val1, cat3.id) > 1) {
|
|
454
525
|
grid.setPossibility(cat1.id, val1, cat3.id, definiteVal3, true);
|
|
455
526
|
deductions++;
|
|
527
|
+
reasons.push({
|
|
528
|
+
type: 'transitivity',
|
|
529
|
+
description: `Since ${cat1.id}:${val1} is ${cat2.id}:${definiteVal2}, and that is ${cat3.id}:${definiteVal3}, ${val1} must be ${definiteVal3}.`,
|
|
530
|
+
cells: [{ cat: cat1.id, val: val1 }, { cat: cat2.id, val: definiteVal2 }, { cat: cat3.id, val: definiteVal3 }]
|
|
531
|
+
});
|
|
456
532
|
}
|
|
457
533
|
}
|
|
458
534
|
}
|
|
@@ -463,6 +539,11 @@ class Solver {
|
|
|
463
539
|
if (!isPathPossible) {
|
|
464
540
|
grid.setPossibility(cat1.id, val1, cat3.id, val3, false);
|
|
465
541
|
deductions++;
|
|
542
|
+
reasons.push({
|
|
543
|
+
type: 'transitivity',
|
|
544
|
+
description: `Negative Transitivity: No path exists between ${val1} and ${val3} via ${cat2.id}.`,
|
|
545
|
+
cells: [{ cat: cat1.id, val: val1 }, { cat: cat3.id, val: val3 }]
|
|
546
|
+
});
|
|
466
547
|
}
|
|
467
548
|
}
|
|
468
549
|
}
|
|
@@ -472,7 +553,7 @@ class Solver {
|
|
|
472
553
|
}
|
|
473
554
|
return deductions;
|
|
474
555
|
}
|
|
475
|
-
applyOrdinalClue(grid, constraint) {
|
|
556
|
+
applyOrdinalClue(grid, constraint, reasons) {
|
|
476
557
|
let deductions = 0;
|
|
477
558
|
const categories = grid.categories;
|
|
478
559
|
const ordCatConfig = categories.find(c => c.id === constraint.ordinalCat);
|
|
@@ -494,6 +575,11 @@ class Solver {
|
|
|
494
575
|
if (grid.isPossible(constraint.item1Cat, constraint.item1Val, constraint.ordinalCat, pval1.val)) {
|
|
495
576
|
grid.setPossibility(constraint.item1Cat, constraint.item1Val, constraint.ordinalCat, pval1.val, false);
|
|
496
577
|
deductions++;
|
|
578
|
+
reasons.push({
|
|
579
|
+
type: 'ordinal',
|
|
580
|
+
description: `${constraint.item1Val} cannot be ${pval1.val} (idx ${pval1.idx}) because it must be > ${constraint.item2Val}.`,
|
|
581
|
+
cells: [{ cat: constraint.item1Cat, val: constraint.item1Val }, { cat: constraint.ordinalCat, val: pval1.val }]
|
|
582
|
+
});
|
|
497
583
|
}
|
|
498
584
|
}
|
|
499
585
|
}
|
|
@@ -505,6 +591,11 @@ class Solver {
|
|
|
505
591
|
if (grid.isPossible(constraint.item1Cat, constraint.item1Val, constraint.ordinalCat, pval1.val)) {
|
|
506
592
|
grid.setPossibility(constraint.item1Cat, constraint.item1Val, constraint.ordinalCat, pval1.val, false);
|
|
507
593
|
deductions++;
|
|
594
|
+
reasons.push({
|
|
595
|
+
type: 'ordinal',
|
|
596
|
+
description: `${constraint.item1Val} cannot be ${pval1.val} (idx ${pval1.idx}) because it must be < ${constraint.item2Val}.`,
|
|
597
|
+
cells: [{ cat: constraint.item1Cat, val: constraint.item1Val }, { cat: constraint.ordinalCat, val: pval1.val }]
|
|
598
|
+
});
|
|
508
599
|
}
|
|
509
600
|
}
|
|
510
601
|
}
|
|
@@ -516,6 +607,11 @@ class Solver {
|
|
|
516
607
|
if (grid.isPossible(constraint.item1Cat, constraint.item1Val, constraint.ordinalCat, pval1.val)) {
|
|
517
608
|
grid.setPossibility(constraint.item1Cat, constraint.item1Val, constraint.ordinalCat, pval1.val, false);
|
|
518
609
|
deductions++;
|
|
610
|
+
reasons.push({
|
|
611
|
+
type: 'ordinal',
|
|
612
|
+
description: `${constraint.item1Val} cannot be ${pval1.val} (idx ${pval1.idx}) because it must be <= ${constraint.item2Val}.`,
|
|
613
|
+
cells: [{ cat: constraint.item1Cat, val: constraint.item1Val }, { cat: constraint.ordinalCat, val: pval1.val }]
|
|
614
|
+
});
|
|
519
615
|
}
|
|
520
616
|
}
|
|
521
617
|
}
|
|
@@ -527,6 +623,11 @@ class Solver {
|
|
|
527
623
|
if (grid.isPossible(constraint.item1Cat, constraint.item1Val, constraint.ordinalCat, pval1.val)) {
|
|
528
624
|
grid.setPossibility(constraint.item1Cat, constraint.item1Val, constraint.ordinalCat, pval1.val, false);
|
|
529
625
|
deductions++;
|
|
626
|
+
reasons.push({
|
|
627
|
+
type: 'ordinal',
|
|
628
|
+
description: `${constraint.item1Val} cannot be ${pval1.val} (idx ${pval1.idx}) because it must be >= ${constraint.item2Val}.`,
|
|
629
|
+
cells: [{ cat: constraint.item1Cat, val: constraint.item1Val }, { cat: constraint.ordinalCat, val: pval1.val }]
|
|
630
|
+
});
|
|
530
631
|
}
|
|
531
632
|
}
|
|
532
633
|
}
|
|
@@ -539,6 +640,11 @@ class Solver {
|
|
|
539
640
|
if (grid.isPossible(constraint.item2Cat, constraint.item2Val, constraint.ordinalCat, pval2.val)) {
|
|
540
641
|
grid.setPossibility(constraint.item2Cat, constraint.item2Val, constraint.ordinalCat, pval2.val, false);
|
|
541
642
|
deductions++;
|
|
643
|
+
reasons.push({
|
|
644
|
+
type: 'ordinal',
|
|
645
|
+
description: `${constraint.item2Val} cannot be ${pval2.val} (idx ${pval2.idx}) because it must be < ${constraint.item1Val}.`,
|
|
646
|
+
cells: [{ cat: constraint.item2Cat, val: constraint.item2Val }, { cat: constraint.ordinalCat, val: pval2.val }]
|
|
647
|
+
});
|
|
542
648
|
}
|
|
543
649
|
}
|
|
544
650
|
}
|
|
@@ -550,6 +656,11 @@ class Solver {
|
|
|
550
656
|
if (grid.isPossible(constraint.item2Cat, constraint.item2Val, constraint.ordinalCat, pval2.val)) {
|
|
551
657
|
grid.setPossibility(constraint.item2Cat, constraint.item2Val, constraint.ordinalCat, pval2.val, false);
|
|
552
658
|
deductions++;
|
|
659
|
+
reasons.push({
|
|
660
|
+
type: 'ordinal',
|
|
661
|
+
description: `${constraint.item2Val} cannot be ${pval2.val} (idx ${pval2.idx}) because it must be > ${constraint.item1Val}.`,
|
|
662
|
+
cells: [{ cat: constraint.item2Cat, val: constraint.item2Val }, { cat: constraint.ordinalCat, val: pval2.val }]
|
|
663
|
+
});
|
|
553
664
|
}
|
|
554
665
|
}
|
|
555
666
|
}
|
|
@@ -561,6 +672,11 @@ class Solver {
|
|
|
561
672
|
if (grid.isPossible(constraint.item2Cat, constraint.item2Val, constraint.ordinalCat, pval2.val)) {
|
|
562
673
|
grid.setPossibility(constraint.item2Cat, constraint.item2Val, constraint.ordinalCat, pval2.val, false);
|
|
563
674
|
deductions++;
|
|
675
|
+
reasons.push({
|
|
676
|
+
type: 'ordinal',
|
|
677
|
+
description: `${constraint.item2Val} cannot be ${pval2.val} (idx ${pval2.idx}) because it must be >= ${constraint.item1Val}.`,
|
|
678
|
+
cells: [{ cat: constraint.item2Cat, val: constraint.item2Val }, { cat: constraint.ordinalCat, val: pval2.val }]
|
|
679
|
+
});
|
|
564
680
|
}
|
|
565
681
|
}
|
|
566
682
|
}
|
|
@@ -572,13 +688,18 @@ class Solver {
|
|
|
572
688
|
if (grid.isPossible(constraint.item2Cat, constraint.item2Val, constraint.ordinalCat, pval2.val)) {
|
|
573
689
|
grid.setPossibility(constraint.item2Cat, constraint.item2Val, constraint.ordinalCat, pval2.val, false);
|
|
574
690
|
deductions++;
|
|
691
|
+
reasons.push({
|
|
692
|
+
type: 'ordinal',
|
|
693
|
+
description: `${constraint.item2Val} cannot be ${pval2.val} (idx ${pval2.idx}) because it must be <= ${constraint.item1Val}.`,
|
|
694
|
+
cells: [{ cat: constraint.item2Cat, val: constraint.item2Val }, { cat: constraint.ordinalCat, val: pval2.val }]
|
|
695
|
+
});
|
|
575
696
|
}
|
|
576
697
|
}
|
|
577
698
|
}
|
|
578
699
|
}
|
|
579
700
|
return deductions;
|
|
580
701
|
}
|
|
581
|
-
applySuperlativeClue(grid, clue) {
|
|
702
|
+
applySuperlativeClue(grid, clue, reasons) {
|
|
582
703
|
const categories = grid.categories;
|
|
583
704
|
const ordinalCatConfig = categories.find(c => c.id === clue.ordinalCat);
|
|
584
705
|
if (!ordinalCatConfig || ordinalCatConfig.type !== types_1.CategoryType.ORDINAL)
|
|
@@ -611,7 +732,7 @@ class Solver {
|
|
|
611
732
|
val2: extremeValue,
|
|
612
733
|
operator: isNot ? types_1.BinaryOperator.IS_NOT : types_1.BinaryOperator.IS,
|
|
613
734
|
};
|
|
614
|
-
return this.applyBinaryClue(grid, binaryClue);
|
|
735
|
+
return this.applyBinaryClue(grid, binaryClue, reasons);
|
|
615
736
|
}
|
|
616
737
|
}
|
|
617
738
|
exports.Solver = Solver;
|
package/dist/src/types.d.ts
CHANGED
|
@@ -103,3 +103,11 @@ export interface ClueGenerationConstraints {
|
|
|
103
103
|
*/
|
|
104
104
|
maxDeductions?: number;
|
|
105
105
|
}
|
|
106
|
+
export interface DeductionReason {
|
|
107
|
+
type: 'elimination' | 'confirmation' | 'uniqueness' | 'transitivity' | 'clue' | 'unary' | 'ordinal' | 'cross_ordinal';
|
|
108
|
+
description: string;
|
|
109
|
+
cells?: {
|
|
110
|
+
cat: string;
|
|
111
|
+
val: ValueLabel;
|
|
112
|
+
}[];
|
|
113
|
+
}
|
package/package.json
CHANGED