@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.
Files changed (41) hide show
  1. package/assets/logic-core.global.d.ts +134 -25
  2. package/dist/data/grid.d.ts +44 -14
  3. package/dist/data/grid.js +43 -36
  4. package/dist/data/primitives.d.ts +19 -1
  5. package/dist/data/primitives.js +20 -0
  6. package/dist/data/rules/banPatternRule.js +1 -1
  7. package/dist/data/rules/customRule.js +1 -1
  8. package/dist/data/rules/lyingSymbolRule.d.ts +30 -0
  9. package/dist/data/rules/lyingSymbolRule.js +239 -0
  10. package/dist/data/rules/musicGridRule.js +1 -1
  11. package/dist/data/rules/mysteryRule.js +2 -2
  12. package/dist/data/rules/offByXRule.d.ts +1 -1
  13. package/dist/data/rules/offByXRule.js +7 -3
  14. package/dist/data/rules/regionAreaRule.js +3 -3
  15. package/dist/data/rules/rules.gen.d.ts +1 -0
  16. package/dist/data/rules/rules.gen.js +1 -0
  17. package/dist/data/serializer/serializer_v0.js +1 -1
  18. package/dist/data/solver/allSolvers.js +2 -2
  19. package/dist/data/solver/backtrack/backtrackSolver.d.ts +3 -4
  20. package/dist/data/solver/backtrack/backtrackSolver.js +4 -30
  21. package/dist/data/solver/backtrack/backtrackWorker.d.ts +1 -2
  22. package/dist/data/solver/backtrack/backtrackWorker.js +12 -8
  23. package/dist/data/solver/eventIteratingSolver.d.ts +6 -0
  24. package/dist/data/solver/eventIteratingSolver.js +33 -0
  25. package/dist/data/solver/solver.d.ts +6 -1
  26. package/dist/data/solver/solver.js +2 -1
  27. package/dist/data/solver/universal/universalSolver.d.ts +7 -0
  28. package/dist/data/solver/universal/universalSolver.js +30 -0
  29. package/dist/data/solver/universal/universalWorker.d.ts +1 -0
  30. package/dist/data/solver/universal/universalWorker.js +119 -0
  31. package/dist/data/symbols/customIconSymbol.js +2 -2
  32. package/dist/data/symbols/customTextSymbol.js +2 -2
  33. package/dist/data/validate.d.ts +1 -1
  34. package/dist/data/validate.js +3 -3
  35. package/dist/index.d.ts +4 -2
  36. package/dist/index.js +4 -2
  37. package/package.json +1 -1
  38. package/dist/data/solver/underclued/undercluedSolver.d.ts +0 -8
  39. package/dist/data/solver/underclued/undercluedSolver.js +0 -55
  40. package/dist/data/solver/underclued/undercluedWorker.d.ts +0 -2
  41. 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: new GridData(5, 4).addRule(new MusicGridRule([new ControlLine(0, 120, false, false, DEFAULT_SCALLE)], null)),
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 === State.Satisfied)
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 new GridData(baseGrid?.width ?? solution.width, baseGrid?.height ?? solution.height, tiles);
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(_grid: GridData): RuleState;
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(_grid) {
42
- return { state: State.Incomplete };
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 new GridData(5, 4, tiles);
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 => new GridData(grid.width, grid.height, grid.tiles.map(row => row.map(tile => tile.withColor(tile.color === Color.Dark ? Color.Light : Color.Dark))))))
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 => new GridData(grid.width, grid.height, grid.tiles.map((row, y) => row.map((tile, x) => tile.withColor(tile.color === Color.Dark
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 new GridData(width ?? tiles?.[0].length ?? 0, height ?? tiles?.length ?? 0, tiles, connections, zones, symbols, rules);
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 UndercluedSolver from './underclued/undercluedSolver.js';
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 UndercluedSolver());
9
+ register(new UniversalSolver());
10
10
  register(new Z3Solver());
11
11
  export { allSolvers };
@@ -1,9 +1,8 @@
1
- import GridData from '../../grid.js';
2
- import Solver from '../solver.js';
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
- solve(grid: GridData): AsyncGenerator<GridData | null>;
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
- class BacktrackSolver extends Solver {
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
- async *solve(grid) {
37
- const worker = new Worker(new URL(`./backtrackWorker.js`, import.meta.url), {
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
- declare const _default: null;
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
- modules.push(module);
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.setTile(x, y, tile.withColor(color));
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.setTile(x, y, tile.withColor(Color.Dark));
264
+ grid = grid.fastCopyWith({
265
+ tiles: grid.setTile(x, y, tile.withColor(Color.Dark)),
266
+ });
262
267
  if (!darkPossible && lightPossible)
263
- grid = grid.setTile(x, y, tile.withColor(Color.Light));
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 => !this.isInstructionSupported(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 {};