@logic-pad/core 0.7.0 → 0.10.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 (40) hide show
  1. package/assets/logic-core.global.d.ts +72 -13
  2. package/dist/data/grid.js +1 -0
  3. package/dist/data/primitives.d.ts +2 -1
  4. package/dist/data/primitives.js +2 -0
  5. package/dist/data/rules/banPatternRule.js +8 -0
  6. package/dist/data/rules/musicGridRule.d.ts +1 -1
  7. package/dist/data/rules/musicGridRule.js +2 -7
  8. package/dist/data/rules/wrapAroundRule.d.ts +0 -2
  9. package/dist/data/rules/wrapAroundRule.js +124 -58
  10. package/dist/data/solver/allSolvers.js +2 -0
  11. package/dist/data/solver/backtrack/backtrackSolver.d.ts +2 -1
  12. package/dist/data/solver/backtrack/backtrackSolver.js +10 -2
  13. package/dist/data/solver/backtrack/backtrackWorker.js +11 -0
  14. package/dist/data/solver/backtrack/symbols/focus.d.ts +9 -0
  15. package/dist/data/solver/backtrack/symbols/focus.js +59 -0
  16. package/dist/data/solver/cspuz/cspuzSolver.d.ts +12 -0
  17. package/dist/data/solver/cspuz/cspuzSolver.js +113 -0
  18. package/dist/data/solver/cspuz/cspuzWorker.d.ts +1 -0
  19. package/dist/data/solver/cspuz/cspuzWorker.js +44 -0
  20. package/dist/data/solver/cspuz/jsonify.d.ts +3 -0
  21. package/dist/data/solver/cspuz/jsonify.js +211 -0
  22. package/dist/data/solver/eventIteratingSolver.d.ts +3 -2
  23. package/dist/data/solver/eventIteratingSolver.js +17 -3
  24. package/dist/data/solver/solver.d.ts +11 -6
  25. package/dist/data/solver/universal/universalSolver.d.ts +1 -0
  26. package/dist/data/solver/universal/universalSolver.js +6 -0
  27. package/dist/data/solver/universal/universalWorker.js +5 -0
  28. package/dist/data/solver/z3/z3Solver.d.ts +3 -1
  29. package/dist/data/solver/z3/z3Solver.js +13 -1
  30. package/dist/data/symbols/directionLinkerSymbol.js +22 -13
  31. package/dist/data/symbols/focusSymbol.d.ts +30 -0
  32. package/dist/data/symbols/focusSymbol.js +110 -0
  33. package/dist/data/symbols/minesweeperSymbol.d.ts +1 -1
  34. package/dist/data/symbols/minesweeperSymbol.js +9 -2
  35. package/dist/data/symbols/symbols.gen.d.ts +1 -0
  36. package/dist/data/symbols/symbols.gen.js +1 -0
  37. package/dist/data/symbols/viewpointSymbol.js +9 -11
  38. package/dist/index.d.ts +5 -1
  39. package/dist/index.js +5 -1
  40. package/package.json +1 -1
@@ -0,0 +1,59 @@
1
+ import BTModule, { BTTile, IntArray2D, createOneTileResult, } from '../data.js';
2
+ export default class FocusBTModule extends BTModule {
3
+ constructor(instr) {
4
+ super();
5
+ Object.defineProperty(this, "instr", {
6
+ enumerable: true,
7
+ configurable: true,
8
+ writable: true,
9
+ value: void 0
10
+ });
11
+ Object.defineProperty(this, "cachedCheckResult", {
12
+ enumerable: true,
13
+ configurable: true,
14
+ writable: true,
15
+ value: void 0
16
+ });
17
+ this.instr = instr;
18
+ }
19
+ checkGlobal(grid) {
20
+ const tile = grid.getTile(this.instr.x, this.instr.y);
21
+ if (tile === BTTile.Empty)
22
+ return createOneTileResult(grid, { x: this.instr.x, y: this.instr.y });
23
+ let gray = 0;
24
+ let same = 0;
25
+ for (let y = this.instr.y - 1; y <= this.instr.y + 1; y++) {
26
+ for (let x = this.instr.x - 1; x <= this.instr.x + 1; x++) {
27
+ if (y !== this.instr.y && x !== this.instr.x)
28
+ continue;
29
+ if (!grid.isInBound(x, y) || (x === this.instr.x && y === this.instr.y))
30
+ continue;
31
+ const checkTile = grid.getTile(x, y);
32
+ if (checkTile === BTTile.Empty)
33
+ gray++;
34
+ else if (checkTile === tile)
35
+ same++;
36
+ }
37
+ }
38
+ if (same > this.instr.number || same + gray < this.instr.number)
39
+ return false;
40
+ if (!this.cachedCheckResult)
41
+ this.cachedCheckResult = this.buildCheckAndRating(grid);
42
+ return this.cachedCheckResult;
43
+ }
44
+ buildCheckAndRating(grid) {
45
+ const tilesNeedCheck = IntArray2D.create(grid.width, grid.height);
46
+ const ratings = [];
47
+ for (let y = this.instr.y - 1; y <= this.instr.y + 1; y++) {
48
+ for (let x = this.instr.x - 1; x <= this.instr.x + 1; x++) {
49
+ if (y !== this.instr.y && x !== this.instr.x)
50
+ continue;
51
+ if (!grid.isInBound(x, y) || (x === this.instr.x && y === this.instr.y))
52
+ continue;
53
+ tilesNeedCheck.set(x, y, 1);
54
+ ratings.push({ pos: { x, y }, score: 1 });
55
+ }
56
+ }
57
+ return { tilesNeedCheck, ratings };
58
+ }
59
+ }
@@ -0,0 +1,12 @@
1
+ import EventIteratingSolver from '../eventIteratingSolver.js';
2
+ import GridData from '../../grid.js';
3
+ export default class CspuzSolver extends EventIteratingSolver {
4
+ private static readonly supportedInstrs;
5
+ readonly id = "cspuz";
6
+ readonly author = "semiexp";
7
+ readonly description = "A blazingly fast WebAssembly solver that supports most rules and symbols (including underclued). No uniqueness check yet.";
8
+ protected createWorker(): Worker;
9
+ isGridSupported(grid: GridData): boolean;
10
+ isInstructionSupported(instructionId: string): boolean;
11
+ isEnvironmentSupported(): Promise<boolean>;
12
+ }
@@ -0,0 +1,113 @@
1
+ import { instance as banPatternInstance } from '../../rules/banPatternRule.js';
2
+ import { instance as cellCountInstance } from '../../rules/cellCountRule.js';
3
+ import { instance as regionAreaInstance } from '../../rules/regionAreaRule.js';
4
+ import { instance as sameShapeInstance } from '../../rules/sameShapeRule.js';
5
+ import { instance as symbolsPerRegionInstance } from '../../rules/symbolsPerRegionRule.js';
6
+ import { instance as undercluedInstance } from '../../rules/undercluedRule.js';
7
+ import { instance as uniqueShapeInstance } from '../../rules/uniqueShapeRule.js';
8
+ import { instance as offByXInstance } from '../../rules/offByXRule.js';
9
+ import { instance as areaNumberInstance } from '../../symbols/areaNumberSymbol.js';
10
+ import { instance as dartInstance } from '../../symbols/dartSymbol.js';
11
+ import GalaxySymbol, { instance as galaxyInstance, } from '../../symbols/galaxySymbol.js';
12
+ import { instance as letterInstance } from '../../symbols/letterSymbol.js';
13
+ import LotusSymbol, { instance as lotusInstance, } from '../../symbols/lotusSymbol.js';
14
+ import { instance as minesweeperInstance } from '../../symbols/minesweeperSymbol.js';
15
+ import { instance as viewpointInstance } from '../../symbols/viewpointSymbol.js';
16
+ import { instance as connectAllInstance } from '../../rules/connectAllRule.js';
17
+ import EventIteratingSolver from '../eventIteratingSolver.js';
18
+ import GridData from '../../grid.js';
19
+ import { Color } from '../../primitives.js';
20
+ class CspuzSolver extends EventIteratingSolver {
21
+ constructor() {
22
+ super(...arguments);
23
+ Object.defineProperty(this, "id", {
24
+ enumerable: true,
25
+ configurable: true,
26
+ writable: true,
27
+ value: 'cspuz'
28
+ });
29
+ Object.defineProperty(this, "author", {
30
+ enumerable: true,
31
+ configurable: true,
32
+ writable: true,
33
+ value: 'semiexp'
34
+ });
35
+ Object.defineProperty(this, "description", {
36
+ enumerable: true,
37
+ configurable: true,
38
+ writable: true,
39
+ value: 'A blazingly fast WebAssembly solver that supports most rules and symbols (including underclued). No uniqueness check yet.'
40
+ });
41
+ }
42
+ createWorker() {
43
+ return new Worker(new URL('./cspuzWorker.js', import.meta.url), {
44
+ type: 'module',
45
+ });
46
+ }
47
+ isGridSupported(grid) {
48
+ if (!super.isGridSupported(grid)) {
49
+ return false;
50
+ }
51
+ // special handling for galaxies and lotuses since dual-color symbols are not supported yet
52
+ for (const [_, symbols] of grid.symbols) {
53
+ for (const symbol of symbols) {
54
+ if (symbol instanceof GalaxySymbol || symbol instanceof LotusSymbol) {
55
+ if (symbol.x % 1 !== 0 && symbol.y % 1 !== 0) {
56
+ return false;
57
+ }
58
+ else if (symbol.x % 1 !== 0 || symbol.y % 1 !== 0) {
59
+ const tile1 = grid.getTile(Math.floor(symbol.x), Math.floor(symbol.y));
60
+ const tile2 = grid.getTile(Math.ceil(symbol.x), Math.ceil(symbol.y));
61
+ if (!tile1.fixed || !tile2.fixed || tile1.color !== tile2.color) {
62
+ return false;
63
+ }
64
+ }
65
+ }
66
+ }
67
+ }
68
+ // special handling for fixed gray tiles
69
+ if (grid.getTileCount(true, true, Color.Gray) > 0) {
70
+ return false;
71
+ }
72
+ return true;
73
+ }
74
+ isInstructionSupported(instructionId) {
75
+ return CspuzSolver.supportedInstrs.includes(instructionId);
76
+ }
77
+ async isEnvironmentSupported() {
78
+ try {
79
+ const abortController = new AbortController();
80
+ for await (const _ of this.solve(GridData.create(['.']), abortController.signal)) {
81
+ abortController.abort();
82
+ }
83
+ return true;
84
+ }
85
+ catch (ex) {
86
+ return false;
87
+ }
88
+ }
89
+ }
90
+ Object.defineProperty(CspuzSolver, "supportedInstrs", {
91
+ enumerable: true,
92
+ configurable: true,
93
+ writable: true,
94
+ value: [
95
+ minesweeperInstance.id,
96
+ areaNumberInstance.id,
97
+ letterInstance.id,
98
+ dartInstance.id,
99
+ viewpointInstance.id,
100
+ lotusInstance.id,
101
+ galaxyInstance.id,
102
+ connectAllInstance.id,
103
+ banPatternInstance.id,
104
+ sameShapeInstance.id,
105
+ uniqueShapeInstance.id,
106
+ regionAreaInstance.id,
107
+ cellCountInstance.id,
108
+ offByXInstance.id,
109
+ undercluedInstance.id,
110
+ symbolsPerRegionInstance.id,
111
+ ]
112
+ });
113
+ export default CspuzSolver;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,44 @@
1
+ import { solveLogicPad } from 'logic-pad-solver-core';
2
+ import { Serializer } from '../../serializer/allSerializers.js';
3
+ import { gridToJson } from './jsonify.js';
4
+ import { instance as undercluedInstance } from '../../rules/undercluedRule.js';
5
+ import { Color } from '../../primitives.js';
6
+ function stringToColor(str) {
7
+ if (str === 'dark') {
8
+ return Color.Dark;
9
+ }
10
+ else if (str === 'light') {
11
+ return Color.Light;
12
+ }
13
+ else {
14
+ return Color.Gray;
15
+ }
16
+ }
17
+ onmessage = e => {
18
+ const grid = Serializer.parseGrid(e.data);
19
+ const puzzleData = gridToJson(grid);
20
+ const solverResult = solveLogicPad(puzzleData, !!grid.findRule(r => r.id === undercluedInstance.id));
21
+ if (solverResult === null) {
22
+ postMessage(null);
23
+ }
24
+ else if ('error' in solverResult) {
25
+ throw new Error(solverResult.error);
26
+ }
27
+ else {
28
+ const solution = grid.withTiles(tiles => {
29
+ for (const [y, row] of solverResult.entries()) {
30
+ for (const [x, color] of row.entries()) {
31
+ tiles[y][x] = tiles[y][x].withColor(stringToColor(color));
32
+ }
33
+ }
34
+ return tiles;
35
+ });
36
+ if (solution.resetTiles().colorEquals(solution)) {
37
+ postMessage(null);
38
+ }
39
+ else {
40
+ postMessage(Serializer.stringifyGrid(solution));
41
+ }
42
+ }
43
+ postMessage(undefined);
44
+ };
@@ -0,0 +1,3 @@
1
+ import { PuzzleData } from 'logic-pad-solver-core';
2
+ import GridData from '../../grid.js';
3
+ export declare function gridToJson(grid: GridData): PuzzleData;
@@ -0,0 +1,211 @@
1
+ import { Color, Comparison } from '../../primitives.js';
2
+ import BanPatternRule from '../../rules/banPatternRule.js';
3
+ import CellCountRule from '../../rules/cellCountRule.js';
4
+ import ConnectAllRule from '../../rules/connectAllRule.js';
5
+ import OffByXRule from '../../rules/offByXRule.js';
6
+ import RegionAreaRule from '../../rules/regionAreaRule.js';
7
+ import SameShapeRule from '../../rules/sameShapeRule.js';
8
+ import SymbolsPerRegionRule from '../../rules/symbolsPerRegionRule.js';
9
+ import UndercluedRule from '../../rules/undercluedRule.js';
10
+ import UniqueShapeRule from '../../rules/uniqueShapeRule.js';
11
+ import { instance as areaNumberInstance, } from '../../symbols/areaNumberSymbol.js';
12
+ import { instance as dartInstance, } from '../../symbols/dartSymbol.js';
13
+ import { instance as galaxyInstance, } from '../../symbols/galaxySymbol.js';
14
+ import { instance as letterInstance, } from '../../symbols/letterSymbol.js';
15
+ import { instance as lotusInstance, } from '../../symbols/lotusSymbol.js';
16
+ import { instance as minesweeperInstance, } from '../../symbols/minesweeperSymbol.js';
17
+ import { instance as viewpointInstance, } from '../../symbols/viewpointSymbol.js';
18
+ import TileData from '../../tile.js';
19
+ function canonizeTiles(tileData) {
20
+ const ret = [];
21
+ for (const row of tileData) {
22
+ const newRow = [];
23
+ for (const tile of row) {
24
+ if (tile.exists) {
25
+ newRow.push(tile);
26
+ }
27
+ else {
28
+ newRow.push(new TileData(true, false, Color.Gray));
29
+ }
30
+ }
31
+ ret.push(newRow);
32
+ }
33
+ return ret;
34
+ }
35
+ export function gridToJson(grid) {
36
+ const rules = [];
37
+ for (const rule of grid.rules) {
38
+ if (rule instanceof ConnectAllRule) {
39
+ rules.push({
40
+ type: 'connectAll',
41
+ color: rule.color,
42
+ });
43
+ }
44
+ else if (rule instanceof BanPatternRule) {
45
+ rules.push({
46
+ type: 'forbiddenPattern',
47
+ pattern: canonizeTiles(rule.pattern.tiles),
48
+ });
49
+ }
50
+ else if (rule instanceof SameShapeRule) {
51
+ rules.push({
52
+ type: 'sameShape',
53
+ color: rule.color,
54
+ });
55
+ }
56
+ else if (rule instanceof UniqueShapeRule) {
57
+ rules.push({
58
+ type: 'uniqueShape',
59
+ color: rule.color,
60
+ });
61
+ }
62
+ else if (rule instanceof RegionAreaRule) {
63
+ rules.push({
64
+ type: 'regionArea',
65
+ color: rule.color,
66
+ size: rule.size,
67
+ });
68
+ }
69
+ else if (rule instanceof CellCountRule) {
70
+ rules.push({
71
+ type: 'cellCount',
72
+ color: rule.color,
73
+ count: rule.count,
74
+ });
75
+ }
76
+ else if (rule instanceof OffByXRule) {
77
+ rules.push({
78
+ type: 'offByX',
79
+ number: rule.number,
80
+ });
81
+ }
82
+ else if (rule instanceof UndercluedRule) {
83
+ continue;
84
+ }
85
+ else if (rule instanceof SymbolsPerRegionRule) {
86
+ let kind;
87
+ if (rule.comparison === Comparison.Equal) {
88
+ kind = 'exactly';
89
+ }
90
+ else if (rule.comparison === Comparison.AtLeast) {
91
+ kind = 'atLeast';
92
+ }
93
+ else if (rule.comparison === Comparison.AtMost) {
94
+ kind = 'atMost';
95
+ }
96
+ else {
97
+ throw new Error(`Unknown comparison (${rule.comparison})`);
98
+ }
99
+ rules.push({
100
+ type: 'symbolCount',
101
+ number: rule.count,
102
+ kind,
103
+ color: rule.color,
104
+ });
105
+ }
106
+ else if (rule.necessaryForCompletion) {
107
+ throw new Error(`Unknown rule type (${rule.explanation})`);
108
+ }
109
+ }
110
+ for (const [rule, symbols] of grid.symbols) {
111
+ if (rule === minesweeperInstance.id) {
112
+ rules.push({
113
+ type: 'minesweeper',
114
+ tiles: symbols.map(s => ({
115
+ x: Math.floor(s.x),
116
+ y: Math.floor(s.y),
117
+ number: s.number,
118
+ })),
119
+ });
120
+ }
121
+ else if (rule === areaNumberInstance.id) {
122
+ rules.push({
123
+ type: 'number',
124
+ tiles: symbols.map(s => ({
125
+ x: Math.floor(s.x),
126
+ y: Math.floor(s.y),
127
+ number: s.number,
128
+ })),
129
+ });
130
+ }
131
+ else if (rule === letterInstance.id) {
132
+ rules.push({
133
+ type: 'letter',
134
+ tiles: symbols.map(s => ({
135
+ x: Math.floor(s.x),
136
+ y: Math.floor(s.y),
137
+ letter: s.letter,
138
+ })),
139
+ });
140
+ }
141
+ else if (rule === dartInstance.id) {
142
+ rules.push({
143
+ type: 'dart',
144
+ tiles: symbols.map(s => ({
145
+ x: Math.floor(s.x),
146
+ y: Math.floor(s.y),
147
+ orientation: s.orientation,
148
+ number: s.number,
149
+ })),
150
+ });
151
+ }
152
+ else if (rule === viewpointInstance.id) {
153
+ rules.push({
154
+ type: 'viewpoint',
155
+ tiles: symbols.map(s => ({
156
+ x: Math.floor(s.x),
157
+ y: Math.floor(s.y),
158
+ number: s.number,
159
+ })),
160
+ });
161
+ }
162
+ else if (rule === lotusInstance.id) {
163
+ const tiles = symbols.map(s => ({
164
+ x: Math.round(s.x * 2),
165
+ y: Math.round(s.y * 2),
166
+ orientation: s.orientation,
167
+ }));
168
+ rules.push({
169
+ type: 'lotus',
170
+ tiles,
171
+ });
172
+ }
173
+ else if (rule === galaxyInstance.id) {
174
+ const tiles = symbols.map(s => ({
175
+ x: Math.round(s.x * 2),
176
+ y: Math.round(s.y * 2),
177
+ }));
178
+ rules.push({
179
+ type: 'galaxy',
180
+ tiles,
181
+ });
182
+ }
183
+ else if (symbols.some(s => s.necessaryForCompletion)) {
184
+ throw new Error(`Unknown symbol type: ${rule}`);
185
+ }
186
+ }
187
+ const connections = grid.connections.edges.map(edge => {
188
+ return {
189
+ x1: edge.x1,
190
+ y1: edge.y1,
191
+ x2: edge.x2,
192
+ y2: edge.y2,
193
+ };
194
+ });
195
+ const tiles = grid.tiles.map(row => {
196
+ return row.map(tile => {
197
+ return {
198
+ exists: tile.exists,
199
+ fixed: tile.fixed,
200
+ color: tile.color,
201
+ };
202
+ });
203
+ });
204
+ return {
205
+ width: grid.width,
206
+ height: grid.height,
207
+ connections,
208
+ tiles,
209
+ rules,
210
+ };
211
+ }
@@ -1,6 +1,7 @@
1
1
  import GridData from '../grid.js';
2
- import Solver, { CancelRef } from './solver.js';
2
+ import Solver from './solver.js';
3
3
  export default abstract class EventIteratingSolver extends Solver {
4
+ readonly supportsCancellation = true;
4
5
  protected abstract createWorker(): Worker;
5
- solve(grid: GridData, cancelRef: CancelRef): AsyncGenerator<GridData | null>;
6
+ solve(grid: GridData, abortSignal?: AbortSignal): AsyncGenerator<GridData | null>;
6
7
  }
@@ -2,9 +2,19 @@ import { Serializer } from '../serializer/allSerializers.js';
2
2
  import Solver from './solver.js';
3
3
  import { EventIterator } from 'event-iterator';
4
4
  export default class EventIteratingSolver extends Solver {
5
- async *solve(grid, cancelRef) {
5
+ constructor() {
6
+ super(...arguments);
7
+ Object.defineProperty(this, "supportsCancellation", {
8
+ enumerable: true,
9
+ configurable: true,
10
+ writable: true,
11
+ value: true
12
+ });
13
+ }
14
+ async *solve(grid, abortSignal) {
6
15
  const worker = this.createWorker();
7
- cancelRef.cancel = () => worker.terminate();
16
+ const terminateHandler = () => worker.terminate();
17
+ abortSignal?.addEventListener('abort', terminateHandler);
8
18
  try {
9
19
  const iterator = new EventIterator(({ push, stop, fail }) => {
10
20
  worker.postMessage(Serializer.stringifyGrid(grid.resetTiles()));
@@ -12,6 +22,10 @@ export default class EventIteratingSolver extends Solver {
12
22
  if (e.data) {
13
23
  push(Serializer.parseGrid(e.data));
14
24
  }
25
+ else if (e.data === null) {
26
+ push(null);
27
+ stop(); // Stop after the first signal for out of solutions
28
+ }
15
29
  else {
16
30
  stop();
17
31
  }
@@ -24,10 +38,10 @@ export default class EventIteratingSolver extends Solver {
24
38
  for await (const solution of iterator) {
25
39
  yield solution;
26
40
  }
27
- yield null;
28
41
  }
29
42
  finally {
30
43
  worker.terminate();
44
+ abortSignal?.removeEventListener('abort', terminateHandler);
31
45
  }
32
46
  }
33
47
  }
@@ -1,7 +1,4 @@
1
1
  import GridData from '../grid.js';
2
- export interface CancelRef {
3
- cancel?: () => void;
4
- }
5
2
  /**
6
3
  * Base class that all solvers must extend.
7
4
  */
@@ -12,10 +9,18 @@ export default abstract class Solver {
12
9
  * This is also displayed to the user when selecting a solver.
13
10
  */
14
11
  abstract get id(): string;
12
+ /**
13
+ * The author(s) of the solver.
14
+ */
15
+ abstract get author(): string;
15
16
  /**
16
17
  * A short paragraph describing when the user should use this solver.
17
18
  */
18
19
  abstract get description(): string;
20
+ /**
21
+ * Whether the solver supports cancellation. If `true`, the solver must respond to the abort signal if it is provided.
22
+ */
23
+ abstract get supportsCancellation(): boolean;
19
24
  /**
20
25
  * Solve the given grid. The implementation should delegate long-running tasks to a worker thread and yield solutions
21
26
  * asynchronously.
@@ -31,10 +36,10 @@ export default abstract class Solver {
31
36
  *
32
37
  * @param grid The grid to solve. The provided grid is guaranteed to be supported by the solver. Some tiles in the
33
38
  * 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.
39
+ * @param abortSignal An optional signal that the solver should subscribe to in order to cancel the operation. If the
40
+ * solver does not support cancellation, it should ignore this parameter.
36
41
  */
37
- abstract solve(grid: GridData, cancelRef: CancelRef): AsyncGenerator<GridData | null>;
42
+ abstract solve(grid: GridData, abortSignal?: AbortSignal): AsyncGenerator<GridData | null>;
38
43
  /**
39
44
  * Check if the solver supports the current browser environment. This method is called once when the user first clicks
40
45
  * the "Solve" button, and the result is cached for the duration of the editor session.
@@ -1,6 +1,7 @@
1
1
  import EventIteratingSolver from '../eventIteratingSolver.js';
2
2
  export default class UniversalSolver extends EventIteratingSolver {
3
3
  readonly id = "universal";
4
+ readonly author = "romain22222, Lysine";
4
5
  readonly description = "A backtracking solver that supports all rules and symbols (including underclued) but is less optimized.";
5
6
  protected createWorker(): Worker;
6
7
  isInstructionSupported(instructionId: string): boolean;
@@ -9,6 +9,12 @@ export default class UniversalSolver extends EventIteratingSolver {
9
9
  writable: true,
10
10
  value: 'universal'
11
11
  });
12
+ Object.defineProperty(this, "author", {
13
+ enumerable: true,
14
+ configurable: true,
15
+ writable: true,
16
+ value: 'romain22222, Lysine'
17
+ });
12
18
  Object.defineProperty(this, "description", {
13
19
  enumerable: true,
14
20
  configurable: true,
@@ -115,6 +115,11 @@ onmessage = e => {
115
115
  const grid = Serializer.parseGrid(e.data);
116
116
  let count = 0;
117
117
  solve(grid, solution => {
118
+ if (solution) {
119
+ if (solution.resetTiles().colorEquals(solution)) {
120
+ solution = null;
121
+ }
122
+ }
118
123
  postMessage(solution ? Serializer.stringifyGrid(solution) : null);
119
124
  count += 1;
120
125
  return count < 2;
@@ -2,7 +2,9 @@ import GridData from '../../grid.js';
2
2
  import Solver from '../solver.js';
3
3
  export default class Z3Solver extends Solver {
4
4
  readonly id = "z3";
5
- readonly description = "Good for confirming that a solution is unique, especially for larger puzzles. It is otherwise slower than most solvers in small to medium-sized puzzles.";
5
+ readonly author = "Lysine";
6
+ readonly description = "(Obsolete) A WebAssembly solver that supports a limited set of rules and symbols.";
7
+ readonly supportsCancellation = false;
6
8
  isEnvironmentSupported(): Promise<boolean>;
7
9
  solve(grid: GridData): AsyncGenerator<GridData | null>;
8
10
  isInstructionSupported(instructionId: string): boolean;
@@ -14,11 +14,23 @@ export default class Z3Solver extends Solver {
14
14
  writable: true,
15
15
  value: 'z3'
16
16
  });
17
+ Object.defineProperty(this, "author", {
18
+ enumerable: true,
19
+ configurable: true,
20
+ writable: true,
21
+ value: 'Lysine'
22
+ });
17
23
  Object.defineProperty(this, "description", {
18
24
  enumerable: true,
19
25
  configurable: true,
20
26
  writable: true,
21
- value: 'Good for confirming that a solution is unique, especially for larger puzzles. It is otherwise slower than most solvers in small to medium-sized puzzles.'
27
+ value: '(Obsolete) A WebAssembly solver that supports a limited set of rules and symbols.'
28
+ });
29
+ Object.defineProperty(this, "supportsCancellation", {
30
+ enumerable: true,
31
+ configurable: true,
32
+ writable: true,
33
+ value: false
22
34
  });
23
35
  }
24
36
  async isEnvironmentSupported() {