@logic-pad/core 0.6.1 → 0.9.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 -11
  2. package/dist/data/config.d.ts +6 -2
  3. package/dist/data/config.js +1 -0
  4. package/dist/data/events/onGetTile.d.ts +7 -0
  5. package/dist/data/events/onGetTile.js +4 -0
  6. package/dist/data/grid.d.ts +9 -3
  7. package/dist/data/grid.js +50 -10
  8. package/dist/data/primitives.d.ts +9 -1
  9. package/dist/data/primitives.js +14 -0
  10. package/dist/data/rules/banPatternRule.js +11 -6
  11. package/dist/data/rules/cellCountPerZoneRule.js +9 -7
  12. package/dist/data/rules/regionShapeRule.js +7 -3
  13. package/dist/data/rules/rules.gen.d.ts +1 -0
  14. package/dist/data/rules/rules.gen.js +1 -0
  15. package/dist/data/rules/wrapAroundRule.d.ts +34 -0
  16. package/dist/data/rules/wrapAroundRule.js +271 -0
  17. package/dist/data/serializer/serializer_v0.js +3 -0
  18. package/dist/data/solver/backtrack/backtrackSolver.d.ts +1 -0
  19. package/dist/data/solver/backtrack/backtrackSolver.js +8 -0
  20. package/dist/data/solver/backtrack/backtrackWorker.js +11 -0
  21. package/dist/data/solver/backtrack/symbols/focus.d.ts +9 -0
  22. package/dist/data/solver/backtrack/symbols/focus.js +59 -0
  23. package/dist/data/solver/eventIteratingSolver.d.ts +3 -2
  24. package/dist/data/solver/eventIteratingSolver.js +13 -2
  25. package/dist/data/solver/solver.d.ts +11 -6
  26. package/dist/data/solver/universal/universalSolver.d.ts +1 -0
  27. package/dist/data/solver/universal/universalSolver.js +6 -0
  28. package/dist/data/solver/universal/universalWorker.js +5 -0
  29. package/dist/data/solver/z3/z3Solver.d.ts +2 -0
  30. package/dist/data/solver/z3/z3Solver.js +12 -0
  31. package/dist/data/symbols/directionLinkerSymbol.js +15 -9
  32. package/dist/data/symbols/focusSymbol.d.ts +30 -0
  33. package/dist/data/symbols/focusSymbol.js +110 -0
  34. package/dist/data/symbols/minesweeperSymbol.d.ts +1 -1
  35. package/dist/data/symbols/minesweeperSymbol.js +9 -2
  36. package/dist/data/symbols/symbols.gen.d.ts +1 -0
  37. package/dist/data/symbols/symbols.gen.js +1 -0
  38. package/dist/data/symbols/viewpointSymbol.js +10 -11
  39. package/dist/index.d.ts +6 -2
  40. package/dist/index.js +6 -2
  41. package/package.json +1 -1
@@ -58,7 +58,8 @@ class CellCountPerZoneRule extends Rule {
58
58
  };
59
59
  const stack = [seed];
60
60
  while (stack.length > 0) {
61
- const { x, y } = stack.pop();
61
+ let { x, y } = stack.pop();
62
+ ({ x, y } = grid.toArrayCoordinates(x, y));
62
63
  if (visited[y][x])
63
64
  continue;
64
65
  visited[y][x] = true;
@@ -71,12 +72,13 @@ class CellCountPerZoneRule extends Rule {
71
72
  complete = false;
72
73
  }
73
74
  for (const offset of NEIGHBOR_OFFSETS) {
74
- const next = { x: x + offset.x, y: y + offset.y };
75
- if (!grid.zones.edges.some(e => (e.x1 === x &&
76
- e.y1 === y &&
77
- e.x2 === next.x &&
78
- e.y2 === next.y) ||
79
- (e.x1 === next.x && e.y1 === next.y && e.x2 === x && e.y2 === y))) {
75
+ const next = grid.toArrayCoordinates(x + offset.x, y + offset.y);
76
+ if (!grid.zones.edges.some(e => {
77
+ const { x: x1, y: y1 } = grid.toArrayCoordinates(e.x1, e.y1);
78
+ const { x: x2, y: y2 } = grid.toArrayCoordinates(e.x2, e.y2);
79
+ return ((x1 === x && y1 === y && x2 === next.x && y2 === next.y) ||
80
+ (x2 === x && y2 === y && x1 === next.x && y1 === next.y));
81
+ })) {
80
82
  const nextTile = grid.getTile(next.x, next.y);
81
83
  if (nextTile.exists) {
82
84
  stack.push(next);
@@ -30,9 +30,9 @@ export default class RegionShapeRule extends Rule {
30
30
  if (!seed)
31
31
  break;
32
32
  const positions = [];
33
- grid.iterateArea(seed, tile => tile.color === this.color, (_, x, y) => {
33
+ grid.iterateArea(seed, tile => tile.color === this.color, (_, x, y, logX, logY) => {
34
34
  visited[y][x] = true;
35
- positions.push({ x, y });
35
+ positions.push({ x: logX, y: logY });
36
36
  });
37
37
  const incomplete = grid.iterateArea(seed, tile => tile.color === this.color || tile.color === Color.Gray, tile => {
38
38
  if (tile.color === Color.Gray)
@@ -46,7 +46,11 @@ export default class RegionShapeRule extends Rule {
46
46
  existing.count++;
47
47
  }
48
48
  else {
49
- regions.push({ positions, shape, count: 1 });
49
+ regions.push({
50
+ positions: positions.map(pos => grid.toArrayCoordinates(pos.x, pos.y)),
51
+ shape,
52
+ count: 1,
53
+ });
50
54
  }
51
55
  }
52
56
  return { regions, complete };
@@ -15,3 +15,4 @@ export { instance as SameShapeRule } from './sameShapeRule.js';
15
15
  export { instance as SymbolsPerRegionRule } from './symbolsPerRegionRule.js';
16
16
  export { instance as UndercluedRule } from './undercluedRule.js';
17
17
  export { instance as UniqueShapeRule } from './uniqueShapeRule.js';
18
+ export { instance as WrapAroundRule } from './wrapAroundRule.js';
@@ -19,3 +19,4 @@ export { instance as SameShapeRule } from './sameShapeRule.js';
19
19
  export { instance as SymbolsPerRegionRule } from './symbolsPerRegionRule.js';
20
20
  export { instance as UndercluedRule } from './undercluedRule.js';
21
21
  export { instance as UniqueShapeRule } from './uniqueShapeRule.js';
22
+ export { instance as WrapAroundRule } from './wrapAroundRule.js';
@@ -0,0 +1,34 @@
1
+ import { AnyConfig } from '../config.js';
2
+ import { GetTileHandler } from '../events/onGetTile.js';
3
+ import GridData from '../grid.js';
4
+ import { Position, RuleState, Wrapping } from '../primitives.js';
5
+ import Rule, { SearchVariant } from './rule.js';
6
+ export default class WrapAroundRule extends Rule implements GetTileHandler {
7
+ readonly horizontal: Wrapping;
8
+ readonly vertical: Wrapping;
9
+ private static readonly EXAMPLE_GRID_NONE;
10
+ private static readonly EXAMPLE_GRID_HORIZONTAL;
11
+ private static readonly EXAMPLE_GRID_VERTICAL;
12
+ private static readonly SEARCH_VARIANTS;
13
+ private static readonly CONFIGS;
14
+ /**
15
+ * **The left and right edges are connected (in reverse)**
16
+ *
17
+ * @param horizontal - The horizontal wrapping.
18
+ * @param vertical - The vertical wrapping.
19
+ */
20
+ constructor(horizontal: Wrapping, vertical: Wrapping);
21
+ onGetTile(x: number, y: number, grid: GridData): Position;
22
+ get id(): string;
23
+ get explanation(): string;
24
+ createExampleGrid(): GridData;
25
+ get configs(): readonly AnyConfig[] | null;
26
+ get searchVariants(): SearchVariant[];
27
+ validateGrid(grid: GridData): RuleState;
28
+ copyWith({ horizontal, vertical, }: {
29
+ horizontal?: Wrapping;
30
+ vertical?: Wrapping;
31
+ }): this;
32
+ get isSingleton(): boolean;
33
+ }
34
+ export declare const instance: WrapAroundRule;
@@ -0,0 +1,271 @@
1
+ import { ConfigType } from '../config.js';
2
+ import { array } from '../dataHelper.js';
3
+ import GridData from '../grid.js';
4
+ import { Color, MajorRule, Orientation, State, Wrapping, orientationToggle, } from '../primitives.js';
5
+ import LetterSymbol from '../symbols/letterSymbol.js';
6
+ import MyopiaSymbol from '../symbols/myopiaSymbol.js';
7
+ import Rule from './rule.js';
8
+ class WrapAroundRule extends Rule {
9
+ /**
10
+ * **The left and right edges are connected (in reverse)**
11
+ *
12
+ * @param horizontal - The horizontal wrapping.
13
+ * @param vertical - The vertical wrapping.
14
+ */
15
+ constructor(horizontal, vertical) {
16
+ super();
17
+ Object.defineProperty(this, "horizontal", {
18
+ enumerable: true,
19
+ configurable: true,
20
+ writable: true,
21
+ value: horizontal
22
+ });
23
+ Object.defineProperty(this, "vertical", {
24
+ enumerable: true,
25
+ configurable: true,
26
+ writable: true,
27
+ value: vertical
28
+ });
29
+ this.horizontal = horizontal;
30
+ this.vertical = vertical;
31
+ }
32
+ onGetTile(x, y, grid) {
33
+ if (grid.width === 0 || grid.height === 0) {
34
+ return { x, y };
35
+ }
36
+ if (this.horizontal !== Wrapping.None) {
37
+ const idx = Math.abs(Math.floor(x / grid.width));
38
+ x = ((x % grid.width) + grid.width) % grid.width;
39
+ if ((this.horizontal === Wrapping.WrapReverse ||
40
+ this.horizontal === Wrapping.ReflectReverse) &&
41
+ idx % 2 === 1) {
42
+ y = grid.height - 1 - y;
43
+ }
44
+ if (this.horizontal === Wrapping.ReflectReverse && idx % 2 === 1) {
45
+ x = grid.width - 1 - x;
46
+ }
47
+ }
48
+ if (this.vertical !== Wrapping.None) {
49
+ const idx = Math.abs(Math.floor(y / grid.height));
50
+ y = ((y % grid.height) + grid.height) % grid.height;
51
+ if ((this.vertical === Wrapping.WrapReverse ||
52
+ this.vertical === Wrapping.ReflectReverse) &&
53
+ idx % 2 === 1) {
54
+ x = grid.width - 1 - x;
55
+ }
56
+ if (this.vertical === Wrapping.ReflectReverse && idx % 2 === 1) {
57
+ y = grid.height - 1 - y;
58
+ }
59
+ }
60
+ return { x, y };
61
+ }
62
+ get id() {
63
+ return MajorRule.WrapAround;
64
+ }
65
+ get explanation() {
66
+ if (this.horizontal === Wrapping.None && this.vertical === Wrapping.None) {
67
+ return `No edges are connected.`;
68
+ }
69
+ const horizontal = this.horizontal === Wrapping.None
70
+ ? null
71
+ : this.horizontal === Wrapping.Wrap ||
72
+ this.horizontal === Wrapping.WrapReverse
73
+ ? 'connected'
74
+ : 'reflective';
75
+ const vertical = this.vertical === Wrapping.None
76
+ ? null
77
+ : this.vertical === Wrapping.Wrap ||
78
+ this.vertical === Wrapping.WrapReverse
79
+ ? 'connected'
80
+ : 'reflective';
81
+ const horizontalReverse = this.horizontal === Wrapping.WrapReverse ||
82
+ this.horizontal === Wrapping.ReflectReverse
83
+ ? ' in reverse'
84
+ : '';
85
+ const verticalReverse = this.vertical === Wrapping.WrapReverse ||
86
+ this.vertical === Wrapping.ReflectReverse
87
+ ? ' in reverse'
88
+ : '';
89
+ if (this.horizontal === this.vertical) {
90
+ return `All four edges are ${horizontal}${horizontalReverse}.`;
91
+ }
92
+ if (this.horizontal === Wrapping.None) {
93
+ return `The top and bottom edges are ${vertical}${verticalReverse}.`;
94
+ }
95
+ if (this.vertical === Wrapping.None) {
96
+ return `The left and right edges are ${horizontal}${horizontalReverse}.`;
97
+ }
98
+ if (horizontal === vertical) {
99
+ if (horizontalReverse !== '') {
100
+ return `All four edges are ${horizontal}, with the left and right edges${horizontalReverse}.`;
101
+ }
102
+ else {
103
+ return `All four edges are ${horizontal}, with the top and bottom edges${verticalReverse}.`;
104
+ }
105
+ }
106
+ return `The left and right edges are ${horizontal}${horizontalReverse}. The top and bottom edges are ${vertical}${verticalReverse}.`;
107
+ }
108
+ createExampleGrid() {
109
+ const horizontal = WrapAroundRule.EXAMPLE_GRID_HORIZONTAL[this.horizontal];
110
+ const vertical = WrapAroundRule.EXAMPLE_GRID_VERTICAL[this.vertical];
111
+ if (horizontal === WrapAroundRule.EXAMPLE_GRID_NONE) {
112
+ return vertical;
113
+ }
114
+ else if (vertical === WrapAroundRule.EXAMPLE_GRID_NONE) {
115
+ return horizontal;
116
+ }
117
+ else {
118
+ const tiles = array(5, 5, (x, y) => {
119
+ const hTile = horizontal.getTile(x, y);
120
+ const vTile = vertical.getTile(x, y);
121
+ return hTile.withColor(hTile.color === Color.Dark || vTile.color === Color.Dark
122
+ ? Color.Dark
123
+ : Color.Light);
124
+ });
125
+ const symbols = [];
126
+ horizontal.symbols.forEach(list => symbols.push(...list));
127
+ vertical.symbols.forEach(list => symbols.push(...list));
128
+ return horizontal.withTiles(tiles).withSymbols(symbols);
129
+ }
130
+ }
131
+ get configs() {
132
+ return WrapAroundRule.CONFIGS;
133
+ }
134
+ get searchVariants() {
135
+ return WrapAroundRule.SEARCH_VARIANTS;
136
+ }
137
+ validateGrid(grid) {
138
+ if (grid.getTileCount(true, false, Color.Gray) > 0) {
139
+ return { state: State.Incomplete };
140
+ }
141
+ else {
142
+ return { state: State.Satisfied };
143
+ }
144
+ }
145
+ copyWith({ horizontal, vertical, }) {
146
+ return new WrapAroundRule(horizontal ?? this.horizontal, vertical ?? this.vertical);
147
+ }
148
+ get isSingleton() {
149
+ return true;
150
+ }
151
+ }
152
+ Object.defineProperty(WrapAroundRule, "EXAMPLE_GRID_NONE", {
153
+ enumerable: true,
154
+ configurable: true,
155
+ writable: true,
156
+ value: Object.freeze(GridData.create(['wwwww', 'wwwww', 'wwwww', 'wwwww', 'wwwww']))
157
+ });
158
+ Object.defineProperty(WrapAroundRule, "EXAMPLE_GRID_HORIZONTAL", {
159
+ enumerable: true,
160
+ configurable: true,
161
+ writable: true,
162
+ value: Object.freeze({
163
+ [Wrapping.None]: WrapAroundRule.EXAMPLE_GRID_NONE,
164
+ [Wrapping.Wrap]: GridData.create([
165
+ 'wwwww',
166
+ 'bwwwb',
167
+ 'wwwww',
168
+ 'bwwwb',
169
+ 'wwwww',
170
+ ])
171
+ .addSymbol(new LetterSymbol(0, 1, 'A'))
172
+ .addSymbol(new LetterSymbol(4, 1, 'A'))
173
+ .addSymbol(new LetterSymbol(0, 3, 'B'))
174
+ .addSymbol(new LetterSymbol(4, 3, 'B')),
175
+ [Wrapping.WrapReverse]: GridData.create([
176
+ 'wwwww',
177
+ 'bwwwb',
178
+ 'wwwww',
179
+ 'bwwwb',
180
+ 'wwwww',
181
+ ])
182
+ .addSymbol(new LetterSymbol(0, 1, 'A'))
183
+ .addSymbol(new LetterSymbol(4, 1, 'B'))
184
+ .addSymbol(new LetterSymbol(0, 3, 'B'))
185
+ .addSymbol(new LetterSymbol(4, 3, 'A')),
186
+ [Wrapping.ReflectReverse]: GridData.create([
187
+ 'wwwww',
188
+ 'bwwww',
189
+ 'wwwww',
190
+ 'wwwwb',
191
+ 'wwwww',
192
+ ])
193
+ .addSymbol(new MyopiaSymbol(0, 3, false, orientationToggle(Orientation.Left)))
194
+ .addSymbol(new MyopiaSymbol(4, 1, false, orientationToggle(Orientation.Right))),
195
+ })
196
+ });
197
+ Object.defineProperty(WrapAroundRule, "EXAMPLE_GRID_VERTICAL", {
198
+ enumerable: true,
199
+ configurable: true,
200
+ writable: true,
201
+ value: Object.freeze({
202
+ [Wrapping.None]: WrapAroundRule.EXAMPLE_GRID_NONE,
203
+ [Wrapping.Wrap]: GridData.create([
204
+ 'wbwbw',
205
+ 'wwwww',
206
+ 'wwwww',
207
+ 'wwwww',
208
+ 'wbwbw',
209
+ ])
210
+ .addSymbol(new LetterSymbol(1, 0, 'C'))
211
+ .addSymbol(new LetterSymbol(3, 0, 'D'))
212
+ .addSymbol(new LetterSymbol(1, 4, 'C'))
213
+ .addSymbol(new LetterSymbol(3, 4, 'D')),
214
+ [Wrapping.WrapReverse]: GridData.create([
215
+ 'wbwbw',
216
+ 'wwwww',
217
+ 'wwwww',
218
+ 'wwwww',
219
+ 'wbwbw',
220
+ ])
221
+ .addSymbol(new LetterSymbol(1, 0, 'C'))
222
+ .addSymbol(new LetterSymbol(3, 0, 'D'))
223
+ .addSymbol(new LetterSymbol(1, 4, 'D'))
224
+ .addSymbol(new LetterSymbol(3, 4, 'C')),
225
+ [Wrapping.ReflectReverse]: GridData.create([
226
+ 'wbwww',
227
+ 'wwwww',
228
+ 'wwwww',
229
+ 'wwwww',
230
+ 'wwwbw',
231
+ ])
232
+ .addSymbol(new MyopiaSymbol(3, 0, false, orientationToggle(Orientation.Up)))
233
+ .addSymbol(new MyopiaSymbol(1, 4, false, orientationToggle(Orientation.Down))),
234
+ })
235
+ });
236
+ Object.defineProperty(WrapAroundRule, "SEARCH_VARIANTS", {
237
+ enumerable: true,
238
+ configurable: true,
239
+ writable: true,
240
+ value: [
241
+ new WrapAroundRule(Wrapping.Wrap, Wrapping.None).searchVariant(),
242
+ new WrapAroundRule(Wrapping.None, Wrapping.Wrap).searchVariant(),
243
+ new WrapAroundRule(Wrapping.Wrap, Wrapping.Wrap).searchVariant(),
244
+ new WrapAroundRule(Wrapping.ReflectReverse, Wrapping.None).searchVariant(),
245
+ new WrapAroundRule(Wrapping.None, Wrapping.ReflectReverse).searchVariant(),
246
+ new WrapAroundRule(Wrapping.ReflectReverse, Wrapping.ReflectReverse).searchVariant(),
247
+ ]
248
+ });
249
+ Object.defineProperty(WrapAroundRule, "CONFIGS", {
250
+ enumerable: true,
251
+ configurable: true,
252
+ writable: true,
253
+ value: Object.freeze([
254
+ {
255
+ type: ConfigType.Wrapping,
256
+ default: Wrapping.Wrap,
257
+ field: 'horizontal',
258
+ description: 'Horizontal wrap',
259
+ configurable: true,
260
+ },
261
+ {
262
+ type: ConfigType.Wrapping,
263
+ default: Wrapping.Wrap,
264
+ field: 'vertical',
265
+ description: 'Vertical wrap',
266
+ configurable: true,
267
+ },
268
+ ])
269
+ });
270
+ export default WrapAroundRule;
271
+ export const instance = new WrapAroundRule(Wrapping.Wrap, Wrapping.Wrap);
@@ -103,6 +103,7 @@ export default class SerializerV0 extends SerializerBase {
103
103
  case ConfigType.Number:
104
104
  case ConfigType.Color:
105
105
  case ConfigType.Comparison:
106
+ case ConfigType.Wrapping:
106
107
  case ConfigType.Direction:
107
108
  case ConfigType.Orientation:
108
109
  return (config.field +
@@ -190,6 +191,8 @@ export default class SerializerV0 extends SerializerBase {
190
191
  return [config.field, value];
191
192
  case ConfigType.Comparison:
192
193
  return [config.field, value];
194
+ case ConfigType.Wrapping:
195
+ return [config.field, value];
193
196
  case ConfigType.Direction:
194
197
  return [config.field, value];
195
198
  case ConfigType.DirectionToggle: {
@@ -2,6 +2,7 @@ import EventIteratingSolver from '../eventIteratingSolver.js';
2
2
  export default class BacktrackSolver extends EventIteratingSolver {
3
3
  private static readonly supportedInstrs;
4
4
  readonly id = "backtrack";
5
+ readonly author = "ALaggyDev";
5
6
  readonly description = "Solves puzzles using backtracking with optimizations (blazingly fast). Support most rules and symbols (including underclued).";
6
7
  protected createWorker(): Worker;
7
8
  isInstructionSupported(instructionId: string): boolean;
@@ -11,6 +11,7 @@ import { instance as galaxyInstance } from '../../symbols/galaxySymbol.js';
11
11
  import { instance as letterInstance } from '../../symbols/letterSymbol.js';
12
12
  import { instance as lotusInstance } from '../../symbols/lotusSymbol.js';
13
13
  import { instance as minesweeperInstance } from '../../symbols/minesweeperSymbol.js';
14
+ import { instance as focusInstance } from '../../symbols/focusSymbol.js';
14
15
  import { instance as myopiaInstance } from '../../symbols/myopiaSymbol.js';
15
16
  import { instance as viewpointInstance } from '../../symbols/viewpointSymbol.js';
16
17
  import { instance as connectAllInstance } from '../z3/modules/connectAllModule.js';
@@ -24,6 +25,12 @@ class BacktrackSolver extends EventIteratingSolver {
24
25
  writable: true,
25
26
  value: 'backtrack'
26
27
  });
28
+ Object.defineProperty(this, "author", {
29
+ enumerable: true,
30
+ configurable: true,
31
+ writable: true,
32
+ value: 'ALaggyDev'
33
+ });
27
34
  Object.defineProperty(this, "description", {
28
35
  enumerable: true,
29
36
  configurable: true,
@@ -52,6 +59,7 @@ Object.defineProperty(BacktrackSolver, "supportedInstrs", {
52
59
  lotusInstance.id,
53
60
  myopiaInstance.id,
54
61
  minesweeperInstance.id,
62
+ focusInstance.id,
55
63
  letterInstance.id,
56
64
  undercluedInstance.id,
57
65
  connectAllInstance.id,
@@ -14,6 +14,7 @@ import { instance as galaxyInstance, } from '../../symbols/galaxySymbol.js';
14
14
  import { instance as letterInstance, } from '../../symbols/letterSymbol.js';
15
15
  import { instance as lotusInstance, } from '../../symbols/lotusSymbol.js';
16
16
  import { instance as minesweeperInstance, } from '../../symbols/minesweeperSymbol.js';
17
+ import { instance as focusInstance, } from '../../symbols/focusSymbol.js';
17
18
  import { instance as myopiaInstance, } from '../../symbols/myopiaSymbol.js';
18
19
  import { instance as viewpointInstance, } from '../../symbols/viewpointSymbol.js';
19
20
  import { instance as connectAllInstance } from '../z3/modules/connectAllModule.js';
@@ -33,6 +34,7 @@ import LotusBTModule from './symbols/lotus.js';
33
34
  import MinesweeperBTModule from './symbols/minesweeper.js';
34
35
  import MyopiaBTModule from './symbols/myopia.js';
35
36
  import ViewpointBTModule from './symbols/viewpoint.js';
37
+ import FocusBTModule from './symbols/focus.js';
36
38
  function translateToBTGridData(grid) {
37
39
  const tiles = array(grid.width, grid.height, (x, y) => {
38
40
  const tile = grid.getTile(x, y);
@@ -71,6 +73,9 @@ function translateToBTGridData(grid) {
71
73
  else if (id === minesweeperInstance.id) {
72
74
  module = new MinesweeperBTModule(symbol);
73
75
  }
76
+ else if (id === focusInstance.id) {
77
+ module = new FocusBTModule(symbol);
78
+ }
74
79
  else if (id === letterInstance.id) {
75
80
  continue;
76
81
  }
@@ -289,6 +294,12 @@ onmessage = e => {
289
294
  let count = 0;
290
295
  solve(grid, solution => {
291
296
  // if (count === 0) console.timeLog('Solve time', 'First solution');
297
+ if (solution) {
298
+ if (solution.resetTiles().colorEquals(solution)) {
299
+ postMessage(null);
300
+ return false;
301
+ }
302
+ }
292
303
  postMessage(Serializer.stringifyGrid(solution));
293
304
  count += 1;
294
305
  return count < 2;
@@ -0,0 +1,9 @@
1
+ import FocusSymbol from '../../../symbols/focusSymbol.js';
2
+ import BTModule, { BTGridData, CheckResult } from '../data.js';
3
+ export default class FocusBTModule extends BTModule {
4
+ instr: FocusSymbol;
5
+ private cachedCheckResult?;
6
+ constructor(instr: FocusSymbol);
7
+ checkGlobal(grid: BTGridData): CheckResult | false;
8
+ private buildCheckAndRating;
9
+ }
@@ -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
+ }
@@ -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()));
@@ -28,6 +38,7 @@ export default class EventIteratingSolver extends Solver {
28
38
  }
29
39
  finally {
30
40
  worker.terminate();
41
+ abortSignal?.removeEventListener('abort', terminateHandler);
31
42
  }
32
43
  }
33
44
  }
@@ -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 author = "Lysine";
5
6
  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.";
7
+ readonly supportsCancellation = false;
6
8
  isEnvironmentSupported(): Promise<boolean>;
7
9
  solve(grid: GridData): AsyncGenerator<GridData | null>;
8
10
  isInstructionSupported(instructionId: string): boolean;