@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.
- package/assets/logic-core.global.d.ts +134 -11
- package/dist/data/config.d.ts +6 -2
- package/dist/data/config.js +1 -0
- package/dist/data/events/onGetTile.d.ts +7 -0
- package/dist/data/events/onGetTile.js +4 -0
- package/dist/data/grid.d.ts +9 -3
- package/dist/data/grid.js +50 -10
- package/dist/data/primitives.d.ts +9 -1
- package/dist/data/primitives.js +14 -0
- package/dist/data/rules/banPatternRule.js +11 -6
- package/dist/data/rules/cellCountPerZoneRule.js +9 -7
- package/dist/data/rules/regionShapeRule.js +7 -3
- package/dist/data/rules/rules.gen.d.ts +1 -0
- package/dist/data/rules/rules.gen.js +1 -0
- package/dist/data/rules/wrapAroundRule.d.ts +34 -0
- package/dist/data/rules/wrapAroundRule.js +271 -0
- package/dist/data/serializer/serializer_v0.js +3 -0
- package/dist/data/solver/backtrack/backtrackSolver.d.ts +1 -0
- package/dist/data/solver/backtrack/backtrackSolver.js +8 -0
- package/dist/data/solver/backtrack/backtrackWorker.js +11 -0
- package/dist/data/solver/backtrack/symbols/focus.d.ts +9 -0
- package/dist/data/solver/backtrack/symbols/focus.js +59 -0
- package/dist/data/solver/eventIteratingSolver.d.ts +3 -2
- package/dist/data/solver/eventIteratingSolver.js +13 -2
- package/dist/data/solver/solver.d.ts +11 -6
- package/dist/data/solver/universal/universalSolver.d.ts +1 -0
- package/dist/data/solver/universal/universalSolver.js +6 -0
- package/dist/data/solver/universal/universalWorker.js +5 -0
- package/dist/data/solver/z3/z3Solver.d.ts +2 -0
- package/dist/data/solver/z3/z3Solver.js +12 -0
- package/dist/data/symbols/directionLinkerSymbol.js +15 -9
- package/dist/data/symbols/focusSymbol.d.ts +30 -0
- package/dist/data/symbols/focusSymbol.js +110 -0
- package/dist/data/symbols/minesweeperSymbol.d.ts +1 -1
- package/dist/data/symbols/minesweeperSymbol.js +9 -2
- package/dist/data/symbols/symbols.gen.d.ts +1 -0
- package/dist/data/symbols/symbols.gen.js +1 -0
- package/dist/data/symbols/viewpointSymbol.js +10 -11
- package/dist/index.d.ts +6 -2
- package/dist/index.js +6 -2
- 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
|
-
|
|
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 =
|
|
75
|
-
if (!grid.zones.edges.some(e =>
|
|
76
|
-
|
|
77
|
-
e.x2
|
|
78
|
-
|
|
79
|
-
|
|
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({
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
35
|
-
*
|
|
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,
|
|
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;
|