@logic-pad/core 0.7.0 → 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 (33) hide show
  1. package/assets/logic-core.global.d.ts +57 -11
  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/backtrack/backtrackSolver.d.ts +1 -0
  11. package/dist/data/solver/backtrack/backtrackSolver.js +8 -0
  12. package/dist/data/solver/backtrack/backtrackWorker.js +11 -0
  13. package/dist/data/solver/backtrack/symbols/focus.d.ts +9 -0
  14. package/dist/data/solver/backtrack/symbols/focus.js +59 -0
  15. package/dist/data/solver/eventIteratingSolver.d.ts +3 -2
  16. package/dist/data/solver/eventIteratingSolver.js +13 -2
  17. package/dist/data/solver/solver.d.ts +11 -6
  18. package/dist/data/solver/universal/universalSolver.d.ts +1 -0
  19. package/dist/data/solver/universal/universalSolver.js +6 -0
  20. package/dist/data/solver/universal/universalWorker.js +5 -0
  21. package/dist/data/solver/z3/z3Solver.d.ts +2 -0
  22. package/dist/data/solver/z3/z3Solver.js +12 -0
  23. package/dist/data/symbols/directionLinkerSymbol.js +22 -13
  24. package/dist/data/symbols/focusSymbol.d.ts +30 -0
  25. package/dist/data/symbols/focusSymbol.js +110 -0
  26. package/dist/data/symbols/minesweeperSymbol.d.ts +1 -1
  27. package/dist/data/symbols/minesweeperSymbol.js +9 -2
  28. package/dist/data/symbols/symbols.gen.d.ts +1 -0
  29. package/dist/data/symbols/symbols.gen.js +1 -0
  30. package/dist/data/symbols/viewpointSymbol.js +9 -11
  31. package/dist/index.d.ts +3 -1
  32. package/dist/index.js +3 -1
  33. package/package.json +1 -1
@@ -86,6 +86,7 @@ declare global {
86
86
  None = 'none',
87
87
  Wrap = 'wrap',
88
88
  WrapReverse = 'wrap-reverse',
89
+ ReflectReverse = 'reflect-reverse',
89
90
  }
90
91
  export declare const WRAPPINGS: readonly Wrapping[];
91
92
  export declare enum Direction {
@@ -567,7 +568,7 @@ declare global {
567
568
  get configs(): readonly AnyConfig[] | null;
568
569
  createExampleGrid(): GridData;
569
570
  get searchVariants(): SearchVariant[];
570
- validateGrid(grid: GridData): RuleState;
571
+ validateGrid(_grid: GridData): RuleState;
571
572
  onSetGrid(
572
573
  _oldGrid: GridData,
573
574
  newGrid: GridData,
@@ -650,9 +651,7 @@ declare global {
650
651
  readonly vertical: Wrapping;
651
652
  private static readonly EXAMPLE_GRID_NONE;
652
653
  private static readonly EXAMPLE_GRID_HORIZONTAL;
653
- private static readonly EXAMPLE_GRID_HORIZONTAL_REVERSE;
654
654
  private static readonly EXAMPLE_GRID_VERTICAL;
655
- private static readonly EXAMPLE_GRID_VERTICAL_REVERSE;
656
655
  private static readonly SEARCH_VARIANTS;
657
656
  private static readonly CONFIGS;
658
657
  /**
@@ -2034,9 +2033,6 @@ declare global {
2034
2033
  stringifyPuzzle(puzzle: Puzzle): string;
2035
2034
  parsePuzzle(input: string): Puzzle;
2036
2035
  }
2037
- export interface CancelRef {
2038
- cancel?: () => void;
2039
- }
2040
2036
  /**
2041
2037
  * Base class that all solvers must extend.
2042
2038
  */
@@ -2047,10 +2043,18 @@ declare global {
2047
2043
  * This is also displayed to the user when selecting a solver.
2048
2044
  */
2049
2045
  abstract get id(): string;
2046
+ /**
2047
+ * The author(s) of the solver.
2048
+ */
2049
+ abstract get author(): string;
2050
2050
  /**
2051
2051
  * A short paragraph describing when the user should use this solver.
2052
2052
  */
2053
2053
  abstract get description(): string;
2054
+ /**
2055
+ * Whether the solver supports cancellation. If `true`, the solver must respond to the abort signal if it is provided.
2056
+ */
2057
+ abstract get supportsCancellation(): boolean;
2054
2058
  /**
2055
2059
  * Solve the given grid. The implementation should delegate long-running tasks to a worker thread and yield solutions
2056
2060
  * asynchronously.
@@ -2066,12 +2070,12 @@ declare global {
2066
2070
  *
2067
2071
  * @param grid The grid to solve. The provided grid is guaranteed to be supported by the solver. Some tiles in the
2068
2072
  * grid may already be filled by the user. It is up to the solver to decide whether to respect these tiles or not.
2069
- * @param cancelRef A reference to a function that can be called to cancel the solver. If cancellation is supported,
2070
- * the solver can assign a function to `cancelRef.cancel` that will stop the solver when called.
2073
+ * @param abortSignal An optional signal that the solver should subscribe to in order to cancel the operation. If the
2074
+ * solver does not support cancellation, it should ignore this parameter.
2071
2075
  */
2072
2076
  abstract solve(
2073
2077
  grid: GridData,
2074
- cancelRef: CancelRef
2078
+ abortSignal?: AbortSignal
2075
2079
  ): AsyncGenerator<GridData | null>;
2076
2080
  /**
2077
2081
  * Check if the solver supports the current browser environment. This method is called once when the user first clicks
@@ -2104,15 +2108,17 @@ declare global {
2104
2108
  }
2105
2109
  export declare const allSolvers: Map<string, Solver>;
2106
2110
  export declare abstract class EventIteratingSolver extends Solver {
2111
+ readonly supportsCancellation = true;
2107
2112
  protected abstract createWorker(): Worker;
2108
2113
  solve(
2109
2114
  grid: GridData,
2110
- cancelRef: CancelRef
2115
+ abortSignal?: AbortSignal
2111
2116
  ): AsyncGenerator<GridData | null>;
2112
2117
  }
2113
2118
  export declare class BacktrackSolver extends EventIteratingSolver {
2114
2119
  private static readonly supportedInstrs;
2115
2120
  readonly id = 'backtrack';
2121
+ readonly author = 'ALaggyDev';
2116
2122
  readonly description =
2117
2123
  'Solves puzzles using backtracking with optimizations (blazingly fast). Support most rules and symbols (including underclued).';
2118
2124
  protected createWorker(): Worker;
@@ -2357,6 +2363,43 @@ declare global {
2357
2363
  y: number
2358
2364
  ): Position$1 | null;
2359
2365
  }
2366
+ export declare class FocusSymbol extends NumberSymbol {
2367
+ private static readonly CONFIGS;
2368
+ private static readonly EXAMPLE_GRID;
2369
+ /**
2370
+ * **Focus Numbers count directly adjacent cells of the same color**
2371
+ * @param x - The x-coordinate of the symbol.
2372
+ * @param y - The y-coordinate of the symbol.
2373
+ * @param number - The focus number.
2374
+ */
2375
+ constructor(x: number, y: number, number: number);
2376
+ get id(): string;
2377
+ get placementStep(): number;
2378
+ get explanation(): string;
2379
+ get configs(): readonly AnyConfig[] | null;
2380
+ createExampleGrid(): GridData;
2381
+ countTiles(grid: GridData): {
2382
+ completed: number;
2383
+ possible: number;
2384
+ };
2385
+ copyWith({
2386
+ x,
2387
+ y,
2388
+ number,
2389
+ }: {
2390
+ x?: number;
2391
+ y?: number;
2392
+ number?: number;
2393
+ }): this;
2394
+ withNumber(number: number): this;
2395
+ }
2396
+ export declare class FocusBTModule extends BTModule {
2397
+ instr: FocusSymbol;
2398
+ private cachedCheckResult?;
2399
+ constructor(instr: FocusSymbol);
2400
+ checkGlobal(grid: BTGridData): CheckResult | false;
2401
+ private buildCheckAndRating;
2402
+ }
2360
2403
  export declare class GalaxySymbol extends DirectionLinkerSymbol {
2361
2404
  readonly x: number;
2362
2405
  readonly y: number;
@@ -2464,7 +2507,7 @@ declare global {
2464
2507
  private static readonly CONFIGS;
2465
2508
  private static readonly EXAMPLE_GRID;
2466
2509
  /**
2467
- * **Minesweeper numbers count opposite cells in 8 adjacent spaces**
2510
+ * **Minesweeper Numbers count opposite cells in 8 adjacent spaces**
2468
2511
  *
2469
2512
  * @param x - The x-coordinate of the symbol.
2470
2513
  * @param y - The y-coordinate of the symbol.
@@ -2588,6 +2631,7 @@ declare global {
2588
2631
  }
2589
2632
  export declare class UniversalSolver extends EventIteratingSolver {
2590
2633
  readonly id = 'universal';
2634
+ readonly author = 'romain22222, Lysine';
2591
2635
  readonly description =
2592
2636
  'A backtracking solver that supports all rules and symbols (including underclued) but is less optimized.';
2593
2637
  protected createWorker(): Worker;
@@ -2678,8 +2722,10 @@ declare global {
2678
2722
  ): import('grilops').Direction;
2679
2723
  export declare class Z3Solver extends Solver {
2680
2724
  readonly id = 'z3';
2725
+ readonly author = 'Lysine';
2681
2726
  readonly description =
2682
2727
  '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.';
2728
+ readonly supportsCancellation = false;
2683
2729
  isEnvironmentSupported(): Promise<boolean>;
2684
2730
  solve(grid: GridData): AsyncGenerator<GridData | null>;
2685
2731
  isInstructionSupported(instructionId: string): boolean;
package/dist/data/grid.js CHANGED
@@ -204,6 +204,7 @@ export default class GridData {
204
204
  const tiles = this.tiles.map(row => [...row]);
205
205
  const newTile = typeof tile === 'function' ? tile(tiles[y][x]) : tile;
206
206
  changing.forEach(({ x, y }) => {
207
+ ({ x, y } = this.toArrayCoordinates(x, y));
207
208
  tiles[y][x] = tiles[y][x].withColor(newTile.color);
208
209
  });
209
210
  tiles[y][x] = newTile;
@@ -67,7 +67,8 @@ export declare const COMPARISONS: readonly Comparison[];
67
67
  export declare enum Wrapping {
68
68
  None = "none",
69
69
  Wrap = "wrap",
70
- WrapReverse = "wrap-reverse"
70
+ WrapReverse = "wrap-reverse",
71
+ ReflectReverse = "reflect-reverse"
71
72
  }
72
73
  export declare const WRAPPINGS: readonly Wrapping[];
73
74
  export declare enum Direction {
@@ -56,11 +56,13 @@ export var Wrapping;
56
56
  Wrapping["None"] = "none";
57
57
  Wrapping["Wrap"] = "wrap";
58
58
  Wrapping["WrapReverse"] = "wrap-reverse";
59
+ Wrapping["ReflectReverse"] = "reflect-reverse";
59
60
  })(Wrapping || (Wrapping = {}));
60
61
  export const WRAPPINGS = [
61
62
  Wrapping.None,
62
63
  Wrapping.Wrap,
63
64
  Wrapping.WrapReverse,
65
+ Wrapping.ReflectReverse,
64
66
  ];
65
67
  export var Direction;
66
68
  (function (Direction) {
@@ -74,7 +74,15 @@ class BanPatternRule extends Rule {
74
74
  for (let y = 0; y <= grid.height - 1; y++) {
75
75
  for (let x = 0; x <= grid.width - 1; x++) {
76
76
  let match = true;
77
+ const visited = [];
77
78
  for (const tile of pattern.elements) {
79
+ const pos = grid.toArrayCoordinates(x + tile.x, y + tile.y);
80
+ if (grid.wrapAround.value && // optimization: not need to check visited if wrapAround is disabled
81
+ visited.some(p => p.x === pos.x && p.y === pos.y)) {
82
+ match = false;
83
+ break;
84
+ }
85
+ visited.push(pos);
78
86
  const t = grid.getTile(x + tile.x, y + tile.y);
79
87
  if (!t.exists || t.color !== tile.color) {
80
88
  match = false;
@@ -25,7 +25,7 @@ export default class MusicGridRule extends Rule implements GridChangeHandler, Se
25
25
  get configs(): readonly AnyConfig[] | null;
26
26
  createExampleGrid(): GridData;
27
27
  get searchVariants(): SearchVariant[];
28
- validateGrid(grid: GridData): RuleState;
28
+ validateGrid(_grid: GridData): RuleState;
29
29
  onSetGrid(_oldGrid: GridData, newGrid: GridData, _solution: GridData | null): GridData;
30
30
  onGridChange(newGrid: GridData): this;
31
31
  onGridResize(_grid: GridData, mode: 'insert' | 'remove', direction: 'row' | 'column', index: number): this | null;
@@ -61,13 +61,8 @@ class MusicGridRule extends Rule {
61
61
  get searchVariants() {
62
62
  return MusicGridRule.SEARCH_VARIANTS;
63
63
  }
64
- validateGrid(grid) {
65
- if (grid.getTileCount(true, false, Color.Gray) > 0) {
66
- return { state: State.Incomplete };
67
- }
68
- else {
69
- return { state: State.Satisfied };
70
- }
64
+ validateGrid(_grid) {
65
+ return { state: State.Incomplete };
71
66
  }
72
67
  onSetGrid(_oldGrid, newGrid, _solution) {
73
68
  if (newGrid.getTileCount(true, undefined, Color.Gray) === 0)
@@ -8,9 +8,7 @@ export default class WrapAroundRule extends Rule implements GetTileHandler {
8
8
  readonly vertical: Wrapping;
9
9
  private static readonly EXAMPLE_GRID_NONE;
10
10
  private static readonly EXAMPLE_GRID_HORIZONTAL;
11
- private static readonly EXAMPLE_GRID_HORIZONTAL_REVERSE;
12
11
  private static readonly EXAMPLE_GRID_VERTICAL;
13
- private static readonly EXAMPLE_GRID_VERTICAL_REVERSE;
14
12
  private static readonly SEARCH_VARIANTS;
15
13
  private static readonly CONFIGS;
16
14
  /**
@@ -1,8 +1,9 @@
1
1
  import { ConfigType } from '../config.js';
2
2
  import { array } from '../dataHelper.js';
3
3
  import GridData from '../grid.js';
4
- import { Color, MajorRule, State, Wrapping, } from '../primitives.js';
4
+ import { Color, MajorRule, Orientation, State, Wrapping, orientationToggle, } from '../primitives.js';
5
5
  import LetterSymbol from '../symbols/letterSymbol.js';
6
+ import MyopiaSymbol from '../symbols/myopiaSymbol.js';
6
7
  import Rule from './rule.js';
7
8
  class WrapAroundRule extends Rule {
8
9
  /**
@@ -29,19 +30,32 @@ class WrapAroundRule extends Rule {
29
30
  this.vertical = vertical;
30
31
  }
31
32
  onGetTile(x, y, grid) {
33
+ if (grid.width === 0 || grid.height === 0) {
34
+ return { x, y };
35
+ }
32
36
  if (this.horizontal !== Wrapping.None) {
33
37
  const idx = Math.abs(Math.floor(x / grid.width));
34
38
  x = ((x % grid.width) + grid.width) % grid.width;
35
- if (this.horizontal === Wrapping.WrapReverse && idx % 2 === 1) {
39
+ if ((this.horizontal === Wrapping.WrapReverse ||
40
+ this.horizontal === Wrapping.ReflectReverse) &&
41
+ idx % 2 === 1) {
36
42
  y = grid.height - 1 - y;
37
43
  }
44
+ if (this.horizontal === Wrapping.ReflectReverse && idx % 2 === 1) {
45
+ x = grid.width - 1 - x;
46
+ }
38
47
  }
39
48
  if (this.vertical !== Wrapping.None) {
40
49
  const idx = Math.abs(Math.floor(y / grid.height));
41
50
  y = ((y % grid.height) + grid.height) % grid.height;
42
- if (this.vertical === Wrapping.WrapReverse && idx % 2 === 1) {
51
+ if ((this.vertical === Wrapping.WrapReverse ||
52
+ this.vertical === Wrapping.ReflectReverse) &&
53
+ idx % 2 === 1) {
43
54
  x = grid.width - 1 - x;
44
55
  }
56
+ if (this.vertical === Wrapping.ReflectReverse && idx % 2 === 1) {
57
+ y = grid.height - 1 - y;
58
+ }
45
59
  }
46
60
  return { x, y };
47
61
  }
@@ -52,37 +66,48 @@ class WrapAroundRule extends Rule {
52
66
  if (this.horizontal === Wrapping.None && this.vertical === Wrapping.None) {
53
67
  return `No edges are connected.`;
54
68
  }
55
- else if (this.horizontal === Wrapping.None) {
56
- return `The top and bottom edges are connected${this.vertical === Wrapping.WrapReverse ? ' in reverse' : ''}.`;
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}.`;
57
91
  }
58
- else if (this.vertical === Wrapping.None) {
59
- return `The left and right edges are connected${this.horizontal === Wrapping.WrapReverse ? ' in reverse' : ''}.`;
92
+ if (this.horizontal === Wrapping.None) {
93
+ return `The top and bottom edges are ${vertical}${verticalReverse}.`;
60
94
  }
61
- else if (this.horizontal === Wrapping.Wrap &&
62
- this.vertical === Wrapping.Wrap) {
63
- return `All four edges are connected.`;
95
+ if (this.vertical === Wrapping.None) {
96
+ return `The left and right edges are ${horizontal}${horizontalReverse}.`;
64
97
  }
65
- else if (this.horizontal === Wrapping.Wrap) {
66
- return `All four edges are connected, with the top and bottom edges in reverse.`;
67
- }
68
- else if (this.vertical === Wrapping.Wrap) {
69
- return `All four edges are connected, with the left and right edges in reverse.`;
70
- }
71
- else {
72
- return `All four edges are connected in reverse.`;
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
+ }
73
105
  }
106
+ return `The left and right edges are ${horizontal}${horizontalReverse}. The top and bottom edges are ${vertical}${verticalReverse}.`;
74
107
  }
75
108
  createExampleGrid() {
76
- const horizontal = this.horizontal === Wrapping.Wrap
77
- ? WrapAroundRule.EXAMPLE_GRID_HORIZONTAL
78
- : this.horizontal === Wrapping.WrapReverse
79
- ? WrapAroundRule.EXAMPLE_GRID_HORIZONTAL_REVERSE
80
- : WrapAroundRule.EXAMPLE_GRID_NONE;
81
- const vertical = this.vertical === Wrapping.Wrap
82
- ? WrapAroundRule.EXAMPLE_GRID_VERTICAL
83
- : this.vertical === Wrapping.WrapReverse
84
- ? WrapAroundRule.EXAMPLE_GRID_VERTICAL_REVERSE
85
- : WrapAroundRule.EXAMPLE_GRID_NONE;
109
+ const horizontal = WrapAroundRule.EXAMPLE_GRID_HORIZONTAL[this.horizontal];
110
+ const vertical = WrapAroundRule.EXAMPLE_GRID_VERTICAL[this.vertical];
86
111
  if (horizontal === WrapAroundRule.EXAMPLE_GRID_NONE) {
87
112
  return vertical;
88
113
  }
@@ -134,41 +159,79 @@ Object.defineProperty(WrapAroundRule, "EXAMPLE_GRID_HORIZONTAL", {
134
159
  enumerable: true,
135
160
  configurable: true,
136
161
  writable: true,
137
- value: Object.freeze(GridData.create(['wwwww', 'bwwwb', 'wwwww', 'bwwwb', 'wwwww'])
138
- .addSymbol(new LetterSymbol(0, 1, 'A'))
139
- .addSymbol(new LetterSymbol(4, 1, 'A'))
140
- .addSymbol(new LetterSymbol(0, 3, 'B'))
141
- .addSymbol(new LetterSymbol(4, 3, 'B')))
142
- });
143
- Object.defineProperty(WrapAroundRule, "EXAMPLE_GRID_HORIZONTAL_REVERSE", {
144
- enumerable: true,
145
- configurable: true,
146
- writable: true,
147
- value: Object.freeze(GridData.create(['wwwww', 'bwwwb', 'wwwww', 'bwwwb', 'wwwww'])
148
- .addSymbol(new LetterSymbol(0, 1, 'A'))
149
- .addSymbol(new LetterSymbol(4, 1, 'B'))
150
- .addSymbol(new LetterSymbol(0, 3, 'B'))
151
- .addSymbol(new LetterSymbol(4, 3, 'A')))
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
+ })
152
196
  });
153
197
  Object.defineProperty(WrapAroundRule, "EXAMPLE_GRID_VERTICAL", {
154
198
  enumerable: true,
155
199
  configurable: true,
156
200
  writable: true,
157
- value: Object.freeze(GridData.create(['wbwbw', 'wwwww', 'wwwww', 'wwwww', 'wbwbw'])
158
- .addSymbol(new LetterSymbol(1, 0, 'C'))
159
- .addSymbol(new LetterSymbol(3, 0, 'D'))
160
- .addSymbol(new LetterSymbol(1, 4, 'C'))
161
- .addSymbol(new LetterSymbol(3, 4, 'D')))
162
- });
163
- Object.defineProperty(WrapAroundRule, "EXAMPLE_GRID_VERTICAL_REVERSE", {
164
- enumerable: true,
165
- configurable: true,
166
- writable: true,
167
- value: Object.freeze(GridData.create(['wbwbw', 'wwwww', 'wwwww', 'wwwww', 'wbwbw'])
168
- .addSymbol(new LetterSymbol(1, 0, 'C'))
169
- .addSymbol(new LetterSymbol(3, 0, 'D'))
170
- .addSymbol(new LetterSymbol(1, 4, 'D'))
171
- .addSymbol(new LetterSymbol(3, 4, 'C')))
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
+ })
172
235
  });
173
236
  Object.defineProperty(WrapAroundRule, "SEARCH_VARIANTS", {
174
237
  enumerable: true,
@@ -178,6 +241,9 @@ Object.defineProperty(WrapAroundRule, "SEARCH_VARIANTS", {
178
241
  new WrapAroundRule(Wrapping.Wrap, Wrapping.None).searchVariant(),
179
242
  new WrapAroundRule(Wrapping.None, Wrapping.Wrap).searchVariant(),
180
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(),
181
247
  ]
182
248
  });
183
249
  Object.defineProperty(WrapAroundRule, "CONFIGS", {
@@ -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;
@@ -14,12 +14,24 @@ 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
27
  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.'
22
28
  });
29
+ Object.defineProperty(this, "supportsCancellation", {
30
+ enumerable: true,
31
+ configurable: true,
32
+ writable: true,
33
+ value: false
34
+ });
23
35
  }
24
36
  async isEnvironmentSupported() {
25
37
  try {
@@ -65,8 +65,11 @@ class DirectionLinkerSymbol extends Symbol {
65
65
  createExampleGrid() {
66
66
  return DirectionLinkerSymbol.EXAMPLE_GRID;
67
67
  }
68
- deltaCoordinate(c, direction, grid) {
69
- return grid.toArrayCoordinates(c.x + DirectionLinkerSymbol.directionDeltas[direction].dx, c.y + DirectionLinkerSymbol.directionDeltas[direction].dy);
68
+ deltaCoordinate(c, direction) {
69
+ return {
70
+ x: c.x + DirectionLinkerSymbol.directionDeltas[direction].dx,
71
+ y: c.y + DirectionLinkerSymbol.directionDeltas[direction].dy,
72
+ };
70
73
  }
71
74
  validateSymbol(grid) {
72
75
  // A turtle is an object which have 2 coordinates
@@ -112,22 +115,28 @@ class DirectionLinkerSymbol extends Symbol {
112
115
  const directions = Object.keys(this.linkedDirections);
113
116
  for (const direction of directions) {
114
117
  const newTurtle = {
115
- pos1: this.deltaCoordinate(pos1, direction, grid),
116
- pos2: this.deltaCoordinate(pos2, this.linkedDirections[direction], grid),
118
+ pos1: this.deltaCoordinate(pos1, direction),
119
+ pos2: this.deltaCoordinate(pos2, this.linkedDirections[direction]),
117
120
  color1: baseColor1,
118
121
  color2: baseColor2,
119
122
  };
120
- if (checkedCouples.some(({ pos1, pos2 }) => pos1.x === newTurtle.pos1.x &&
121
- pos1.y === newTurtle.pos1.y &&
122
- pos2.x === newTurtle.pos2.x &&
123
- pos2.y === newTurtle.pos2.y) ||
124
- (pos1.x === newTurtle.pos2.x &&
125
- pos1.y === newTurtle.pos2.y &&
126
- pos2.x === newTurtle.pos1.x &&
127
- pos2.y === newTurtle.pos1.y)) {
123
+ const newArrTurtle = {
124
+ pos1: grid.toArrayCoordinates(newTurtle.pos1.x, newTurtle.pos1.y),
125
+ pos2: grid.toArrayCoordinates(newTurtle.pos2.x, newTurtle.pos2.y),
126
+ color1: baseColor1,
127
+ color2: baseColor2,
128
+ };
129
+ if (checkedCouples.some(({ pos1, pos2 }) => pos1.x === newArrTurtle.pos1.x &&
130
+ pos1.y === newArrTurtle.pos1.y &&
131
+ pos2.x === newArrTurtle.pos2.x &&
132
+ pos2.y === newArrTurtle.pos2.y) ||
133
+ (pos1.x === newArrTurtle.pos2.x &&
134
+ pos1.y === newArrTurtle.pos2.y &&
135
+ pos2.x === newArrTurtle.pos1.x &&
136
+ pos2.y === newArrTurtle.pos1.y)) {
128
137
  continue;
129
138
  }
130
- checkedCouples.push(newTurtle);
139
+ checkedCouples.push(newArrTurtle);
131
140
  queue.push(newTurtle);
132
141
  }
133
142
  }
@@ -0,0 +1,30 @@
1
+ import { AnyConfig } from '../config.js';
2
+ import GridData from '../grid.js';
3
+ import NumberSymbol from './numberSymbol.js';
4
+ export default class FocusSymbol extends NumberSymbol {
5
+ private static readonly CONFIGS;
6
+ private static readonly EXAMPLE_GRID;
7
+ /**
8
+ * **Focus Numbers count directly adjacent cells of the same color**
9
+ * @param x - The x-coordinate of the symbol.
10
+ * @param y - The y-coordinate of the symbol.
11
+ * @param number - The focus number.
12
+ */
13
+ constructor(x: number, y: number, number: number);
14
+ get id(): string;
15
+ get placementStep(): number;
16
+ get explanation(): string;
17
+ get configs(): readonly AnyConfig[] | null;
18
+ createExampleGrid(): GridData;
19
+ countTiles(grid: GridData): {
20
+ completed: number;
21
+ possible: number;
22
+ };
23
+ copyWith({ x, y, number, }: {
24
+ x?: number;
25
+ y?: number;
26
+ number?: number;
27
+ }): this;
28
+ withNumber(number: number): this;
29
+ }
30
+ export declare const instance: FocusSymbol;
@@ -0,0 +1,110 @@
1
+ import { ConfigType } from '../config.js';
2
+ import GridData from '../grid.js';
3
+ import { Color } from '../primitives.js';
4
+ import NumberSymbol from './numberSymbol.js';
5
+ const OFFSETS = [
6
+ [0, -1],
7
+ [1, 0],
8
+ [0, 1],
9
+ [-1, 0],
10
+ ];
11
+ class FocusSymbol extends NumberSymbol {
12
+ /**
13
+ * **Focus Numbers count directly adjacent cells of the same color**
14
+ * @param x - The x-coordinate of the symbol.
15
+ * @param y - The y-coordinate of the symbol.
16
+ * @param number - The focus number.
17
+ */
18
+ constructor(x, y, number) {
19
+ super(x, y, number);
20
+ }
21
+ get id() {
22
+ return `focus`;
23
+ }
24
+ get placementStep() {
25
+ return 1;
26
+ }
27
+ get explanation() {
28
+ return '*Focus Numbers* count directly adjacent cells of the same color';
29
+ }
30
+ get configs() {
31
+ return FocusSymbol.CONFIGS;
32
+ }
33
+ createExampleGrid() {
34
+ return FocusSymbol.EXAMPLE_GRID;
35
+ }
36
+ countTiles(grid) {
37
+ if (Math.floor(this.x) !== this.x || Math.floor(this.y) !== this.y)
38
+ return { completed: 0, possible: Number.MAX_SAFE_INTEGER };
39
+ const color = grid.getTile(this.x, this.y).color;
40
+ if (color === Color.Gray)
41
+ return { completed: 0, possible: Number.MAX_SAFE_INTEGER };
42
+ let gray = 0;
43
+ let same = 0;
44
+ const visited = [];
45
+ for (const [dx, dy] of OFFSETS) {
46
+ const x = this.x + dx;
47
+ const y = this.y + dy;
48
+ if (grid.wrapAround.value) {
49
+ const pos = grid.toArrayCoordinates(x, y);
50
+ if (visited.some(v => v.x === pos.x && v.y === pos.y))
51
+ continue;
52
+ visited.push(pos);
53
+ }
54
+ const tile = grid.getTile(x, y);
55
+ if (!tile.exists)
56
+ continue;
57
+ if (tile.color === Color.Gray)
58
+ gray++;
59
+ else if (tile.color === color)
60
+ same++;
61
+ }
62
+ return { completed: same, possible: same + gray };
63
+ }
64
+ copyWith({ x, y, number, }) {
65
+ return new FocusSymbol(x ?? this.x, y ?? this.y, number ?? this.number);
66
+ }
67
+ withNumber(number) {
68
+ return this.copyWith({ number });
69
+ }
70
+ }
71
+ Object.defineProperty(FocusSymbol, "CONFIGS", {
72
+ enumerable: true,
73
+ configurable: true,
74
+ writable: true,
75
+ value: Object.freeze([
76
+ {
77
+ type: ConfigType.Number,
78
+ default: 0,
79
+ field: 'x',
80
+ description: 'X',
81
+ configurable: false,
82
+ },
83
+ {
84
+ type: ConfigType.Number,
85
+ default: 0,
86
+ field: 'y',
87
+ description: 'Y',
88
+ configurable: false,
89
+ },
90
+ {
91
+ type: ConfigType.Number,
92
+ default: 1,
93
+ field: 'number',
94
+ description: 'Number',
95
+ configurable: true,
96
+ },
97
+ ])
98
+ });
99
+ Object.defineProperty(FocusSymbol, "EXAMPLE_GRID", {
100
+ enumerable: true,
101
+ configurable: true,
102
+ writable: true,
103
+ value: Object.freeze(GridData.create(['wwwww', 'bbbbw', 'wwbbw', 'wwwww']).withSymbols([
104
+ new FocusSymbol(0, 0, 1),
105
+ new FocusSymbol(4, 1, 2),
106
+ new FocusSymbol(1, 3, 3),
107
+ ]))
108
+ });
109
+ export default FocusSymbol;
110
+ export const instance = new FocusSymbol(0, 0, 1);
@@ -5,7 +5,7 @@ export default class MinesweeperSymbol extends NumberSymbol {
5
5
  private static readonly CONFIGS;
6
6
  private static readonly EXAMPLE_GRID;
7
7
  /**
8
- * **Minesweeper numbers count opposite cells in 8 adjacent spaces**
8
+ * **Minesweeper Numbers count opposite cells in 8 adjacent spaces**
9
9
  *
10
10
  * @param x - The x-coordinate of the symbol.
11
11
  * @param y - The y-coordinate of the symbol.
@@ -4,7 +4,7 @@ import { Color } from '../primitives.js';
4
4
  import NumberSymbol from './numberSymbol.js';
5
5
  class MinesweeperSymbol extends NumberSymbol {
6
6
  /**
7
- * **Minesweeper numbers count opposite cells in 8 adjacent spaces**
7
+ * **Minesweeper Numbers count opposite cells in 8 adjacent spaces**
8
8
  *
9
9
  * @param x - The x-coordinate of the symbol.
10
10
  * @param y - The y-coordinate of the symbol.
@@ -20,7 +20,7 @@ class MinesweeperSymbol extends NumberSymbol {
20
20
  return 1;
21
21
  }
22
22
  get explanation() {
23
- return `*Minesweeper numbers* count opposite cells in 8 adjacent spaces`;
23
+ return `*Minesweeper Numbers* count opposite cells in 8 adjacent spaces`;
24
24
  }
25
25
  get configs() {
26
26
  return MinesweeperSymbol.CONFIGS;
@@ -36,10 +36,17 @@ class MinesweeperSymbol extends NumberSymbol {
36
36
  return { completed: 0, possible: Number.MAX_SAFE_INTEGER };
37
37
  let gray = 0;
38
38
  let opposite = 0;
39
+ const visited = [];
39
40
  for (let y = this.y - 1; y <= this.y + 1; y++) {
40
41
  for (let x = this.x - 1; x <= this.x + 1; x++) {
41
42
  if (x === this.x && y === this.y)
42
43
  continue;
44
+ if (grid.wrapAround.value) {
45
+ const pos = grid.toArrayCoordinates(x, y);
46
+ if (visited.some(v => v.x === pos.x && v.y === pos.y))
47
+ continue;
48
+ visited.push(pos);
49
+ }
43
50
  const tile = grid.getTile(x, y);
44
51
  if (!tile.exists)
45
52
  continue;
@@ -2,6 +2,7 @@ export { instance as AreaNumberSymbol } from './areaNumberSymbol.js';
2
2
  export { instance as CustomIconSymbol } from './customIconSymbol.js';
3
3
  export { instance as CustomTextSymbol } from './customTextSymbol.js';
4
4
  export { instance as DartSymbol } from './dartSymbol.js';
5
+ export { instance as FocusSymbol } from './focusSymbol.js';
5
6
  export { instance as GalaxySymbol } from './galaxySymbol.js';
6
7
  export { instance as HiddenSymbol } from './hiddenSymbol.js';
7
8
  export { instance as LetterSymbol } from './letterSymbol.js';
@@ -6,6 +6,7 @@ export { instance as AreaNumberSymbol } from './areaNumberSymbol.js';
6
6
  export { instance as CustomIconSymbol } from './customIconSymbol.js';
7
7
  export { instance as CustomTextSymbol } from './customTextSymbol.js';
8
8
  export { instance as DartSymbol } from './dartSymbol.js';
9
+ export { instance as FocusSymbol } from './focusSymbol.js';
9
10
  export { instance as GalaxySymbol } from './galaxySymbol.js';
10
11
  export { instance as HiddenSymbol } from './hiddenSymbol.js';
11
12
  export { instance as LetterSymbol } from './letterSymbol.js';
@@ -31,19 +31,17 @@ class ViewpointSymbol extends NumberSymbol {
31
31
  countForColor(grid, color, pos) {
32
32
  let minSize = 1;
33
33
  let maxSize = 1;
34
- const visited = array(grid.width, grid.height, (x, y) => x === pos.x && y === pos.y);
34
+ const visitedColored = array(grid.width, grid.height, (x, y) => x === pos.x && y === pos.y);
35
35
  for (const direction of DIRECTIONS) {
36
- let continuous = true;
37
- grid.iterateDirection(move(pos, direction), direction, tile => tile.color === color || tile.color === Color.Gray, tile => {
36
+ grid.iterateDirection(move(pos, direction), direction, tile => tile.color === color, () => {
37
+ minSize++;
38
+ }, visitedColored);
39
+ }
40
+ const visitedAll = array(grid.width, grid.height, (x, y) => x === pos.x && y === pos.y);
41
+ for (const direction of DIRECTIONS) {
42
+ grid.iterateDirection(move(pos, direction), direction, tile => tile.color === color || tile.color === Color.Gray, () => {
38
43
  maxSize++;
39
- if (tile.color === Color.Gray) {
40
- continuous = false;
41
- }
42
- else {
43
- if (continuous)
44
- minSize++;
45
- }
46
- }, visited);
44
+ }, visitedAll);
47
45
  }
48
46
  return { completed: minSize, possible: maxSize };
49
47
  }
package/dist/index.d.ts CHANGED
@@ -60,6 +60,7 @@ import UniqueShapeBTModule from './data/solver/backtrack/rules/uniqueShape.js';
60
60
  import AreaNumberBTModule from './data/solver/backtrack/symbols/areaNumber.js';
61
61
  import DartBTModule from './data/solver/backtrack/symbols/dart.js';
62
62
  import DirectionLinkerBTModule from './data/solver/backtrack/symbols/directionLinker.js';
63
+ import FocusBTModule from './data/solver/backtrack/symbols/focus.js';
63
64
  import GalaxyBTModule from './data/solver/backtrack/symbols/galaxy.js';
64
65
  import LetterBTModule from './data/solver/backtrack/symbols/letter.js';
65
66
  import LotusBTModule from './data/solver/backtrack/symbols/lotus.js';
@@ -88,6 +89,7 @@ import CustomSymbol from './data/symbols/customSymbol.js';
88
89
  import CustomTextSymbol from './data/symbols/customTextSymbol.js';
89
90
  import DartSymbol from './data/symbols/dartSymbol.js';
90
91
  import DirectionLinkerSymbol from './data/symbols/directionLinkerSymbol.js';
92
+ import FocusSymbol from './data/symbols/focusSymbol.js';
91
93
  import GalaxySymbol from './data/symbols/galaxySymbol.js';
92
94
  import HiddenSymbol from './data/symbols/hiddenSymbol.js';
93
95
  import { allSymbols } from './data/symbols/index.js';
@@ -102,4 +104,4 @@ import ViewpointSymbol from './data/symbols/viewpointSymbol.js';
102
104
  import TileData from './data/tile.js';
103
105
  import TileConnections from './data/tileConnections.js';
104
106
  import validateGrid, { aggregateState, applyFinalOverrides } from './data/validate.js';
105
- export { ConfigType, configEquals, Configurable, CachedAccess, allEqual, array, directionToRotation, escape, isSameEdge, maxBy, minBy, move, orientationToRotation, resize, unescape, isEventHandler, handlesFinalValidation, handlesGetTile, handlesGridChange, handlesGridResize, handlesSetGrid, invokeSetGrid, handlesSymbolDisplay, handlesSymbolValidation, GridData, NEIGHBOR_OFFSETS, GridConnections, GridZones, Instruction, COMPARISONS, Color, Comparison, DIRECTIONS, Direction, MajorRule, Mode, ORIENTATIONS, Orientation, State, WRAPPINGS, Wrapping, directionToggle, orientationToggle, MetadataSchema, PuzzleSchema, BanPatternRule, CellCountPerZoneRule, CellCountRule, CompletePatternRule, ConnectAllRule, CustomRule, ForesightRule, allRules, LyingSymbolRule, ControlLine, Row, MusicGridRule, MysteryRule, OffByXRule, PerfectionRule, RegionAreaRule, RegionShapeRule, Rule, SameShapeRule, SymbolsPerRegionRule, UndercluedRule, UniqueShapeRule, WrapAroundRule, Serializer, Compressor, CompressorBase, DeflateCompressor, GzipCompressor, StreamCompressor, SerializerBase, SerializerV0, getShapeVariants, normalizeShape, positionsToShape, shapeEquals, tilesToShape, allSolvers, BacktrackSolver, BTModule, BTGridData, BTTile, IntArray2D, colorToBTTile, createOneTileResult, getOppositeColor, BanPatternBTModule, CellCountBTModule, ConnectAllBTModule, RegionAreaBTModule, RegionShapeBTModule, SameShapeBTModule, SymbolsPerRegionBTModule, UniqueShapeBTModule, AreaNumberBTModule, DartBTModule, DirectionLinkerBTModule, GalaxyBTModule, LetterBTModule, LotusBTModule, MinesweeperBTModule, MyopiaBTModule, ViewpointBTModule, EventIteratingSolver, Solver, UniversalSolver, AreaNumberModule, CellCountModule, ConnectAllModule, DartModule, allZ3Modules, LetterModule, MyopiaModule, RegionAreaModule, ViewpointModule, Z3Module, convertDirection, Z3Solver, Z3SolverContext, AreaNumberSymbol, CustomIconSymbol, CustomSymbol, CustomTextSymbol, DartSymbol, DirectionLinkerSymbol, GalaxySymbol, HiddenSymbol, allSymbols, LetterSymbol, LotusSymbol, MinesweeperSymbol, MultiEntrySymbol, MyopiaSymbol, NumberSymbol, Symbol, ViewpointSymbol, TileData, TileConnections, validateGrid, aggregateState, applyFinalOverrides, };
107
+ export { ConfigType, configEquals, Configurable, CachedAccess, allEqual, array, directionToRotation, escape, isSameEdge, maxBy, minBy, move, orientationToRotation, resize, unescape, isEventHandler, handlesFinalValidation, handlesGetTile, handlesGridChange, handlesGridResize, handlesSetGrid, invokeSetGrid, handlesSymbolDisplay, handlesSymbolValidation, GridData, NEIGHBOR_OFFSETS, GridConnections, GridZones, Instruction, COMPARISONS, Color, Comparison, DIRECTIONS, Direction, MajorRule, Mode, ORIENTATIONS, Orientation, State, WRAPPINGS, Wrapping, directionToggle, orientationToggle, MetadataSchema, PuzzleSchema, BanPatternRule, CellCountPerZoneRule, CellCountRule, CompletePatternRule, ConnectAllRule, CustomRule, ForesightRule, allRules, LyingSymbolRule, ControlLine, Row, MusicGridRule, MysteryRule, OffByXRule, PerfectionRule, RegionAreaRule, RegionShapeRule, Rule, SameShapeRule, SymbolsPerRegionRule, UndercluedRule, UniqueShapeRule, WrapAroundRule, Serializer, Compressor, CompressorBase, DeflateCompressor, GzipCompressor, StreamCompressor, SerializerBase, SerializerV0, getShapeVariants, normalizeShape, positionsToShape, shapeEquals, tilesToShape, allSolvers, BacktrackSolver, BTModule, BTGridData, BTTile, IntArray2D, colorToBTTile, createOneTileResult, getOppositeColor, BanPatternBTModule, CellCountBTModule, ConnectAllBTModule, RegionAreaBTModule, RegionShapeBTModule, SameShapeBTModule, SymbolsPerRegionBTModule, UniqueShapeBTModule, AreaNumberBTModule, DartBTModule, DirectionLinkerBTModule, FocusBTModule, GalaxyBTModule, LetterBTModule, LotusBTModule, MinesweeperBTModule, MyopiaBTModule, ViewpointBTModule, EventIteratingSolver, Solver, UniversalSolver, AreaNumberModule, CellCountModule, ConnectAllModule, DartModule, allZ3Modules, LetterModule, MyopiaModule, RegionAreaModule, ViewpointModule, Z3Module, convertDirection, Z3Solver, Z3SolverContext, AreaNumberSymbol, CustomIconSymbol, CustomSymbol, CustomTextSymbol, DartSymbol, DirectionLinkerSymbol, FocusSymbol, GalaxySymbol, HiddenSymbol, allSymbols, LetterSymbol, LotusSymbol, MinesweeperSymbol, MultiEntrySymbol, MyopiaSymbol, NumberSymbol, Symbol, ViewpointSymbol, TileData, TileConnections, validateGrid, aggregateState, applyFinalOverrides, };
package/dist/index.js CHANGED
@@ -63,6 +63,7 @@ import UniqueShapeBTModule from './data/solver/backtrack/rules/uniqueShape.js';
63
63
  import AreaNumberBTModule from './data/solver/backtrack/symbols/areaNumber.js';
64
64
  import DartBTModule from './data/solver/backtrack/symbols/dart.js';
65
65
  import DirectionLinkerBTModule from './data/solver/backtrack/symbols/directionLinker.js';
66
+ import FocusBTModule from './data/solver/backtrack/symbols/focus.js';
66
67
  import GalaxyBTModule from './data/solver/backtrack/symbols/galaxy.js';
67
68
  import LetterBTModule from './data/solver/backtrack/symbols/letter.js';
68
69
  import LotusBTModule from './data/solver/backtrack/symbols/lotus.js';
@@ -91,6 +92,7 @@ import CustomSymbol from './data/symbols/customSymbol.js';
91
92
  import CustomTextSymbol from './data/symbols/customTextSymbol.js';
92
93
  import DartSymbol from './data/symbols/dartSymbol.js';
93
94
  import DirectionLinkerSymbol from './data/symbols/directionLinkerSymbol.js';
95
+ import FocusSymbol from './data/symbols/focusSymbol.js';
94
96
  import GalaxySymbol from './data/symbols/galaxySymbol.js';
95
97
  import HiddenSymbol from './data/symbols/hiddenSymbol.js';
96
98
  import { allSymbols } from './data/symbols/index.js';
@@ -105,4 +107,4 @@ import ViewpointSymbol from './data/symbols/viewpointSymbol.js';
105
107
  import TileData from './data/tile.js';
106
108
  import TileConnections from './data/tileConnections.js';
107
109
  import validateGrid, { aggregateState, applyFinalOverrides } from './data/validate.js';
108
- export { ConfigType, configEquals, Configurable, CachedAccess, allEqual, array, directionToRotation, escape, isSameEdge, maxBy, minBy, move, orientationToRotation, resize, unescape, isEventHandler, handlesFinalValidation, handlesGetTile, handlesGridChange, handlesGridResize, handlesSetGrid, invokeSetGrid, handlesSymbolDisplay, handlesSymbolValidation, GridData, NEIGHBOR_OFFSETS, GridConnections, GridZones, Instruction, COMPARISONS, Color, Comparison, DIRECTIONS, Direction, MajorRule, Mode, ORIENTATIONS, Orientation, State, WRAPPINGS, Wrapping, directionToggle, orientationToggle, MetadataSchema, PuzzleSchema, BanPatternRule, CellCountPerZoneRule, CellCountRule, CompletePatternRule, ConnectAllRule, CustomRule, ForesightRule, allRules, LyingSymbolRule, ControlLine, Row, MusicGridRule, MysteryRule, OffByXRule, PerfectionRule, RegionAreaRule, RegionShapeRule, Rule, SameShapeRule, SymbolsPerRegionRule, UndercluedRule, UniqueShapeRule, WrapAroundRule, Serializer, Compressor, CompressorBase, DeflateCompressor, GzipCompressor, StreamCompressor, SerializerBase, SerializerV0, getShapeVariants, normalizeShape, positionsToShape, shapeEquals, tilesToShape, allSolvers, BacktrackSolver, BTModule, BTGridData, BTTile, IntArray2D, colorToBTTile, createOneTileResult, getOppositeColor, BanPatternBTModule, CellCountBTModule, ConnectAllBTModule, RegionAreaBTModule, RegionShapeBTModule, SameShapeBTModule, SymbolsPerRegionBTModule, UniqueShapeBTModule, AreaNumberBTModule, DartBTModule, DirectionLinkerBTModule, GalaxyBTModule, LetterBTModule, LotusBTModule, MinesweeperBTModule, MyopiaBTModule, ViewpointBTModule, EventIteratingSolver, Solver, UniversalSolver, AreaNumberModule, CellCountModule, ConnectAllModule, DartModule, allZ3Modules, LetterModule, MyopiaModule, RegionAreaModule, ViewpointModule, Z3Module, convertDirection, Z3Solver, Z3SolverContext, AreaNumberSymbol, CustomIconSymbol, CustomSymbol, CustomTextSymbol, DartSymbol, DirectionLinkerSymbol, GalaxySymbol, HiddenSymbol, allSymbols, LetterSymbol, LotusSymbol, MinesweeperSymbol, MultiEntrySymbol, MyopiaSymbol, NumberSymbol, Symbol, ViewpointSymbol, TileData, TileConnections, validateGrid, aggregateState, applyFinalOverrides, };
110
+ export { ConfigType, configEquals, Configurable, CachedAccess, allEqual, array, directionToRotation, escape, isSameEdge, maxBy, minBy, move, orientationToRotation, resize, unescape, isEventHandler, handlesFinalValidation, handlesGetTile, handlesGridChange, handlesGridResize, handlesSetGrid, invokeSetGrid, handlesSymbolDisplay, handlesSymbolValidation, GridData, NEIGHBOR_OFFSETS, GridConnections, GridZones, Instruction, COMPARISONS, Color, Comparison, DIRECTIONS, Direction, MajorRule, Mode, ORIENTATIONS, Orientation, State, WRAPPINGS, Wrapping, directionToggle, orientationToggle, MetadataSchema, PuzzleSchema, BanPatternRule, CellCountPerZoneRule, CellCountRule, CompletePatternRule, ConnectAllRule, CustomRule, ForesightRule, allRules, LyingSymbolRule, ControlLine, Row, MusicGridRule, MysteryRule, OffByXRule, PerfectionRule, RegionAreaRule, RegionShapeRule, Rule, SameShapeRule, SymbolsPerRegionRule, UndercluedRule, UniqueShapeRule, WrapAroundRule, Serializer, Compressor, CompressorBase, DeflateCompressor, GzipCompressor, StreamCompressor, SerializerBase, SerializerV0, getShapeVariants, normalizeShape, positionsToShape, shapeEquals, tilesToShape, allSolvers, BacktrackSolver, BTModule, BTGridData, BTTile, IntArray2D, colorToBTTile, createOneTileResult, getOppositeColor, BanPatternBTModule, CellCountBTModule, ConnectAllBTModule, RegionAreaBTModule, RegionShapeBTModule, SameShapeBTModule, SymbolsPerRegionBTModule, UniqueShapeBTModule, AreaNumberBTModule, DartBTModule, DirectionLinkerBTModule, FocusBTModule, GalaxyBTModule, LetterBTModule, LotusBTModule, MinesweeperBTModule, MyopiaBTModule, ViewpointBTModule, EventIteratingSolver, Solver, UniversalSolver, AreaNumberModule, CellCountModule, ConnectAllModule, DartModule, allZ3Modules, LetterModule, MyopiaModule, RegionAreaModule, ViewpointModule, Z3Module, convertDirection, Z3Solver, Z3SolverContext, AreaNumberSymbol, CustomIconSymbol, CustomSymbol, CustomTextSymbol, DartSymbol, DirectionLinkerSymbol, FocusSymbol, GalaxySymbol, HiddenSymbol, allSymbols, LetterSymbol, LotusSymbol, MinesweeperSymbol, MultiEntrySymbol, MyopiaSymbol, NumberSymbol, Symbol, ViewpointSymbol, TileData, TileConnections, validateGrid, aggregateState, applyFinalOverrides, };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logic-pad/core",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",