@logic-pad/core 0.4.6 → 0.6.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/assets/logic-core.global.d.ts +134 -25
- package/dist/data/grid.d.ts +44 -14
- package/dist/data/grid.js +43 -36
- package/dist/data/primitives.d.ts +19 -1
- package/dist/data/primitives.js +20 -0
- package/dist/data/rules/banPatternRule.js +1 -1
- package/dist/data/rules/customRule.js +1 -1
- package/dist/data/rules/lyingSymbolRule.d.ts +30 -0
- package/dist/data/rules/lyingSymbolRule.js +239 -0
- package/dist/data/rules/musicGridRule.js +1 -1
- package/dist/data/rules/mysteryRule.js +2 -2
- package/dist/data/rules/offByXRule.d.ts +1 -1
- package/dist/data/rules/offByXRule.js +7 -3
- package/dist/data/rules/regionAreaRule.js +3 -3
- package/dist/data/rules/rules.gen.d.ts +1 -0
- package/dist/data/rules/rules.gen.js +1 -0
- package/dist/data/serializer/serializer_v0.js +1 -1
- package/dist/data/solver/allSolvers.js +2 -2
- package/dist/data/solver/backtrack/backtrackSolver.d.ts +3 -4
- package/dist/data/solver/backtrack/backtrackSolver.js +4 -30
- package/dist/data/solver/backtrack/backtrackWorker.d.ts +1 -2
- package/dist/data/solver/backtrack/backtrackWorker.js +12 -8
- package/dist/data/solver/eventIteratingSolver.d.ts +6 -0
- package/dist/data/solver/eventIteratingSolver.js +33 -0
- package/dist/data/solver/solver.d.ts +6 -1
- package/dist/data/solver/solver.js +2 -1
- package/dist/data/solver/universal/universalSolver.d.ts +7 -0
- package/dist/data/solver/universal/universalSolver.js +30 -0
- package/dist/data/solver/universal/universalWorker.d.ts +1 -0
- package/dist/data/solver/universal/universalWorker.js +119 -0
- package/dist/data/symbols/customIconSymbol.js +2 -2
- package/dist/data/symbols/customTextSymbol.js +2 -2
- package/dist/data/validate.d.ts +1 -1
- package/dist/data/validate.js +3 -3
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -2
- package/package.json +1 -1
- package/dist/data/solver/underclued/undercluedSolver.d.ts +0 -8
- package/dist/data/solver/underclued/undercluedSolver.js +0 -55
- package/dist/data/solver/underclued/undercluedWorker.d.ts +0 -2
- package/dist/data/solver/underclued/undercluedWorker.js +0 -135
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { ConfigType } from '../config.js';
|
|
2
|
+
import GridData from '../grid.js';
|
|
3
|
+
import { Color, Orientation, State, } from '../primitives.js';
|
|
4
|
+
import Rule from './rule.js';
|
|
5
|
+
import validateGrid from '../validate.js';
|
|
6
|
+
import Symbol from '../symbols/symbol.js';
|
|
7
|
+
import LetterSymbol from '../symbols/letterSymbol.js';
|
|
8
|
+
import GalaxySymbol from '../symbols/galaxySymbol.js';
|
|
9
|
+
import LotusSymbol from '../symbols/lotusSymbol.js';
|
|
10
|
+
import AreaNumberSymbol from '../symbols/areaNumberSymbol.js';
|
|
11
|
+
class IgnoredSymbol extends Symbol {
|
|
12
|
+
constructor(symbol, state) {
|
|
13
|
+
super(symbol.x, symbol.y);
|
|
14
|
+
Object.defineProperty(this, "symbol", {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
configurable: true,
|
|
17
|
+
writable: true,
|
|
18
|
+
value: symbol
|
|
19
|
+
});
|
|
20
|
+
Object.defineProperty(this, "state", {
|
|
21
|
+
enumerable: true,
|
|
22
|
+
configurable: true,
|
|
23
|
+
writable: true,
|
|
24
|
+
value: state
|
|
25
|
+
});
|
|
26
|
+
this.symbol = symbol;
|
|
27
|
+
this.state = state;
|
|
28
|
+
}
|
|
29
|
+
get id() {
|
|
30
|
+
return `ignored_${this.symbol.id}`;
|
|
31
|
+
}
|
|
32
|
+
get explanation() {
|
|
33
|
+
return this.symbol.explanation;
|
|
34
|
+
}
|
|
35
|
+
get configs() {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
createExampleGrid() {
|
|
39
|
+
return this.symbol.createExampleGrid();
|
|
40
|
+
}
|
|
41
|
+
get necessaryForCompletion() {
|
|
42
|
+
return this.symbol.necessaryForCompletion;
|
|
43
|
+
}
|
|
44
|
+
get visibleWhenSolving() {
|
|
45
|
+
return this.symbol.visibleWhenSolving;
|
|
46
|
+
}
|
|
47
|
+
get sortOrder() {
|
|
48
|
+
return this.symbol.sortOrder;
|
|
49
|
+
}
|
|
50
|
+
validateSymbol(_grid, _solution) {
|
|
51
|
+
return this.state;
|
|
52
|
+
}
|
|
53
|
+
copyWith({ symbol, state }) {
|
|
54
|
+
return new IgnoredSymbol(symbol ?? this.symbol, state ?? this.state);
|
|
55
|
+
}
|
|
56
|
+
withSymbol(symbol) {
|
|
57
|
+
return this.copyWith({ symbol });
|
|
58
|
+
}
|
|
59
|
+
equals(other) {
|
|
60
|
+
return other === this;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
class IgnoredRule extends Rule {
|
|
64
|
+
constructor(rule, state) {
|
|
65
|
+
super();
|
|
66
|
+
Object.defineProperty(this, "rule", {
|
|
67
|
+
enumerable: true,
|
|
68
|
+
configurable: true,
|
|
69
|
+
writable: true,
|
|
70
|
+
value: rule
|
|
71
|
+
});
|
|
72
|
+
Object.defineProperty(this, "state", {
|
|
73
|
+
enumerable: true,
|
|
74
|
+
configurable: true,
|
|
75
|
+
writable: true,
|
|
76
|
+
value: state
|
|
77
|
+
});
|
|
78
|
+
this.rule = rule;
|
|
79
|
+
this.state = state;
|
|
80
|
+
}
|
|
81
|
+
get searchVariants() {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
get id() {
|
|
85
|
+
return `ignored_${this.rule.id}`;
|
|
86
|
+
}
|
|
87
|
+
get explanation() {
|
|
88
|
+
return this.rule.explanation;
|
|
89
|
+
}
|
|
90
|
+
get configs() {
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
createExampleGrid() {
|
|
94
|
+
return this.rule.createExampleGrid();
|
|
95
|
+
}
|
|
96
|
+
get necessaryForCompletion() {
|
|
97
|
+
return this.rule.necessaryForCompletion;
|
|
98
|
+
}
|
|
99
|
+
get visibleWhenSolving() {
|
|
100
|
+
return this.rule.visibleWhenSolving;
|
|
101
|
+
}
|
|
102
|
+
get isSingleton() {
|
|
103
|
+
return this.rule.isSingleton;
|
|
104
|
+
}
|
|
105
|
+
validateGrid(_grid) {
|
|
106
|
+
if (this.state === State.Error)
|
|
107
|
+
return { state: State.Error, positions: [] };
|
|
108
|
+
else
|
|
109
|
+
return { state: this.state };
|
|
110
|
+
}
|
|
111
|
+
copyWith({ rule, state }) {
|
|
112
|
+
return new IgnoredRule(rule ?? this.rule, state ?? this.state);
|
|
113
|
+
}
|
|
114
|
+
equals(other) {
|
|
115
|
+
return other === this;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
class LyingSymbolRule extends Rule {
|
|
119
|
+
/**
|
|
120
|
+
* **<count> symbols are lying and are incorrect**
|
|
121
|
+
*
|
|
122
|
+
* @param count Number of lying symbols
|
|
123
|
+
*/
|
|
124
|
+
constructor(count) {
|
|
125
|
+
super();
|
|
126
|
+
Object.defineProperty(this, "count", {
|
|
127
|
+
enumerable: true,
|
|
128
|
+
configurable: true,
|
|
129
|
+
writable: true,
|
|
130
|
+
value: count
|
|
131
|
+
});
|
|
132
|
+
this.count = count;
|
|
133
|
+
}
|
|
134
|
+
get id() {
|
|
135
|
+
return `lying_symbols`;
|
|
136
|
+
}
|
|
137
|
+
get explanation() {
|
|
138
|
+
return `${this.count} symbol${this.count <= 1 ? ' is' : 's are'} *lying* and ${this.count <= 1 ? 'is' : 'are'} incorrect`;
|
|
139
|
+
}
|
|
140
|
+
get configs() {
|
|
141
|
+
return LyingSymbolRule.CONFIGS;
|
|
142
|
+
}
|
|
143
|
+
createExampleGrid() {
|
|
144
|
+
return LyingSymbolRule.EXAMPLE_GRID;
|
|
145
|
+
}
|
|
146
|
+
get searchVariants() {
|
|
147
|
+
return LyingSymbolRule.SEARCH_VARIANTS;
|
|
148
|
+
}
|
|
149
|
+
validateGrid(_) {
|
|
150
|
+
return { state: State.Incomplete };
|
|
151
|
+
}
|
|
152
|
+
get isSingleton() {
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
onFinalValidation(grid, solution, state) {
|
|
156
|
+
const ignoredSymbols = [];
|
|
157
|
+
state.symbols.forEach((values, key) => {
|
|
158
|
+
values.forEach((state, idx) => {
|
|
159
|
+
if (state === State.Error)
|
|
160
|
+
ignoredSymbols.push([key, idx]);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
if (ignoredSymbols.length > this.count) {
|
|
164
|
+
const thisIdx = grid.rules.findIndex(rule => rule.id === this.id);
|
|
165
|
+
return {
|
|
166
|
+
final: State.Error,
|
|
167
|
+
rules: state.rules.map((rule, idx) => idx === thisIdx ? { state: State.Error, positions: [] } : rule),
|
|
168
|
+
symbols: state.symbols,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
const newSymbols = new Map();
|
|
172
|
+
grid.symbols.forEach((values, key) => {
|
|
173
|
+
values.forEach((symbol, idx) => {
|
|
174
|
+
if (!newSymbols.has(key)) {
|
|
175
|
+
newSymbols.set(key, []);
|
|
176
|
+
}
|
|
177
|
+
if (ignoredSymbols.some(([k, i]) => k === key && i === idx)) {
|
|
178
|
+
newSymbols.get(key).push(new IgnoredSymbol(symbol, State.Ignored));
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
newSymbols.get(key).push(symbol);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
const newRules = grid.rules.map(rule => rule.id === this.id
|
|
186
|
+
? new IgnoredRule(this, ignoredSymbols.length === this.count
|
|
187
|
+
? State.Satisfied
|
|
188
|
+
: grid.getTileCount(true, false, Color.Gray) === 0
|
|
189
|
+
? State.Error
|
|
190
|
+
: State.Incomplete)
|
|
191
|
+
: rule);
|
|
192
|
+
return validateGrid(grid.copyWith({ rules: newRules, symbols: newSymbols }), solution);
|
|
193
|
+
}
|
|
194
|
+
copyWith({ count }) {
|
|
195
|
+
return new LyingSymbolRule(count ?? this.count);
|
|
196
|
+
}
|
|
197
|
+
withCount(count) {
|
|
198
|
+
return this.copyWith({ count });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
Object.defineProperty(LyingSymbolRule, "EXAMPLE_GRID", {
|
|
202
|
+
enumerable: true,
|
|
203
|
+
configurable: true,
|
|
204
|
+
writable: true,
|
|
205
|
+
value: Object.freeze(GridData.create(['bbbbw', 'wwbbb', 'bbbbw', 'wbbww']).withSymbols([
|
|
206
|
+
new LetterSymbol(4, 0, 'A'),
|
|
207
|
+
new GalaxySymbol(1, 1),
|
|
208
|
+
new LotusSymbol(2, 2, Orientation.Up),
|
|
209
|
+
new LetterSymbol(0, 3, 'A'),
|
|
210
|
+
new AreaNumberSymbol(4, 3, 1),
|
|
211
|
+
]))
|
|
212
|
+
});
|
|
213
|
+
Object.defineProperty(LyingSymbolRule, "CONFIGS", {
|
|
214
|
+
enumerable: true,
|
|
215
|
+
configurable: true,
|
|
216
|
+
writable: true,
|
|
217
|
+
value: Object.freeze([
|
|
218
|
+
{
|
|
219
|
+
type: ConfigType.Number,
|
|
220
|
+
default: 1,
|
|
221
|
+
min: 1,
|
|
222
|
+
max: 100,
|
|
223
|
+
step: 1,
|
|
224
|
+
field: 'count',
|
|
225
|
+
description: 'Number of liars',
|
|
226
|
+
configurable: true,
|
|
227
|
+
},
|
|
228
|
+
])
|
|
229
|
+
});
|
|
230
|
+
Object.defineProperty(LyingSymbolRule, "SEARCH_VARIANTS", {
|
|
231
|
+
enumerable: true,
|
|
232
|
+
configurable: true,
|
|
233
|
+
writable: true,
|
|
234
|
+
value: [
|
|
235
|
+
new LyingSymbolRule(1).searchVariant(),
|
|
236
|
+
]
|
|
237
|
+
});
|
|
238
|
+
export default LyingSymbolRule;
|
|
239
|
+
export const instance = new LyingSymbolRule(1);
|
|
@@ -200,7 +200,7 @@ Object.defineProperty(MusicGridRule, "CONFIGS", {
|
|
|
200
200
|
{
|
|
201
201
|
type: ConfigType.NullableGrid,
|
|
202
202
|
default: null,
|
|
203
|
-
nonNullDefault:
|
|
203
|
+
nonNullDefault: GridData.create(5, 4).addRule(new MusicGridRule([new ControlLine(0, 120, false, false, DEFAULT_SCALLE)], null)),
|
|
204
204
|
field: 'track',
|
|
205
205
|
description: 'Track',
|
|
206
206
|
configurable: true,
|
|
@@ -52,7 +52,7 @@ class MysteryRule extends Rule {
|
|
|
52
52
|
return false;
|
|
53
53
|
}
|
|
54
54
|
onFinalValidation(grid, _solution, state) {
|
|
55
|
-
if (state.final
|
|
55
|
+
if (State.isSatisfied(state.final))
|
|
56
56
|
return state;
|
|
57
57
|
if (grid.colorEquals(this.solution))
|
|
58
58
|
return {
|
|
@@ -121,7 +121,7 @@ class MysteryRule extends Rule {
|
|
|
121
121
|
return tile.withColor(solutionTile.color);
|
|
122
122
|
})
|
|
123
123
|
: solution.tiles;
|
|
124
|
-
return
|
|
124
|
+
return GridData.create(baseGrid?.width ?? solution.width, baseGrid?.height ?? solution.height, tiles);
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
Object.defineProperty(MysteryRule, "EXAMPLE_GRID", {
|
|
@@ -20,7 +20,7 @@ export default class OffByXRule extends Rule implements SymbolValidationHandler
|
|
|
20
20
|
get configs(): readonly AnyConfig[] | null;
|
|
21
21
|
createExampleGrid(): GridData;
|
|
22
22
|
get searchVariants(): SearchVariant[];
|
|
23
|
-
validateGrid(
|
|
23
|
+
validateGrid(grid: GridData): RuleState;
|
|
24
24
|
onSymbolValidation(grid: GridData, symbol: Symbol, _validator: (grid: GridData) => State): State | undefined;
|
|
25
25
|
get isSingleton(): boolean;
|
|
26
26
|
copyWith({ number }: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ConfigType } from '../config.js';
|
|
2
2
|
import GridData from '../grid.js';
|
|
3
|
-
import { State } from '../primitives.js';
|
|
3
|
+
import { Color, State } from '../primitives.js';
|
|
4
4
|
import AreaNumberSymbol from '../symbols/areaNumberSymbol.js';
|
|
5
5
|
import NumberSymbol from '../symbols/numberSymbol.js';
|
|
6
6
|
import Rule from './rule.js';
|
|
@@ -38,8 +38,12 @@ class OffByXRule extends Rule {
|
|
|
38
38
|
get searchVariants() {
|
|
39
39
|
return OffByXRule.SEARCH_VARIANTS;
|
|
40
40
|
}
|
|
41
|
-
validateGrid(
|
|
42
|
-
return {
|
|
41
|
+
validateGrid(grid) {
|
|
42
|
+
return {
|
|
43
|
+
state: grid.getTileCount(true, false, Color.Gray) === 0
|
|
44
|
+
? State.Satisfied
|
|
45
|
+
: State.Incomplete,
|
|
46
|
+
};
|
|
43
47
|
}
|
|
44
48
|
onSymbolValidation(grid, symbol, _validator) {
|
|
45
49
|
if (symbol instanceof NumberSymbol) {
|
|
@@ -62,7 +62,7 @@ class RegionAreaRule extends Rule {
|
|
|
62
62
|
? Color.Dark
|
|
63
63
|
: Color.Light);
|
|
64
64
|
});
|
|
65
|
-
return
|
|
65
|
+
return GridData.create(5, 4, tiles);
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
get searchVariants() {
|
|
@@ -157,13 +157,13 @@ Object.defineProperty(RegionAreaRule, "EXAMPLE_GRID_LIGHT", {
|
|
|
157
157
|
enumerable: true,
|
|
158
158
|
configurable: true,
|
|
159
159
|
writable: true,
|
|
160
|
-
value: Object.freeze(RegionAreaRule.EXAMPLE_GRID_DARK.map(grid =>
|
|
160
|
+
value: Object.freeze(RegionAreaRule.EXAMPLE_GRID_DARK.map(grid => GridData.create(grid.width, grid.height, grid.tiles.map(row => row.map(tile => tile.withColor(tile.color === Color.Dark ? Color.Light : Color.Dark))))))
|
|
161
161
|
});
|
|
162
162
|
Object.defineProperty(RegionAreaRule, "EXAMPLE_GRID_GRAY", {
|
|
163
163
|
enumerable: true,
|
|
164
164
|
configurable: true,
|
|
165
165
|
writable: true,
|
|
166
|
-
value: Object.freeze(RegionAreaRule.EXAMPLE_GRID_DARK.map(grid =>
|
|
166
|
+
value: Object.freeze(RegionAreaRule.EXAMPLE_GRID_DARK.map(grid => GridData.create(grid.width, grid.height, grid.tiles.map((row, y) => row.map((tile, x) => tile.withColor(tile.color === Color.Dark
|
|
167
167
|
? Color.Gray
|
|
168
168
|
: x % 2 !== y % 2
|
|
169
169
|
? Color.Dark
|
|
@@ -5,6 +5,7 @@ export { instance as CompletePatternRule } from './completePatternRule.js';
|
|
|
5
5
|
export { instance as ConnectAllRule } from './connectAllRule.js';
|
|
6
6
|
export { instance as CustomRule } from './customRule.js';
|
|
7
7
|
export { instance as ForesightRule } from './foresightRule.js';
|
|
8
|
+
export { instance as LyingSymbolRule } from './lyingSymbolRule.js';
|
|
8
9
|
export { instance as MusicGridRule } from './musicGridRule.js';
|
|
9
10
|
export { instance as MysteryRule } from './mysteryRule.js';
|
|
10
11
|
export { instance as OffByXRule } from './offByXRule.js';
|
|
@@ -9,6 +9,7 @@ export { instance as CompletePatternRule } from './completePatternRule.js';
|
|
|
9
9
|
export { instance as ConnectAllRule } from './connectAllRule.js';
|
|
10
10
|
export { instance as CustomRule } from './customRule.js';
|
|
11
11
|
export { instance as ForesightRule } from './foresightRule.js';
|
|
12
|
+
export { instance as LyingSymbolRule } from './lyingSymbolRule.js';
|
|
12
13
|
export { instance as MusicGridRule } from './musicGridRule.js';
|
|
13
14
|
export { instance as MysteryRule } from './mysteryRule.js';
|
|
14
15
|
export { instance as OffByXRule } from './offByXRule.js';
|
|
@@ -422,7 +422,7 @@ export default class SerializerV0 extends SerializerBase {
|
|
|
422
422
|
throw new Error(`Invalid data: ${d}`);
|
|
423
423
|
}
|
|
424
424
|
}
|
|
425
|
-
return
|
|
425
|
+
return GridData.create(width ?? tiles?.[0].length ?? 0, height ?? tiles?.length ?? 0, tiles, connections, zones, symbols, rules);
|
|
426
426
|
}
|
|
427
427
|
stringifyPuzzle(puzzle) {
|
|
428
428
|
let grid = puzzle.grid;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import UniversalSolver from './universal/universalSolver.js';
|
|
2
2
|
import BacktrackSolver from './backtrack/backtrackSolver.js';
|
|
3
3
|
import Z3Solver from './z3/z3Solver.js';
|
|
4
4
|
const allSolvers = new Map();
|
|
@@ -6,6 +6,6 @@ function register(prototype) {
|
|
|
6
6
|
allSolvers.set(prototype.id, prototype);
|
|
7
7
|
}
|
|
8
8
|
register(new BacktrackSolver());
|
|
9
|
-
register(new
|
|
9
|
+
register(new UniversalSolver());
|
|
10
10
|
register(new Z3Solver());
|
|
11
11
|
export { allSolvers };
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
export default class BacktrackSolver extends Solver {
|
|
1
|
+
import EventIteratingSolver from '../eventIteratingSolver.js';
|
|
2
|
+
export default class BacktrackSolver extends EventIteratingSolver {
|
|
4
3
|
private static readonly supportedInstrs;
|
|
5
4
|
readonly id = "backtrack";
|
|
6
5
|
readonly description = "Solves puzzles using backtracking with optimizations (blazingly fast). Support most rules and symbols (including underclued).";
|
|
7
|
-
|
|
6
|
+
protected createWorker(): Worker;
|
|
8
7
|
isInstructionSupported(instructionId: string): boolean;
|
|
9
8
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { EventIterator } from 'event-iterator';
|
|
2
1
|
import { instance as banPatternInstance } from '../../rules/banPatternRule.js';
|
|
3
2
|
import { instance as cellCountInstance } from '../../rules/cellCountRule.js';
|
|
4
3
|
import { instance as regionAreaInstance } from '../../rules/regionAreaRule.js';
|
|
@@ -6,7 +5,6 @@ import { instance as sameShapeInstance } from '../../rules/sameShapeRule.js';
|
|
|
6
5
|
import { instance as symbolsPerRegionInstance } from '../../rules/symbolsPerRegionRule.js';
|
|
7
6
|
import { instance as undercluedInstance } from '../../rules/undercluedRule.js';
|
|
8
7
|
import { instance as uniqueShapeInstance } from '../../rules/uniqueShapeRule.js';
|
|
9
|
-
import { Serializer } from '../../serializer/allSerializers.js';
|
|
10
8
|
import { instance as areaNumberInstance } from '../../symbols/areaNumberSymbol.js';
|
|
11
9
|
import { instance as dartInstance } from '../../symbols/dartSymbol.js';
|
|
12
10
|
import { instance as galaxyInstance } from '../../symbols/galaxySymbol.js';
|
|
@@ -15,9 +13,9 @@ import { instance as lotusInstance } from '../../symbols/lotusSymbol.js';
|
|
|
15
13
|
import { instance as minesweeperInstance } from '../../symbols/minesweeperSymbol.js';
|
|
16
14
|
import { instance as myopiaInstance } from '../../symbols/myopiaSymbol.js';
|
|
17
15
|
import { instance as viewpointInstance } from '../../symbols/viewpointSymbol.js';
|
|
18
|
-
import Solver from '../solver.js';
|
|
19
16
|
import { instance as connectAllInstance } from '../z3/modules/connectAllModule.js';
|
|
20
|
-
|
|
17
|
+
import EventIteratingSolver from '../eventIteratingSolver.js';
|
|
18
|
+
class BacktrackSolver extends EventIteratingSolver {
|
|
21
19
|
constructor() {
|
|
22
20
|
super(...arguments);
|
|
23
21
|
Object.defineProperty(this, "id", {
|
|
@@ -33,34 +31,10 @@ class BacktrackSolver extends Solver {
|
|
|
33
31
|
value: 'Solves puzzles using backtracking with optimizations (blazingly fast). Support most rules and symbols (including underclued).'
|
|
34
32
|
});
|
|
35
33
|
}
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
createWorker() {
|
|
35
|
+
return new Worker(new URL(`./backtrackWorker.js`, import.meta.url), {
|
|
38
36
|
type: 'module',
|
|
39
37
|
});
|
|
40
|
-
try {
|
|
41
|
-
const iterator = new EventIterator(({ push, stop, fail }) => {
|
|
42
|
-
worker.postMessage(Serializer.stringifyGrid(grid.resetTiles()));
|
|
43
|
-
worker.addEventListener('message', (e) => {
|
|
44
|
-
if (e.data) {
|
|
45
|
-
push(Serializer.parseGrid(e.data));
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
stop();
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
worker.addEventListener('error', (e) => {
|
|
52
|
-
alert(`Error while solving!\n${e.message}`);
|
|
53
|
-
fail(e);
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
for await (const solution of iterator) {
|
|
57
|
-
yield solution;
|
|
58
|
-
}
|
|
59
|
-
yield null;
|
|
60
|
-
}
|
|
61
|
-
finally {
|
|
62
|
-
worker.terminate();
|
|
63
|
-
}
|
|
64
38
|
}
|
|
65
39
|
isInstructionSupported(instructionId) {
|
|
66
40
|
return BacktrackSolver.supportedInstrs.includes(instructionId);
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export default _default;
|
|
1
|
+
export {};
|
|
@@ -74,9 +74,10 @@ function translateToBTGridData(grid) {
|
|
|
74
74
|
else if (id === letterInstance.id) {
|
|
75
75
|
continue;
|
|
76
76
|
}
|
|
77
|
-
if (!module)
|
|
77
|
+
if (!module && symbol.necessaryForCompletion)
|
|
78
78
|
throw new Error('Symbol not supported.');
|
|
79
|
-
|
|
79
|
+
if (module)
|
|
80
|
+
modules.push(module);
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
83
|
const letterSymbols = grid.symbols.get(letterInstance.id);
|
|
@@ -226,7 +227,9 @@ function solveUnderclued(input) {
|
|
|
226
227
|
function search(x, y, tile, color) {
|
|
227
228
|
// count++;
|
|
228
229
|
// console.log(`Trying (${x}, ${y}) with ${color}`);
|
|
229
|
-
const newGrid = grid.
|
|
230
|
+
const newGrid = grid.fastCopyWith({
|
|
231
|
+
tiles: grid.setTile(x, y, tile.withColor(color)),
|
|
232
|
+
});
|
|
230
233
|
// Solve
|
|
231
234
|
let solution;
|
|
232
235
|
solveNormal(newGrid, sol => {
|
|
@@ -258,9 +261,13 @@ function solveUnderclued(input) {
|
|
|
258
261
|
if (!darkPossible && !lightPossible)
|
|
259
262
|
return null;
|
|
260
263
|
if (darkPossible && !lightPossible)
|
|
261
|
-
grid = grid.
|
|
264
|
+
grid = grid.fastCopyWith({
|
|
265
|
+
tiles: grid.setTile(x, y, tile.withColor(Color.Dark)),
|
|
266
|
+
});
|
|
262
267
|
if (!darkPossible && lightPossible)
|
|
263
|
-
grid = grid.
|
|
268
|
+
grid = grid.fastCopyWith({
|
|
269
|
+
tiles: grid.setTile(x, y, tile.withColor(Color.Light)),
|
|
270
|
+
});
|
|
264
271
|
}
|
|
265
272
|
}
|
|
266
273
|
// console.log(`Solve count: ${count}`);
|
|
@@ -289,6 +296,3 @@ onmessage = e => {
|
|
|
289
296
|
// console.timeEnd('Solve time');
|
|
290
297
|
postMessage(null);
|
|
291
298
|
};
|
|
292
|
-
// make typescript happy
|
|
293
|
-
// eslint-disable-next-line import/no-anonymous-default-export
|
|
294
|
-
export default null;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import GridData from '../grid.js';
|
|
2
|
+
import Solver, { CancelRef } from './solver.js';
|
|
3
|
+
export default abstract class EventIteratingSolver extends Solver {
|
|
4
|
+
protected abstract createWorker(): Worker;
|
|
5
|
+
solve(grid: GridData, cancelRef: CancelRef): AsyncGenerator<GridData | null>;
|
|
6
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Serializer } from '../serializer/allSerializers.js';
|
|
2
|
+
import Solver from './solver.js';
|
|
3
|
+
import { EventIterator } from 'event-iterator';
|
|
4
|
+
export default class EventIteratingSolver extends Solver {
|
|
5
|
+
async *solve(grid, cancelRef) {
|
|
6
|
+
const worker = this.createWorker();
|
|
7
|
+
cancelRef.cancel = () => worker.terminate();
|
|
8
|
+
try {
|
|
9
|
+
const iterator = new EventIterator(({ push, stop, fail }) => {
|
|
10
|
+
worker.postMessage(Serializer.stringifyGrid(grid.resetTiles()));
|
|
11
|
+
worker.addEventListener('message', (e) => {
|
|
12
|
+
if (e.data) {
|
|
13
|
+
push(Serializer.parseGrid(e.data));
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
stop();
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
worker.addEventListener('error', (e) => {
|
|
20
|
+
alert(`Error while solving!\n${e.message}`);
|
|
21
|
+
fail(e);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
for await (const solution of iterator) {
|
|
25
|
+
yield solution;
|
|
26
|
+
}
|
|
27
|
+
yield null;
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
worker.terminate();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import GridData from '../grid.js';
|
|
2
|
+
export interface CancelRef {
|
|
3
|
+
cancel?: () => void;
|
|
4
|
+
}
|
|
2
5
|
/**
|
|
3
6
|
* Base class that all solvers must extend.
|
|
4
7
|
*/
|
|
@@ -28,8 +31,10 @@ export default abstract class Solver {
|
|
|
28
31
|
*
|
|
29
32
|
* @param grid The grid to solve. The provided grid is guaranteed to be supported by the solver. Some tiles in the
|
|
30
33
|
* grid may already be filled by the user. It is up to the solver to decide whether to respect these tiles or not.
|
|
34
|
+
* @param cancelRef A reference to a function that can be called to cancel the solver. If cancellation is supported,
|
|
35
|
+
* the solver can assign a function to `cancelRef.cancel` that will stop the solver when called.
|
|
31
36
|
*/
|
|
32
|
-
abstract solve(grid: GridData): AsyncGenerator<GridData | null>;
|
|
37
|
+
abstract solve(grid: GridData, cancelRef: CancelRef): AsyncGenerator<GridData | null>;
|
|
33
38
|
/**
|
|
34
39
|
* Check if the solver supports the current browser environment. This method is called once when the user first clicks
|
|
35
40
|
* the "Solve" button, and the result is cached for the duration of the editor session.
|
|
@@ -47,7 +47,8 @@ export default class Solver {
|
|
|
47
47
|
if (grid.rules.some(rule => rule.necessaryForCompletion && !this.isInstructionSupported(rule.id))) {
|
|
48
48
|
return false;
|
|
49
49
|
}
|
|
50
|
-
if ([...grid.symbols.keys()].some(id =>
|
|
50
|
+
if ([...grid.symbols.keys()].some(id => grid.symbols.get(id)?.some(s => s.necessaryForCompletion) &&
|
|
51
|
+
!this.isInstructionSupported(id))) {
|
|
51
52
|
return false;
|
|
52
53
|
}
|
|
53
54
|
return true;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import EventIteratingSolver from '../eventIteratingSolver.js';
|
|
2
|
+
export default class UniversalSolver extends EventIteratingSolver {
|
|
3
|
+
readonly id = "universal";
|
|
4
|
+
readonly description = "A backtracking solver that supports all rules and symbols (including underclued) but is less optimized.";
|
|
5
|
+
protected createWorker(): Worker;
|
|
6
|
+
isInstructionSupported(instructionId: string): boolean;
|
|
7
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { instance as undercluedInstance } from '../../rules/undercluedRule.js';
|
|
2
|
+
import EventIteratingSolver from '../eventIteratingSolver.js';
|
|
3
|
+
export default class UniversalSolver extends EventIteratingSolver {
|
|
4
|
+
constructor() {
|
|
5
|
+
super(...arguments);
|
|
6
|
+
Object.defineProperty(this, "id", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
configurable: true,
|
|
9
|
+
writable: true,
|
|
10
|
+
value: 'universal'
|
|
11
|
+
});
|
|
12
|
+
Object.defineProperty(this, "description", {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
configurable: true,
|
|
15
|
+
writable: true,
|
|
16
|
+
value: 'A backtracking solver that supports all rules and symbols (including underclued) but is less optimized.'
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
createWorker() {
|
|
20
|
+
return new Worker(new URL('./universalWorker.js', import.meta.url), {
|
|
21
|
+
type: 'module',
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
isInstructionSupported(instructionId) {
|
|
25
|
+
if (super.isInstructionSupported(instructionId)) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
return instructionId === undercluedInstance.id;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|