@logic-pad/core 0.20.0 → 0.22.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 +96 -3
- package/dist/data/grid.d.ts +12 -3
- package/dist/data/grid.js +49 -4
- package/dist/data/rules/cellCountPerZoneRule.js +14 -44
- package/dist/data/rules/connectAllRule.js +3 -4
- package/dist/data/rules/connectZonesRule.d.ts +29 -0
- package/dist/data/rules/connectZonesRule.js +132 -0
- package/dist/data/rules/exactCountPerZoneRule.d.ts +33 -0
- package/dist/data/rules/exactCountPerZoneRule.js +138 -0
- package/dist/data/rules/musicGridRule.js +5 -5
- package/dist/data/rules/rules.gen.d.ts +2 -0
- package/dist/data/rules/rules.gen.js +2 -0
- package/dist/data/validateAsync.d.ts +15 -0
- package/dist/data/validateAsync.js +128 -0
- package/dist/data/validateAsyncWorker.d.ts +1 -0
- package/dist/data/validateAsyncWorker.js +9 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +4 -1
- package/package.json +4 -2
|
@@ -1036,7 +1036,11 @@ declare global {
|
|
|
1036
1036
|
*/
|
|
1037
1037
|
iterateArea<T>(
|
|
1038
1038
|
position: Position$1,
|
|
1039
|
-
predicate: (
|
|
1039
|
+
predicate: (
|
|
1040
|
+
tile: TileData,
|
|
1041
|
+
logicalX: number,
|
|
1042
|
+
logicalY: number
|
|
1043
|
+
) => boolean,
|
|
1040
1044
|
callback: (
|
|
1041
1045
|
tile: TileData,
|
|
1042
1046
|
x: number,
|
|
@@ -1061,7 +1065,11 @@ declare global {
|
|
|
1061
1065
|
iterateDirection<T>(
|
|
1062
1066
|
position: Position$1,
|
|
1063
1067
|
direction: Direction | Orientation,
|
|
1064
|
-
predicate: (
|
|
1068
|
+
predicate: (
|
|
1069
|
+
tile: TileData,
|
|
1070
|
+
logicalX: number,
|
|
1071
|
+
logicalY: number
|
|
1072
|
+
) => boolean,
|
|
1065
1073
|
callback: (
|
|
1066
1074
|
tile: TileData,
|
|
1067
1075
|
x: number,
|
|
@@ -1086,7 +1094,11 @@ declare global {
|
|
|
1086
1094
|
iterateDirectionAll<T>(
|
|
1087
1095
|
position: Position$1,
|
|
1088
1096
|
direction: Direction | Orientation,
|
|
1089
|
-
predicate: (
|
|
1097
|
+
predicate: (
|
|
1098
|
+
tile: TileData,
|
|
1099
|
+
logicalX: number,
|
|
1100
|
+
logicalY: number
|
|
1101
|
+
) => boolean,
|
|
1090
1102
|
callback: (
|
|
1091
1103
|
tile: TileData,
|
|
1092
1104
|
x: number,
|
|
@@ -1096,6 +1108,26 @@ declare global {
|
|
|
1096
1108
|
) => T | undefined,
|
|
1097
1109
|
visited?: boolean[][]
|
|
1098
1110
|
): T | undefined;
|
|
1111
|
+
/**
|
|
1112
|
+
* Reduce the grid by zones defined in the GridZones.
|
|
1113
|
+
*
|
|
1114
|
+
* @param reducer The reducer function to apply to each zone.
|
|
1115
|
+
* @param initializer The initializer function to create the initial value for each zone.
|
|
1116
|
+
* @param visited A 2D array to keep track of visited tiles. This array is modified by the function.
|
|
1117
|
+
* @returns An array of reduced values, one for each zone.
|
|
1118
|
+
*/
|
|
1119
|
+
reduceByZone<T>(
|
|
1120
|
+
reducer: (
|
|
1121
|
+
acc: T,
|
|
1122
|
+
tile: TileData,
|
|
1123
|
+
x: number,
|
|
1124
|
+
y: number,
|
|
1125
|
+
logicalX: number,
|
|
1126
|
+
logicalY: number
|
|
1127
|
+
) => T,
|
|
1128
|
+
initializer: () => T,
|
|
1129
|
+
visited?: boolean[][]
|
|
1130
|
+
): T[];
|
|
1099
1131
|
/**
|
|
1100
1132
|
* Check if every tile in the grid is filled with a color other than gray.
|
|
1101
1133
|
*
|
|
@@ -1628,6 +1660,28 @@ declare global {
|
|
|
1628
1660
|
copyWith({ color }: { color?: Color }): this;
|
|
1629
1661
|
withColor(color: Color): this;
|
|
1630
1662
|
}
|
|
1663
|
+
export declare class CollectZonesRule extends Rule {
|
|
1664
|
+
readonly color: Color;
|
|
1665
|
+
readonly title = 'Connect Zones';
|
|
1666
|
+
private static readonly CONFIGS;
|
|
1667
|
+
private static readonly EXAMPLE_GRID_LIGHT;
|
|
1668
|
+
private static readonly EXAMPLE_GRID_DARK;
|
|
1669
|
+
private static readonly SEARCH_VARIANTS;
|
|
1670
|
+
/**
|
|
1671
|
+
* **Connect all <color> cells in each zone**
|
|
1672
|
+
*
|
|
1673
|
+
* @param color - The color of the cells to connect.
|
|
1674
|
+
*/
|
|
1675
|
+
constructor(color: Color);
|
|
1676
|
+
get id(): string;
|
|
1677
|
+
get explanation(): string;
|
|
1678
|
+
get configs(): readonly AnyConfig[] | null;
|
|
1679
|
+
createExampleGrid(): GridData;
|
|
1680
|
+
get searchVariants(): SearchVariant[];
|
|
1681
|
+
validateGrid(grid: GridData): RuleState;
|
|
1682
|
+
copyWith({ color }: { color?: Color }): this;
|
|
1683
|
+
withColor(color: Color): this;
|
|
1684
|
+
}
|
|
1631
1685
|
export type ShapeRegions = {
|
|
1632
1686
|
regions: {
|
|
1633
1687
|
positions: Position$1[];
|
|
@@ -1723,6 +1777,30 @@ declare global {
|
|
|
1723
1777
|
validateGrid(grid: GridData): RuleState;
|
|
1724
1778
|
copyWith({ color }: { color?: Color }): this;
|
|
1725
1779
|
}
|
|
1780
|
+
export declare class ExactCountPerZoneRule extends CellCountPerZoneRule {
|
|
1781
|
+
readonly color: Color;
|
|
1782
|
+
readonly count: number;
|
|
1783
|
+
readonly title = 'Exact Count Per Zone';
|
|
1784
|
+
private static readonly CONFIGS;
|
|
1785
|
+
private static readonly EXAMPLE_GRID_LIGHT;
|
|
1786
|
+
private static readonly EXAMPLE_GRID_DARK;
|
|
1787
|
+
private static readonly EXAMPLE_GRID_GRAY;
|
|
1788
|
+
private static readonly SEARCH_VARIANTS;
|
|
1789
|
+
/**
|
|
1790
|
+
* **Each zone has <count> <color> cells.**
|
|
1791
|
+
*
|
|
1792
|
+
* @param color - The color of the cells to count.
|
|
1793
|
+
* @param count - The exact count of the cells in each zone.
|
|
1794
|
+
*/
|
|
1795
|
+
constructor(color: Color, count: number);
|
|
1796
|
+
get id(): string;
|
|
1797
|
+
get explanation(): string;
|
|
1798
|
+
get configs(): readonly AnyConfig[] | null;
|
|
1799
|
+
createExampleGrid(): GridData;
|
|
1800
|
+
get searchVariants(): SearchVariant[];
|
|
1801
|
+
validateGrid(grid: GridData): RuleState;
|
|
1802
|
+
copyWith({ color, count }: { color?: Color; count?: number }): this;
|
|
1803
|
+
}
|
|
1726
1804
|
export declare class ForesightRule extends Rule {
|
|
1727
1805
|
readonly count: number;
|
|
1728
1806
|
readonly regenInterval: number;
|
|
@@ -3153,6 +3231,21 @@ declare global {
|
|
|
3153
3231
|
grid: GridData,
|
|
3154
3232
|
solution: GridData | null
|
|
3155
3233
|
): GridState;
|
|
3234
|
+
export declare class GridValidator {
|
|
3235
|
+
private worker;
|
|
3236
|
+
private stateListeners;
|
|
3237
|
+
private loadListeners;
|
|
3238
|
+
private readonly validateGridDebounced;
|
|
3239
|
+
readonly validateGrid: (grid: GridData, solution: GridData | null) => void;
|
|
3240
|
+
private readonly notifyState;
|
|
3241
|
+
readonly subscribeToState: (
|
|
3242
|
+
listener: (state: GridState) => void
|
|
3243
|
+
) => () => void;
|
|
3244
|
+
private readonly notifyLoad;
|
|
3245
|
+
readonly subscribeToLoad: (listener: () => void) => () => void;
|
|
3246
|
+
readonly isLoading: () => boolean;
|
|
3247
|
+
readonly delete: () => void;
|
|
3248
|
+
}
|
|
3156
3249
|
|
|
3157
3250
|
export { Symbol$1 as Symbol, escape$1 as escape, unescape$1 as unescape };
|
|
3158
3251
|
|
package/dist/data/grid.d.ts
CHANGED
|
@@ -245,7 +245,7 @@ export default class GridData {
|
|
|
245
245
|
* @param visited A 2D array to keep track of visited tiles. This array is modified by the function.
|
|
246
246
|
* @returns The value returned by the callback that stopped the iteration, or undefined if the iteration completed.
|
|
247
247
|
*/
|
|
248
|
-
iterateArea<T>(position: Position, predicate: (tile: TileData) => boolean, callback: (tile: TileData, x: number, y: number, logicalX: number, logicalY: number) => undefined | T, visited?: boolean[][]): T | undefined;
|
|
248
|
+
iterateArea<T>(position: Position, predicate: (tile: TileData, logicalX: number, logicalY: number) => boolean, callback: (tile: TileData, x: number, y: number, logicalX: number, logicalY: number) => undefined | T, visited?: boolean[][]): T | undefined;
|
|
249
249
|
/**
|
|
250
250
|
* Iterate over all tiles in a straight line from the given position in the given direction that satisfy the predicate.
|
|
251
251
|
* The iteration stops when the callback returns a value that is not undefined.
|
|
@@ -258,7 +258,7 @@ export default class GridData {
|
|
|
258
258
|
* @param visited A 2D array to keep track of visited tiles. This array is modified by the function.
|
|
259
259
|
* @returns The value returned by the callback that stopped the iteration, or undefined if the iteration completed.
|
|
260
260
|
*/
|
|
261
|
-
iterateDirection<T>(position: Position, direction: Direction | Orientation, predicate: (tile: TileData) => boolean, callback: (tile: TileData, x: number, y: number, logicalX: number, logicalY: number) => T | undefined, visited?: boolean[][]): T | undefined;
|
|
261
|
+
iterateDirection<T>(position: Position, direction: Direction | Orientation, predicate: (tile: TileData, logicalX: number, logicalY: number) => boolean, callback: (tile: TileData, x: number, y: number, logicalX: number, logicalY: number) => T | undefined, visited?: boolean[][]): T | undefined;
|
|
262
262
|
/**
|
|
263
263
|
* Iterate over all tiles in a straight line from the given position in the given direction that satisfy the predicate.
|
|
264
264
|
* The iteration stops when the callback returns a value that is not undefined.
|
|
@@ -271,7 +271,16 @@ export default class GridData {
|
|
|
271
271
|
* @param visited A 2D array to keep track of visited tiles. This array is modified by the function.
|
|
272
272
|
* @returns The value returned by the callback that stopped the iteration, or undefined if the iteration completed.
|
|
273
273
|
*/
|
|
274
|
-
iterateDirectionAll<T>(position: Position, direction: Direction | Orientation, predicate: (tile: TileData) => boolean, callback: (tile: TileData, x: number, y: number, logicalX: number, logicalY: number) => T | undefined, visited?: boolean[][]): T | undefined;
|
|
274
|
+
iterateDirectionAll<T>(position: Position, direction: Direction | Orientation, predicate: (tile: TileData, logicalX: number, logicalY: number) => boolean, callback: (tile: TileData, x: number, y: number, logicalX: number, logicalY: number) => T | undefined, visited?: boolean[][]): T | undefined;
|
|
275
|
+
/**
|
|
276
|
+
* Reduce the grid by zones defined in the GridZones.
|
|
277
|
+
*
|
|
278
|
+
* @param reducer The reducer function to apply to each zone.
|
|
279
|
+
* @param initializer The initializer function to create the initial value for each zone.
|
|
280
|
+
* @param visited A 2D array to keep track of visited tiles. This array is modified by the function.
|
|
281
|
+
* @returns An array of reduced values, one for each zone.
|
|
282
|
+
*/
|
|
283
|
+
reduceByZone<T>(reducer: (acc: T, tile: TileData, x: number, y: number, logicalX: number, logicalY: number) => T, initializer: () => T, visited?: boolean[][]): T[];
|
|
275
284
|
/**
|
|
276
285
|
* Check if every tile in the grid is filled with a color other than gray.
|
|
277
286
|
*
|
package/dist/data/grid.js
CHANGED
|
@@ -650,7 +650,7 @@ export default class GridData {
|
|
|
650
650
|
*/
|
|
651
651
|
iterateArea(position, predicate, callback, visited = array(this.width, this.height, () => false)) {
|
|
652
652
|
const tile = this.getTile(position.x, position.y);
|
|
653
|
-
if (!tile.exists || !predicate(tile)) {
|
|
653
|
+
if (!tile.exists || !predicate(tile, position.x, position.y)) {
|
|
654
654
|
return;
|
|
655
655
|
}
|
|
656
656
|
const stack = [position];
|
|
@@ -668,7 +668,7 @@ export default class GridData {
|
|
|
668
668
|
const next = { x: x + offset.x, y: y + offset.y };
|
|
669
669
|
if (this.isPositionValid(next.x, next.y)) {
|
|
670
670
|
const nextTile = this.getTile(next.x, next.y);
|
|
671
|
-
if (nextTile.exists && predicate(nextTile))
|
|
671
|
+
if (nextTile.exists && predicate(nextTile, next.x, next.y))
|
|
672
672
|
stack.push(next);
|
|
673
673
|
}
|
|
674
674
|
}
|
|
@@ -687,7 +687,7 @@ export default class GridData {
|
|
|
687
687
|
* @returns The value returned by the callback that stopped the iteration, or undefined if the iteration completed.
|
|
688
688
|
*/
|
|
689
689
|
iterateDirection(position, direction, predicate, callback, visited = array(this.width, this.height, () => false)) {
|
|
690
|
-
return this.iterateDirectionAll(position, direction, tile => tile.exists && predicate(tile), callback, visited);
|
|
690
|
+
return this.iterateDirectionAll(position, direction, (tile, logicalX, logicalY) => tile.exists && predicate(tile, logicalX, logicalY), callback, visited);
|
|
691
691
|
}
|
|
692
692
|
/**
|
|
693
693
|
* Iterate over all tiles in a straight line from the given position in the given direction that satisfy the predicate.
|
|
@@ -710,7 +710,7 @@ export default class GridData {
|
|
|
710
710
|
}
|
|
711
711
|
visited[arrPos.y][arrPos.x] = true;
|
|
712
712
|
const tile = this.getTile(current.x, current.y);
|
|
713
|
-
if (!predicate(tile)) {
|
|
713
|
+
if (!predicate(tile, arrPos.x, arrPos.y)) {
|
|
714
714
|
break;
|
|
715
715
|
}
|
|
716
716
|
const ret = callback(tile, arrPos.x, arrPos.y, current.x, current.y);
|
|
@@ -719,6 +719,51 @@ export default class GridData {
|
|
|
719
719
|
current = move(current, direction);
|
|
720
720
|
}
|
|
721
721
|
}
|
|
722
|
+
/**
|
|
723
|
+
* Reduce the grid by zones defined in the GridZones.
|
|
724
|
+
*
|
|
725
|
+
* @param reducer The reducer function to apply to each zone.
|
|
726
|
+
* @param initializer The initializer function to create the initial value for each zone.
|
|
727
|
+
* @param visited A 2D array to keep track of visited tiles. This array is modified by the function.
|
|
728
|
+
* @returns An array of reduced values, one for each zone.
|
|
729
|
+
*/
|
|
730
|
+
reduceByZone(reducer, initializer, visited = array(this.width, this.height, () => false)) {
|
|
731
|
+
const zones = [];
|
|
732
|
+
while (true) {
|
|
733
|
+
const seed = this.find((tile, x, y) => tile.exists && !visited[y][x]);
|
|
734
|
+
if (!seed)
|
|
735
|
+
break;
|
|
736
|
+
let zone = initializer();
|
|
737
|
+
const stack = [seed];
|
|
738
|
+
while (stack.length > 0) {
|
|
739
|
+
const { x, y } = stack.pop();
|
|
740
|
+
const { x: arrX, y: arrY } = this.toArrayCoordinates(x, y);
|
|
741
|
+
if (visited[arrY][arrX])
|
|
742
|
+
continue;
|
|
743
|
+
visited[arrY][arrX] = true;
|
|
744
|
+
zone = reducer(zone, this.getTile(arrX, arrY), arrX, arrY, x, y);
|
|
745
|
+
for (const offset of NEIGHBOR_OFFSETS) {
|
|
746
|
+
const next = this.toArrayCoordinates(x + offset.x, y + offset.y);
|
|
747
|
+
if (!this.zones.edges.some(e => {
|
|
748
|
+
const { x: x1, y: y1 } = this.toArrayCoordinates(e.x1, e.y1);
|
|
749
|
+
const { x: x2, y: y2 } = this.toArrayCoordinates(e.x2, e.y2);
|
|
750
|
+
return ((x1 === arrX &&
|
|
751
|
+
y1 === arrY &&
|
|
752
|
+
x2 === next.x &&
|
|
753
|
+
y2 === next.y) ||
|
|
754
|
+
(x2 === arrX && y2 === arrY && x1 === next.x && y1 === next.y));
|
|
755
|
+
})) {
|
|
756
|
+
const nextTile = this.getTile(next.x, next.y);
|
|
757
|
+
if (nextTile.exists) {
|
|
758
|
+
stack.push(next);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
zones.push(zone);
|
|
764
|
+
}
|
|
765
|
+
return zones;
|
|
766
|
+
}
|
|
722
767
|
/**
|
|
723
768
|
* Check if every tile in the grid is filled with a color other than gray.
|
|
724
769
|
*
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { array } from '../dataHelper.js';
|
|
2
|
-
import { NEIGHBOR_OFFSETS } from '../grid.js';
|
|
3
1
|
import { Color } from '../primitives.js';
|
|
4
2
|
import Rule from './rule.js';
|
|
5
3
|
export default class CellCountPerZoneRule extends Rule {
|
|
@@ -21,49 +19,21 @@ export default class CellCountPerZoneRule extends Rule {
|
|
|
21
19
|
}
|
|
22
20
|
getZoneCounts(grid) {
|
|
23
21
|
let complete = true;
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (!seed)
|
|
29
|
-
break;
|
|
30
|
-
const zone = {
|
|
31
|
-
positions: [],
|
|
32
|
-
completed: 0,
|
|
33
|
-
possible: 0,
|
|
34
|
-
};
|
|
35
|
-
const stack = [seed];
|
|
36
|
-
while (stack.length > 0) {
|
|
37
|
-
let { x, y } = stack.pop();
|
|
38
|
-
({ x, y } = grid.toArrayCoordinates(x, y));
|
|
39
|
-
if (visited[y][x])
|
|
40
|
-
continue;
|
|
41
|
-
visited[y][x] = true;
|
|
42
|
-
zone.positions.push({ x, y });
|
|
43
|
-
if (grid.getTile(x, y).color === this.color) {
|
|
44
|
-
zone.completed++;
|
|
45
|
-
}
|
|
46
|
-
else if (grid.getTile(x, y).color === Color.Gray) {
|
|
47
|
-
zone.possible++;
|
|
48
|
-
complete = false;
|
|
49
|
-
}
|
|
50
|
-
for (const offset of NEIGHBOR_OFFSETS) {
|
|
51
|
-
const next = grid.toArrayCoordinates(x + offset.x, y + offset.y);
|
|
52
|
-
if (!grid.zones.edges.some(e => {
|
|
53
|
-
const { x: x1, y: y1 } = grid.toArrayCoordinates(e.x1, e.y1);
|
|
54
|
-
const { x: x2, y: y2 } = grid.toArrayCoordinates(e.x2, e.y2);
|
|
55
|
-
return ((x1 === x && y1 === y && x2 === next.x && y2 === next.y) ||
|
|
56
|
-
(x2 === x && y2 === y && x1 === next.x && y1 === next.y));
|
|
57
|
-
})) {
|
|
58
|
-
const nextTile = grid.getTile(next.x, next.y);
|
|
59
|
-
if (nextTile.exists) {
|
|
60
|
-
stack.push(next);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
22
|
+
const zones = grid.reduceByZone((zone, tile, x, y) => {
|
|
23
|
+
zone.positions.push({ x, y });
|
|
24
|
+
if (tile.color === this.color) {
|
|
25
|
+
zone.completed++;
|
|
64
26
|
}
|
|
65
|
-
|
|
66
|
-
|
|
27
|
+
else if (tile.color === Color.Gray) {
|
|
28
|
+
zone.possible++;
|
|
29
|
+
complete = false;
|
|
30
|
+
}
|
|
31
|
+
return zone;
|
|
32
|
+
}, () => ({
|
|
33
|
+
positions: [],
|
|
34
|
+
completed: 0,
|
|
35
|
+
possible: 0,
|
|
36
|
+
}));
|
|
67
37
|
return { zones, complete };
|
|
68
38
|
}
|
|
69
39
|
withColor(color) {
|
|
@@ -44,19 +44,18 @@ class ConnectAllRule extends Rule {
|
|
|
44
44
|
}
|
|
45
45
|
validateGrid(grid) {
|
|
46
46
|
let complete = true;
|
|
47
|
-
const visited = array(grid.width, grid.height, (i, j) => !
|
|
47
|
+
const visited = array(grid.width, grid.height, (i, j) => !grid.getTile(i, j).exists);
|
|
48
48
|
const islands = [];
|
|
49
49
|
while (true) {
|
|
50
|
-
const seed = grid.find((
|
|
50
|
+
const seed = grid.find((tile, x, y) => !visited[y][x] && tile.color === this.color);
|
|
51
51
|
if (!seed)
|
|
52
52
|
break;
|
|
53
53
|
const positions = [];
|
|
54
54
|
grid.iterateArea(seed, tile => tile.color === this.color || tile.color === Color.Gray, (tile, x, y) => {
|
|
55
|
-
visited[y][x] = true;
|
|
56
55
|
if (tile.color === Color.Gray)
|
|
57
56
|
complete = false;
|
|
58
57
|
positions.push({ x, y });
|
|
59
|
-
});
|
|
58
|
+
}, visited);
|
|
60
59
|
islands.push(positions);
|
|
61
60
|
}
|
|
62
61
|
if (islands.length > 1) {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { AnyConfig } from '../config.js';
|
|
2
|
+
import GridData from '../grid.js';
|
|
3
|
+
import { Color, RuleState } from '../primitives.js';
|
|
4
|
+
import Rule, { SearchVariant } from './rule.js';
|
|
5
|
+
export default class CollectZonesRule extends Rule {
|
|
6
|
+
readonly color: Color;
|
|
7
|
+
readonly title = "Connect Zones";
|
|
8
|
+
private static readonly CONFIGS;
|
|
9
|
+
private static readonly EXAMPLE_GRID_LIGHT;
|
|
10
|
+
private static readonly EXAMPLE_GRID_DARK;
|
|
11
|
+
private static readonly SEARCH_VARIANTS;
|
|
12
|
+
/**
|
|
13
|
+
* **Connect all <color> cells in each zone**
|
|
14
|
+
*
|
|
15
|
+
* @param color - The color of the cells to connect.
|
|
16
|
+
*/
|
|
17
|
+
constructor(color: Color);
|
|
18
|
+
get id(): string;
|
|
19
|
+
get explanation(): string;
|
|
20
|
+
get configs(): readonly AnyConfig[] | null;
|
|
21
|
+
createExampleGrid(): GridData;
|
|
22
|
+
get searchVariants(): SearchVariant[];
|
|
23
|
+
validateGrid(grid: GridData): RuleState;
|
|
24
|
+
copyWith({ color }: {
|
|
25
|
+
color?: Color;
|
|
26
|
+
}): this;
|
|
27
|
+
withColor(color: Color): this;
|
|
28
|
+
}
|
|
29
|
+
export declare const instance: CollectZonesRule;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { ConfigType } from '../config.js';
|
|
2
|
+
import GridData from '../grid.js';
|
|
3
|
+
import { array, minBy } from '../dataHelper.js';
|
|
4
|
+
import { Color, State } from '../primitives.js';
|
|
5
|
+
import Rule from './rule.js';
|
|
6
|
+
class CollectZonesRule extends Rule {
|
|
7
|
+
/**
|
|
8
|
+
* **Connect all <color> cells in each zone**
|
|
9
|
+
*
|
|
10
|
+
* @param color - The color of the cells to connect.
|
|
11
|
+
*/
|
|
12
|
+
constructor(color) {
|
|
13
|
+
super();
|
|
14
|
+
Object.defineProperty(this, "color", {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
configurable: true,
|
|
17
|
+
writable: true,
|
|
18
|
+
value: color
|
|
19
|
+
});
|
|
20
|
+
Object.defineProperty(this, "title", {
|
|
21
|
+
enumerable: true,
|
|
22
|
+
configurable: true,
|
|
23
|
+
writable: true,
|
|
24
|
+
value: 'Connect Zones'
|
|
25
|
+
});
|
|
26
|
+
this.color = color;
|
|
27
|
+
}
|
|
28
|
+
get id() {
|
|
29
|
+
return `connect_zones`;
|
|
30
|
+
}
|
|
31
|
+
get explanation() {
|
|
32
|
+
return `Connect all ${this.color} cells in each zone`;
|
|
33
|
+
}
|
|
34
|
+
get configs() {
|
|
35
|
+
return CollectZonesRule.CONFIGS;
|
|
36
|
+
}
|
|
37
|
+
createExampleGrid() {
|
|
38
|
+
return this.color === Color.Light
|
|
39
|
+
? CollectZonesRule.EXAMPLE_GRID_LIGHT
|
|
40
|
+
: CollectZonesRule.EXAMPLE_GRID_DARK;
|
|
41
|
+
}
|
|
42
|
+
get searchVariants() {
|
|
43
|
+
return CollectZonesRule.SEARCH_VARIANTS;
|
|
44
|
+
}
|
|
45
|
+
validateGrid(grid) {
|
|
46
|
+
let complete = true;
|
|
47
|
+
let zoneId = 1;
|
|
48
|
+
const zoneMap = array(grid.width, grid.height, () => 0);
|
|
49
|
+
const visited = array(grid.width, grid.height, (i, j) => !grid.getTile(i, j).exists);
|
|
50
|
+
const zones = grid.reduceByZone((zone, tile, x, y) => {
|
|
51
|
+
zoneMap[y][x] = zone;
|
|
52
|
+
if (tile.exists && tile.color === Color.Gray) {
|
|
53
|
+
complete = false;
|
|
54
|
+
}
|
|
55
|
+
return zone;
|
|
56
|
+
}, () => zoneId++);
|
|
57
|
+
for (const zone of zones) {
|
|
58
|
+
const islands = [];
|
|
59
|
+
while (true) {
|
|
60
|
+
const seed = grid.find((tile, x, y) => !visited[y][x] &&
|
|
61
|
+
zoneMap[y][x] === zone &&
|
|
62
|
+
tile.color === this.color);
|
|
63
|
+
if (!seed)
|
|
64
|
+
break;
|
|
65
|
+
const positions = [];
|
|
66
|
+
grid.iterateArea(seed, (tile, x, y) => {
|
|
67
|
+
if (tile.color !== this.color && tile.color !== Color.Gray) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
const pos = grid.toArrayCoordinates(x, y);
|
|
71
|
+
return zoneMap[pos.y][pos.x] === zone;
|
|
72
|
+
}, (tile, x, y) => {
|
|
73
|
+
if (tile.color === Color.Gray)
|
|
74
|
+
complete = false;
|
|
75
|
+
positions.push({ x, y });
|
|
76
|
+
}, visited);
|
|
77
|
+
islands.push(positions);
|
|
78
|
+
}
|
|
79
|
+
if (islands.length > 1) {
|
|
80
|
+
return {
|
|
81
|
+
state: State.Error,
|
|
82
|
+
positions: minBy(islands, island => island.length),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return { state: complete ? State.Satisfied : State.Incomplete };
|
|
87
|
+
}
|
|
88
|
+
copyWith({ color }) {
|
|
89
|
+
return new CollectZonesRule(color ?? this.color);
|
|
90
|
+
}
|
|
91
|
+
withColor(color) {
|
|
92
|
+
return this.copyWith({ color });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
Object.defineProperty(CollectZonesRule, "CONFIGS", {
|
|
96
|
+
enumerable: true,
|
|
97
|
+
configurable: true,
|
|
98
|
+
writable: true,
|
|
99
|
+
value: Object.freeze([
|
|
100
|
+
{
|
|
101
|
+
type: ConfigType.Color,
|
|
102
|
+
default: Color.Light,
|
|
103
|
+
allowGray: false,
|
|
104
|
+
field: 'color',
|
|
105
|
+
description: 'Color',
|
|
106
|
+
configurable: true,
|
|
107
|
+
},
|
|
108
|
+
])
|
|
109
|
+
});
|
|
110
|
+
Object.defineProperty(CollectZonesRule, "EXAMPLE_GRID_LIGHT", {
|
|
111
|
+
enumerable: true,
|
|
112
|
+
configurable: true,
|
|
113
|
+
writable: true,
|
|
114
|
+
value: Object.freeze(GridData.create(['bwwwb', 'bwbww', 'wwwbb', 'wbwww']))
|
|
115
|
+
});
|
|
116
|
+
Object.defineProperty(CollectZonesRule, "EXAMPLE_GRID_DARK", {
|
|
117
|
+
enumerable: true,
|
|
118
|
+
configurable: true,
|
|
119
|
+
writable: true,
|
|
120
|
+
value: Object.freeze(GridData.create(['wbbbw', 'wbwbb', 'bbbww', 'bwbbb']))
|
|
121
|
+
});
|
|
122
|
+
Object.defineProperty(CollectZonesRule, "SEARCH_VARIANTS", {
|
|
123
|
+
enumerable: true,
|
|
124
|
+
configurable: true,
|
|
125
|
+
writable: true,
|
|
126
|
+
value: [
|
|
127
|
+
new CollectZonesRule(Color.Light).searchVariant(),
|
|
128
|
+
new CollectZonesRule(Color.Dark).searchVariant(),
|
|
129
|
+
]
|
|
130
|
+
});
|
|
131
|
+
export default CollectZonesRule;
|
|
132
|
+
export const instance = new CollectZonesRule(Color.Dark);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { AnyConfig } from '../config.js';
|
|
2
|
+
import GridData from '../grid.js';
|
|
3
|
+
import { Color, RuleState } from '../primitives.js';
|
|
4
|
+
import CellCountPerZoneRule from './cellCountPerZoneRule.js';
|
|
5
|
+
import { SearchVariant } from './rule.js';
|
|
6
|
+
export default class ExactCountPerZoneRule extends CellCountPerZoneRule {
|
|
7
|
+
readonly color: Color;
|
|
8
|
+
readonly count: number;
|
|
9
|
+
readonly title = "Exact Count Per Zone";
|
|
10
|
+
private static readonly CONFIGS;
|
|
11
|
+
private static readonly EXAMPLE_GRID_LIGHT;
|
|
12
|
+
private static readonly EXAMPLE_GRID_DARK;
|
|
13
|
+
private static readonly EXAMPLE_GRID_GRAY;
|
|
14
|
+
private static readonly SEARCH_VARIANTS;
|
|
15
|
+
/**
|
|
16
|
+
* **Each zone has <count> <color> cells.**
|
|
17
|
+
*
|
|
18
|
+
* @param color - The color of the cells to count.
|
|
19
|
+
* @param count - The exact count of the cells in each zone.
|
|
20
|
+
*/
|
|
21
|
+
constructor(color: Color, count: number);
|
|
22
|
+
get id(): string;
|
|
23
|
+
get explanation(): string;
|
|
24
|
+
get configs(): readonly AnyConfig[] | null;
|
|
25
|
+
createExampleGrid(): GridData;
|
|
26
|
+
get searchVariants(): SearchVariant[];
|
|
27
|
+
validateGrid(grid: GridData): RuleState;
|
|
28
|
+
copyWith({ color, count }: {
|
|
29
|
+
color?: Color;
|
|
30
|
+
count?: number;
|
|
31
|
+
}): this;
|
|
32
|
+
}
|
|
33
|
+
export declare const instance: ExactCountPerZoneRule;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { ConfigType } from '../config.js';
|
|
2
|
+
import GridData from '../grid.js';
|
|
3
|
+
import GridZones from '../gridZones.js';
|
|
4
|
+
import { Color, State } from '../primitives.js';
|
|
5
|
+
import CellCountPerZoneRule from './cellCountPerZoneRule.js';
|
|
6
|
+
class ExactCountPerZoneRule extends CellCountPerZoneRule {
|
|
7
|
+
/**
|
|
8
|
+
* **Each zone has <count> <color> cells.**
|
|
9
|
+
*
|
|
10
|
+
* @param color - The color of the cells to count.
|
|
11
|
+
* @param count - The exact count of the cells in each zone.
|
|
12
|
+
*/
|
|
13
|
+
constructor(color, count) {
|
|
14
|
+
super(color);
|
|
15
|
+
Object.defineProperty(this, "color", {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
configurable: true,
|
|
18
|
+
writable: true,
|
|
19
|
+
value: color
|
|
20
|
+
});
|
|
21
|
+
Object.defineProperty(this, "count", {
|
|
22
|
+
enumerable: true,
|
|
23
|
+
configurable: true,
|
|
24
|
+
writable: true,
|
|
25
|
+
value: count
|
|
26
|
+
});
|
|
27
|
+
Object.defineProperty(this, "title", {
|
|
28
|
+
enumerable: true,
|
|
29
|
+
configurable: true,
|
|
30
|
+
writable: true,
|
|
31
|
+
value: 'Exact Count Per Zone'
|
|
32
|
+
});
|
|
33
|
+
this.count = count;
|
|
34
|
+
}
|
|
35
|
+
get id() {
|
|
36
|
+
return `zone_exact_count`;
|
|
37
|
+
}
|
|
38
|
+
get explanation() {
|
|
39
|
+
return `Each zone has exactly ${this.count} ${this.color} cell${this.count === 1 ? '' : 's'}`;
|
|
40
|
+
}
|
|
41
|
+
get configs() {
|
|
42
|
+
return ExactCountPerZoneRule.CONFIGS;
|
|
43
|
+
}
|
|
44
|
+
createExampleGrid() {
|
|
45
|
+
if (this.color === Color.Light) {
|
|
46
|
+
return ExactCountPerZoneRule.EXAMPLE_GRID_LIGHT;
|
|
47
|
+
}
|
|
48
|
+
else if (this.color === Color.Dark) {
|
|
49
|
+
return ExactCountPerZoneRule.EXAMPLE_GRID_DARK;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
return ExactCountPerZoneRule.EXAMPLE_GRID_GRAY;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
get searchVariants() {
|
|
56
|
+
return ExactCountPerZoneRule.SEARCH_VARIANTS;
|
|
57
|
+
}
|
|
58
|
+
validateGrid(grid) {
|
|
59
|
+
const { zones, complete } = this.getZoneCounts(grid);
|
|
60
|
+
const errorZone = zones.find(z => z.completed > this.count || z.completed + z.possible < this.count);
|
|
61
|
+
if (errorZone) {
|
|
62
|
+
return {
|
|
63
|
+
state: State.Error,
|
|
64
|
+
positions: errorZone.positions,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
return { state: complete ? State.Satisfied : State.Incomplete };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
copyWith({ color, count }) {
|
|
72
|
+
return new ExactCountPerZoneRule(color ?? this.color, count ?? this.count);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
Object.defineProperty(ExactCountPerZoneRule, "CONFIGS", {
|
|
76
|
+
enumerable: true,
|
|
77
|
+
configurable: true,
|
|
78
|
+
writable: true,
|
|
79
|
+
value: Object.freeze([
|
|
80
|
+
{
|
|
81
|
+
type: ConfigType.Color,
|
|
82
|
+
default: Color.Light,
|
|
83
|
+
allowGray: true,
|
|
84
|
+
field: 'color',
|
|
85
|
+
description: 'Color',
|
|
86
|
+
configurable: true,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
type: ConfigType.Number,
|
|
90
|
+
default: 1,
|
|
91
|
+
min: 0,
|
|
92
|
+
field: 'count',
|
|
93
|
+
description: 'Count',
|
|
94
|
+
configurable: true,
|
|
95
|
+
},
|
|
96
|
+
])
|
|
97
|
+
});
|
|
98
|
+
Object.defineProperty(ExactCountPerZoneRule, "EXAMPLE_GRID_LIGHT", {
|
|
99
|
+
enumerable: true,
|
|
100
|
+
configurable: true,
|
|
101
|
+
writable: true,
|
|
102
|
+
value: Object.freeze(GridData.create(['wbbbb', 'bbbwb', 'bbbwb', 'bwbbb'])
|
|
103
|
+
.withZones(new GridZones([
|
|
104
|
+
{ x1: 0, y1: 1, x2: 0, y2: 2 },
|
|
105
|
+
{ x1: 1, y1: 1, x2: 1, y2: 2 },
|
|
106
|
+
{ x1: 2, y1: 1, x2: 2, y2: 2 },
|
|
107
|
+
{ x1: 3, y1: 1, x2: 3, y2: 2 },
|
|
108
|
+
{ x1: 4, y1: 1, x2: 4, y2: 2 },
|
|
109
|
+
{ x1: 1, y1: 0, x2: 2, y2: 0 },
|
|
110
|
+
{ x1: 1, y1: 1, x2: 2, y2: 1 },
|
|
111
|
+
{ x1: 2, y1: 2, x2: 3, y2: 2 },
|
|
112
|
+
{ x1: 2, y1: 3, x2: 3, y2: 3 },
|
|
113
|
+
]))
|
|
114
|
+
.addRule(new ExactCountPerZoneRule(Color.Light, 1)))
|
|
115
|
+
});
|
|
116
|
+
Object.defineProperty(ExactCountPerZoneRule, "EXAMPLE_GRID_DARK", {
|
|
117
|
+
enumerable: true,
|
|
118
|
+
configurable: true,
|
|
119
|
+
writable: true,
|
|
120
|
+
value: Object.freeze(ExactCountPerZoneRule.EXAMPLE_GRID_LIGHT.withTiles(tiles => tiles.map(row => row.map(tile => tile.withColor(tile.color === Color.Dark ? Color.Light : Color.Dark)))))
|
|
121
|
+
});
|
|
122
|
+
Object.defineProperty(ExactCountPerZoneRule, "EXAMPLE_GRID_GRAY", {
|
|
123
|
+
enumerable: true,
|
|
124
|
+
configurable: true,
|
|
125
|
+
writable: true,
|
|
126
|
+
value: Object.freeze(ExactCountPerZoneRule.EXAMPLE_GRID_LIGHT.withTiles(tiles => tiles.map(row => row.map(tile => tile.withColor(tile.color === Color.Light ? Color.Gray : tile.color)))))
|
|
127
|
+
});
|
|
128
|
+
Object.defineProperty(ExactCountPerZoneRule, "SEARCH_VARIANTS", {
|
|
129
|
+
enumerable: true,
|
|
130
|
+
configurable: true,
|
|
131
|
+
writable: true,
|
|
132
|
+
value: [
|
|
133
|
+
new ExactCountPerZoneRule(Color.Light, 1).searchVariant(),
|
|
134
|
+
new ExactCountPerZoneRule(Color.Dark, 1).searchVariant(),
|
|
135
|
+
]
|
|
136
|
+
});
|
|
137
|
+
export default ExactCountPerZoneRule;
|
|
138
|
+
export const instance = new ExactCountPerZoneRule(Color.Light, 1);
|
|
@@ -5,7 +5,7 @@ import { Color, Instrument, MajorRule, State, } from '../primitives.js';
|
|
|
5
5
|
import CustomIconSymbol from '../symbols/customIconSymbol.js';
|
|
6
6
|
import { ControlLine, Row } from './musicControlLine.js';
|
|
7
7
|
import Rule from './rule.js';
|
|
8
|
-
const
|
|
8
|
+
const DEFAULT_SCALE = [
|
|
9
9
|
new Row('C5', Instrument.Piano, null),
|
|
10
10
|
new Row('B4', Instrument.Piano, null),
|
|
11
11
|
new Row('A4', Instrument.Piano, null),
|
|
@@ -204,7 +204,7 @@ Object.defineProperty(MusicGridRule, "CONFIGS", {
|
|
|
204
204
|
value: Object.freeze([
|
|
205
205
|
{
|
|
206
206
|
type: ConfigType.ControlLines,
|
|
207
|
-
default: [new ControlLine(0, 120, false, false,
|
|
207
|
+
default: [new ControlLine(0, 120, false, false, DEFAULT_SCALE)],
|
|
208
208
|
field: 'controlLines',
|
|
209
209
|
description: 'Control Lines',
|
|
210
210
|
configurable: false,
|
|
@@ -217,7 +217,7 @@ Object.defineProperty(MusicGridRule, "CONFIGS", {
|
|
|
217
217
|
'wwwww',
|
|
218
218
|
'wwwww',
|
|
219
219
|
'wwwww',
|
|
220
|
-
]).addRule(new MusicGridRule([new ControlLine(0, 120, false, false,
|
|
220
|
+
]).addRule(new MusicGridRule([new ControlLine(0, 120, false, false, DEFAULT_SCALE)], null)),
|
|
221
221
|
field: 'track',
|
|
222
222
|
description: 'Track',
|
|
223
223
|
explanation: 'If set, this grid will be played instead of the solution.',
|
|
@@ -238,8 +238,8 @@ Object.defineProperty(MusicGridRule, "SEARCH_VARIANTS", {
|
|
|
238
238
|
configurable: true,
|
|
239
239
|
writable: true,
|
|
240
240
|
value: [
|
|
241
|
-
new MusicGridRule([new ControlLine(0, 120, false, false,
|
|
241
|
+
new MusicGridRule([new ControlLine(0, 120, false, false, DEFAULT_SCALE)], null).searchVariant(),
|
|
242
242
|
]
|
|
243
243
|
});
|
|
244
244
|
export default MusicGridRule;
|
|
245
|
-
export const instance = new MusicGridRule([new ControlLine(0, 120, false, false,
|
|
245
|
+
export const instance = new MusicGridRule([new ControlLine(0, 120, false, false, DEFAULT_SCALE)], null);
|
|
@@ -2,9 +2,11 @@ export { instance as BanPatternRule } from './banPatternRule.js';
|
|
|
2
2
|
export { instance as CellCountRule } from './cellCountRule.js';
|
|
3
3
|
export { instance as CompletePatternRule } from './completePatternRule.js';
|
|
4
4
|
export { instance as ConnectAllRule } from './connectAllRule.js';
|
|
5
|
+
export { instance as CollectZonesRule } from './connectZonesRule.js';
|
|
5
6
|
export { instance as ContainsShapeRule } from './containsShapeRule.js';
|
|
6
7
|
export { instance as CustomRule } from './customRule.js';
|
|
7
8
|
export { instance as DifferentCountPerZoneRule } from './differentCountPerZoneRule.js';
|
|
9
|
+
export { instance as ExactCountPerZoneRule } from './exactCountPerZoneRule.js';
|
|
8
10
|
export { instance as ForesightRule } from './foresightRule.js';
|
|
9
11
|
export { instance as LyingSymbolRule } from './lyingSymbolRule.js';
|
|
10
12
|
export { instance as MusicGridRule } from './musicGridRule.js';
|
|
@@ -6,9 +6,11 @@ export { instance as BanPatternRule } from './banPatternRule.js';
|
|
|
6
6
|
export { instance as CellCountRule } from './cellCountRule.js';
|
|
7
7
|
export { instance as CompletePatternRule } from './completePatternRule.js';
|
|
8
8
|
export { instance as ConnectAllRule } from './connectAllRule.js';
|
|
9
|
+
export { instance as CollectZonesRule } from './connectZonesRule.js';
|
|
9
10
|
export { instance as ContainsShapeRule } from './containsShapeRule.js';
|
|
10
11
|
export { instance as CustomRule } from './customRule.js';
|
|
11
12
|
export { instance as DifferentCountPerZoneRule } from './differentCountPerZoneRule.js';
|
|
13
|
+
export { instance as ExactCountPerZoneRule } from './exactCountPerZoneRule.js';
|
|
12
14
|
export { instance as ForesightRule } from './foresightRule.js';
|
|
13
15
|
export { instance as LyingSymbolRule } from './lyingSymbolRule.js';
|
|
14
16
|
export { instance as MusicGridRule } from './musicGridRule.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import GridData from './grid.js';
|
|
2
|
+
import { GridState } from './primitives.js';
|
|
3
|
+
export declare class GridValidator {
|
|
4
|
+
private worker;
|
|
5
|
+
private stateListeners;
|
|
6
|
+
private loadListeners;
|
|
7
|
+
private readonly validateGridDebounced;
|
|
8
|
+
readonly validateGrid: (grid: GridData, solution: GridData | null) => void;
|
|
9
|
+
private readonly notifyState;
|
|
10
|
+
readonly subscribeToState: (listener: (state: GridState) => void) => () => void;
|
|
11
|
+
private readonly notifyLoad;
|
|
12
|
+
readonly subscribeToLoad: (listener: () => void) => () => void;
|
|
13
|
+
readonly isLoading: () => boolean;
|
|
14
|
+
readonly delete: () => void;
|
|
15
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import debounce from 'lodash/debounce.js';
|
|
2
|
+
import { Serializer } from './serializer/allSerializers.js';
|
|
3
|
+
import validateGrid from './validate.js';
|
|
4
|
+
const SYNC_VALIDATION_THRESHOLD = 10000;
|
|
5
|
+
export class GridValidator {
|
|
6
|
+
constructor() {
|
|
7
|
+
Object.defineProperty(this, "worker", {
|
|
8
|
+
enumerable: true,
|
|
9
|
+
configurable: true,
|
|
10
|
+
writable: true,
|
|
11
|
+
value: null
|
|
12
|
+
});
|
|
13
|
+
Object.defineProperty(this, "stateListeners", {
|
|
14
|
+
enumerable: true,
|
|
15
|
+
configurable: true,
|
|
16
|
+
writable: true,
|
|
17
|
+
value: new Set()
|
|
18
|
+
});
|
|
19
|
+
Object.defineProperty(this, "loadListeners", {
|
|
20
|
+
enumerable: true,
|
|
21
|
+
configurable: true,
|
|
22
|
+
writable: true,
|
|
23
|
+
value: new Set()
|
|
24
|
+
});
|
|
25
|
+
Object.defineProperty(this, "validateGridDebounced", {
|
|
26
|
+
enumerable: true,
|
|
27
|
+
configurable: true,
|
|
28
|
+
writable: true,
|
|
29
|
+
value: debounce((grid, solution) => {
|
|
30
|
+
this.worker?.terminate();
|
|
31
|
+
this.worker = new Worker(new URL('./validateAsyncWorker.js', import.meta.url), { type: 'module' });
|
|
32
|
+
this.worker.onmessage = (event) => {
|
|
33
|
+
if (event.data) {
|
|
34
|
+
this.notifyState(event.data);
|
|
35
|
+
}
|
|
36
|
+
this.worker?.terminate();
|
|
37
|
+
this.worker = null;
|
|
38
|
+
this.notifyLoad();
|
|
39
|
+
};
|
|
40
|
+
this.worker.onmessageerror = (error) => {
|
|
41
|
+
console.error('Validation worker error:', error);
|
|
42
|
+
this.worker?.terminate();
|
|
43
|
+
this.worker = null;
|
|
44
|
+
this.notifyLoad();
|
|
45
|
+
};
|
|
46
|
+
this.worker.postMessage({
|
|
47
|
+
grid: Serializer.stringifyGrid(grid),
|
|
48
|
+
solution: solution ? Serializer.stringifyGrid(solution) : null,
|
|
49
|
+
});
|
|
50
|
+
this.notifyLoad();
|
|
51
|
+
}, 300, { leading: true, trailing: true })
|
|
52
|
+
});
|
|
53
|
+
Object.defineProperty(this, "validateGrid", {
|
|
54
|
+
enumerable: true,
|
|
55
|
+
configurable: true,
|
|
56
|
+
writable: true,
|
|
57
|
+
value: (grid, solution) => {
|
|
58
|
+
if (grid.width * grid.height <= SYNC_VALIDATION_THRESHOLD) {
|
|
59
|
+
// Synchronous validation for small grids
|
|
60
|
+
// to avoid the overhead of worker communication.
|
|
61
|
+
const state = validateGrid(grid, solution);
|
|
62
|
+
this.notifyState(state);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
this.validateGridDebounced(grid, solution);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
Object.defineProperty(this, "notifyState", {
|
|
70
|
+
enumerable: true,
|
|
71
|
+
configurable: true,
|
|
72
|
+
writable: true,
|
|
73
|
+
value: (state) => {
|
|
74
|
+
this.stateListeners.forEach(listener => listener(state));
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
Object.defineProperty(this, "subscribeToState", {
|
|
78
|
+
enumerable: true,
|
|
79
|
+
configurable: true,
|
|
80
|
+
writable: true,
|
|
81
|
+
value: (listener) => {
|
|
82
|
+
this.stateListeners.add(listener);
|
|
83
|
+
return () => {
|
|
84
|
+
this.stateListeners.delete(listener);
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
Object.defineProperty(this, "notifyLoad", {
|
|
89
|
+
enumerable: true,
|
|
90
|
+
configurable: true,
|
|
91
|
+
writable: true,
|
|
92
|
+
value: () => {
|
|
93
|
+
this.loadListeners.forEach(listener => listener());
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
Object.defineProperty(this, "subscribeToLoad", {
|
|
97
|
+
enumerable: true,
|
|
98
|
+
configurable: true,
|
|
99
|
+
writable: true,
|
|
100
|
+
value: (listener) => {
|
|
101
|
+
this.loadListeners.add(listener);
|
|
102
|
+
return () => {
|
|
103
|
+
this.loadListeners.delete(listener);
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
Object.defineProperty(this, "isLoading", {
|
|
108
|
+
enumerable: true,
|
|
109
|
+
configurable: true,
|
|
110
|
+
writable: true,
|
|
111
|
+
value: () => {
|
|
112
|
+
return this.worker !== null;
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
Object.defineProperty(this, "delete", {
|
|
116
|
+
enumerable: true,
|
|
117
|
+
configurable: true,
|
|
118
|
+
writable: true,
|
|
119
|
+
value: () => {
|
|
120
|
+
if (this.worker) {
|
|
121
|
+
this.worker.terminate();
|
|
122
|
+
this.worker = null;
|
|
123
|
+
}
|
|
124
|
+
this.stateListeners.clear();
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Serializer } from './serializer/allSerializers.js';
|
|
2
|
+
import validateGrid from './validate.js';
|
|
3
|
+
onmessage = e => {
|
|
4
|
+
const data = e.data;
|
|
5
|
+
const grid = Serializer.parseGrid(data.grid);
|
|
6
|
+
const solution = data.solution ? Serializer.parseGrid(data.solution) : null;
|
|
7
|
+
const state = validateGrid(grid, solution);
|
|
8
|
+
postMessage(state);
|
|
9
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -20,9 +20,11 @@ import CellCountPerZoneRule from './data/rules/cellCountPerZoneRule.js';
|
|
|
20
20
|
import CellCountRule from './data/rules/cellCountRule.js';
|
|
21
21
|
import CompletePatternRule from './data/rules/completePatternRule.js';
|
|
22
22
|
import ConnectAllRule from './data/rules/connectAllRule.js';
|
|
23
|
+
import CollectZonesRule from './data/rules/connectZonesRule.js';
|
|
23
24
|
import ContainsShapeRule from './data/rules/containsShapeRule.js';
|
|
24
25
|
import CustomRule from './data/rules/customRule.js';
|
|
25
26
|
import DifferentCountPerZoneRule from './data/rules/differentCountPerZoneRule.js';
|
|
27
|
+
import ExactCountPerZoneRule from './data/rules/exactCountPerZoneRule.js';
|
|
26
28
|
import ForesightRule from './data/rules/foresightRule.js';
|
|
27
29
|
import { allRules } from './data/rules/index.js';
|
|
28
30
|
import LyingSymbolRule from './data/rules/lyingSymbolRule.js';
|
|
@@ -113,4 +115,5 @@ import ViewpointSymbol from './data/symbols/viewpointSymbol.js';
|
|
|
113
115
|
import TileData from './data/tile.js';
|
|
114
116
|
import TileConnections from './data/tileConnections.js';
|
|
115
117
|
import validateGrid, { aggregateState, applyFinalOverrides } from './data/validate.js';
|
|
116
|
-
|
|
118
|
+
import { GridValidator } from './data/validateAsync.js';
|
|
119
|
+
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, DRUM_SAMPLES, Direction, INSTRUMENTS, Instrument, MajorRule, Mode, ORIENTATIONS, Orientation, PuzzleType, State, WRAPPINGS, Wrapping, directionToggle, isDrumSample, orientationToggle, MetadataSchema, PuzzleSchema, getPuzzleTypes, puzzleEquals, validatePuzzleChecklist, BanPatternRule, CellCountPerZoneRule, CellCountRule, CompletePatternRule, ConnectAllRule, CollectZonesRule, ContainsShapeRule, CustomRule, DifferentCountPerZoneRule, ExactCountPerZoneRule, ForesightRule, allRules, LyingSymbolRule, ControlLine, Row, MusicGridRule, MysteryRule, OffByXRule, PerfectionRule, RegionAreaRule, RegionShapeRule, Rule, SameCountPerZoneRule, SameShapeRule, SymbolsPerRegionRule, UndercluedRule, UniqueShapeRule, WrapAroundRule, Serializer, Compressor, ChecksumCompressor, CompressorBase, DeflateCompressor, GzipCompressor, StreamCompressor, SerializerBase, SerializerChecksum, SerializerV0, OFFSETS, orientationChars, getShapeVariants, normalizeShape, positionsToShape, sanitizePatternGrid, shapeEquals, tilesToShape, allSolvers, AutoSolver, 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, CspuzSolver, gridToJson, 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, HouseSymbol, allSymbols, LetterSymbol, LotusSymbol, MinesweeperSymbol, MultiEntrySymbol, MyopiaSymbol, NumberSymbol, Symbol, ViewpointSymbol, TileData, TileConnections, validateGrid, aggregateState, applyFinalOverrides, GridValidator, };
|
package/dist/index.js
CHANGED
|
@@ -23,9 +23,11 @@ import CellCountPerZoneRule from './data/rules/cellCountPerZoneRule.js';
|
|
|
23
23
|
import CellCountRule from './data/rules/cellCountRule.js';
|
|
24
24
|
import CompletePatternRule from './data/rules/completePatternRule.js';
|
|
25
25
|
import ConnectAllRule from './data/rules/connectAllRule.js';
|
|
26
|
+
import CollectZonesRule from './data/rules/connectZonesRule.js';
|
|
26
27
|
import ContainsShapeRule from './data/rules/containsShapeRule.js';
|
|
27
28
|
import CustomRule from './data/rules/customRule.js';
|
|
28
29
|
import DifferentCountPerZoneRule from './data/rules/differentCountPerZoneRule.js';
|
|
30
|
+
import ExactCountPerZoneRule from './data/rules/exactCountPerZoneRule.js';
|
|
29
31
|
import ForesightRule from './data/rules/foresightRule.js';
|
|
30
32
|
import { allRules } from './data/rules/index.js';
|
|
31
33
|
import LyingSymbolRule from './data/rules/lyingSymbolRule.js';
|
|
@@ -116,4 +118,5 @@ import ViewpointSymbol from './data/symbols/viewpointSymbol.js';
|
|
|
116
118
|
import TileData from './data/tile.js';
|
|
117
119
|
import TileConnections from './data/tileConnections.js';
|
|
118
120
|
import validateGrid, { aggregateState, applyFinalOverrides } from './data/validate.js';
|
|
119
|
-
|
|
121
|
+
import { GridValidator } from './data/validateAsync.js';
|
|
122
|
+
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, DRUM_SAMPLES, Direction, INSTRUMENTS, Instrument, MajorRule, Mode, ORIENTATIONS, Orientation, PuzzleType, State, WRAPPINGS, Wrapping, directionToggle, isDrumSample, orientationToggle, MetadataSchema, PuzzleSchema, getPuzzleTypes, puzzleEquals, validatePuzzleChecklist, BanPatternRule, CellCountPerZoneRule, CellCountRule, CompletePatternRule, ConnectAllRule, CollectZonesRule, ContainsShapeRule, CustomRule, DifferentCountPerZoneRule, ExactCountPerZoneRule, ForesightRule, allRules, LyingSymbolRule, ControlLine, Row, MusicGridRule, MysteryRule, OffByXRule, PerfectionRule, RegionAreaRule, RegionShapeRule, Rule, SameCountPerZoneRule, SameShapeRule, SymbolsPerRegionRule, UndercluedRule, UniqueShapeRule, WrapAroundRule, Serializer, Compressor, ChecksumCompressor, CompressorBase, DeflateCompressor, GzipCompressor, StreamCompressor, SerializerBase, SerializerChecksum, SerializerV0, OFFSETS, orientationChars, getShapeVariants, normalizeShape, positionsToShape, sanitizePatternGrid, shapeEquals, tilesToShape, allSolvers, AutoSolver, 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, CspuzSolver, gridToJson, 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, HouseSymbol, allSymbols, LetterSymbol, LotusSymbol, MinesweeperSymbol, MultiEntrySymbol, MyopiaSymbol, NumberSymbol, Symbol, ViewpointSymbol, TileData, TileConnections, validateGrid, aggregateState, applyFinalOverrides, GridValidator, };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logic-pad/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"compression-streams-polyfill": "^0.1.7",
|
|
55
55
|
"event-iterator": "^2.0.0",
|
|
56
56
|
"grilops": "^0.1.2",
|
|
57
|
+
"lodash": "^4.17.21",
|
|
57
58
|
"logic-pad-solver-core": "^0.1.2",
|
|
58
59
|
"z3-solver": "^4.13.0",
|
|
59
60
|
"zod": "^4.0.17"
|
|
@@ -61,6 +62,7 @@
|
|
|
61
62
|
"devDependencies": {
|
|
62
63
|
"@types/bun": "^1.2.20",
|
|
63
64
|
"@types/glob": "^9.0.0",
|
|
65
|
+
"@types/lodash": "^4.17.20",
|
|
64
66
|
"dts-bundle-generator": "^9.5.1",
|
|
65
67
|
"fast-glob": "^3.3.3",
|
|
66
68
|
"prettier": "^3.6.2",
|
|
@@ -70,4 +72,4 @@
|
|
|
70
72
|
"trustedDependencies": [
|
|
71
73
|
"esbuild"
|
|
72
74
|
]
|
|
73
|
-
}
|
|
75
|
+
}
|