@logic-pad/core 0.2.2 → 0.4.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.
@@ -188,6 +188,13 @@ declare global {
188
188
  x: number;
189
189
  y: number;
190
190
  };
191
+ /**
192
+ * Check if two edges are the same, regardless of direction.
193
+ * @param a The first edge.
194
+ * @param b The second edge.
195
+ * @returns Whether the edges are the same.
196
+ */
197
+ export declare function isSameEdge(a: Edge, b: Edge): boolean;
191
198
  /**
192
199
  * Convert the given direction to a rotation in degrees.
193
200
  * @param direction The direction to convert.
@@ -401,7 +408,11 @@ declare global {
401
408
  val: T
402
409
  ): val is T & GridChangeHandler;
403
410
  export interface SetGridHandler {
404
- onSetGrid(oldGrid: GridData, newGrid: GridData): GridData;
411
+ onSetGrid(
412
+ oldGrid: GridData,
413
+ newGrid: GridData,
414
+ solution: GridData | null
415
+ ): GridData;
405
416
  }
406
417
  export declare function handlesSetGrid<T extends Instruction>(
407
418
  val: T
@@ -504,7 +515,11 @@ declare global {
504
515
  createExampleGrid(): GridData;
505
516
  get searchVariants(): SearchVariant[];
506
517
  validateGrid(_grid: GridData): RuleState;
507
- onSetGrid(_oldGrid: GridData, newGrid: GridData): GridData;
518
+ onSetGrid(
519
+ _oldGrid: GridData,
520
+ newGrid: GridData,
521
+ _solution: GridData | null
522
+ ): GridData;
508
523
  onGridChange(newGrid: GridData): this;
509
524
  onGridResize(
510
525
  _grid: GridData,
@@ -569,6 +584,7 @@ declare global {
569
584
  get validateWithSolution(): boolean;
570
585
  get isSingleton(): boolean;
571
586
  }
587
+ export declare const NEIGHBOR_OFFSETS: Position$1[];
572
588
  export declare class GridData {
573
589
  readonly width: number;
574
590
  readonly height: number;
@@ -944,6 +960,14 @@ declare global {
944
960
  * @returns True if the grids are equal in size and tile colors, false otherwise.
945
961
  */
946
962
  colorEquals(grid: GridData): boolean;
963
+ /**
964
+ * Check if this grid conforms to the given solution, or an incomplete version of the solution.
965
+ * Symbols and rules are not validated.
966
+ *
967
+ * @param solution The solution to compare with.
968
+ * @returns True if the grid conforms to the solution, false otherwise.
969
+ */
970
+ solutionMatches(solution: GridData): boolean;
947
971
  /**
948
972
  * Check if this grid is equal to another grid in terms of size, tile colors, connections, symbols, and rules.
949
973
  *
@@ -1014,6 +1038,8 @@ declare global {
1014
1038
  Icon = 'icon',
1015
1039
  ControlLines = 'controlLines',
1016
1040
  NullableNote = 'nullableNote',
1041
+ SolvePath = 'solvePath',
1042
+ Edges = 'edges',
1017
1043
  }
1018
1044
  export interface Config<T> {
1019
1045
  readonly type: ConfigType;
@@ -1084,6 +1110,12 @@ declare global {
1084
1110
  export interface NullableNoteConfig extends Config<string | null> {
1085
1111
  readonly type: ConfigType.NullableNote;
1086
1112
  }
1113
+ export interface SolvePathConfig extends Config<Position$1[]> {
1114
+ readonly type: ConfigType.SolvePath;
1115
+ }
1116
+ export interface EdgesConfig extends Config<Edge[]> {
1117
+ readonly type: ConfigType.Edges;
1118
+ }
1087
1119
  export type AnyConfig =
1088
1120
  | BooleanConfig
1089
1121
  | NullableBooleanConfig
@@ -1101,7 +1133,9 @@ declare global {
1101
1133
  | NullableGridConfig
1102
1134
  | IconConfig
1103
1135
  | ControlLinesConfig
1104
- | NullableNoteConfig;
1136
+ | NullableNoteConfig
1137
+ | SolvePathConfig
1138
+ | EdgesConfig;
1105
1139
  /**
1106
1140
  * Compare two config values for equality, using an appropriate method for the config type.
1107
1141
  *
@@ -1311,6 +1345,39 @@ declare global {
1311
1345
  copyWith({ pattern }: { pattern?: GridData }): this;
1312
1346
  withPattern(pattern: GridData): this;
1313
1347
  }
1348
+ export declare class CellCountPerZoneRule extends Rule {
1349
+ readonly color: Color;
1350
+ readonly edges: readonly Edge[];
1351
+ private static readonly CONFIGS;
1352
+ private static readonly EXAMPLE_GRID_LIGHT;
1353
+ private static readonly EXAMPLE_GRID_DARK;
1354
+ private static readonly EXAMPLE_GRID_GRAY;
1355
+ private static readonly SEARCH_VARIANTS;
1356
+ /**
1357
+ * **Every zone has the same number of &lt;color&gt; cells.**
1358
+ *
1359
+ * @param color - The color of the cells to count.
1360
+ * @param edges - The edges of the zones to count.
1361
+ */
1362
+ constructor(color: Color, edges: readonly Edge[]);
1363
+ get id(): string;
1364
+ get explanation(): string;
1365
+ get configs(): readonly AnyConfig[] | null;
1366
+ createExampleGrid(): GridData;
1367
+ get searchVariants(): SearchVariant[];
1368
+ validateGrid(grid: GridData): RuleState;
1369
+ copyWith({
1370
+ color,
1371
+ edges,
1372
+ }: {
1373
+ color?: Color;
1374
+ edges?: readonly Edge[];
1375
+ }): this;
1376
+ withColor(color: Color): this;
1377
+ withEdges(
1378
+ edges: readonly Edge[] | ((edges: readonly Edge[]) => readonly Edge[])
1379
+ ): this;
1380
+ }
1314
1381
  export declare class CellCountRule extends Rule {
1315
1382
  readonly color: Color;
1316
1383
  readonly count: number;
@@ -1390,13 +1457,19 @@ declare global {
1390
1457
  readonly count: number;
1391
1458
  readonly regenInterval: number;
1392
1459
  readonly startFull: boolean;
1460
+ readonly solvePath: Position$1[];
1393
1461
  private static readonly EXAMPLE_GRID;
1394
1462
  private static readonly CONFIGS;
1395
1463
  private static readonly SEARCH_VARIANTS;
1396
1464
  /**
1397
1465
  * **Foresight: Show hints**
1398
1466
  */
1399
- constructor(count: number, regenInterval: number, startFull: boolean);
1467
+ constructor(
1468
+ count: number,
1469
+ regenInterval: number,
1470
+ startFull: boolean,
1471
+ solvePath?: Position$1[]
1472
+ );
1400
1473
  get id(): string;
1401
1474
  get explanation(): string;
1402
1475
  get visibleWhenSolving(): boolean;
@@ -1410,10 +1483,12 @@ declare global {
1410
1483
  count,
1411
1484
  regenInterval,
1412
1485
  startFull,
1486
+ solvePath,
1413
1487
  }: {
1414
1488
  count?: number;
1415
1489
  regenInterval?: number;
1416
1490
  startFull?: boolean;
1491
+ solvePath?: Position$1[];
1417
1492
  }): this;
1418
1493
  }
1419
1494
  export declare const allRules: Map<string, Rule>;
@@ -1490,6 +1565,45 @@ declare global {
1490
1565
  copyWith({ number }: { number?: number }): this;
1491
1566
  withNumber(number: number): this;
1492
1567
  }
1568
+ export declare class PerfectionRule
1569
+ extends Rule
1570
+ implements SetGridHandler, FinalValidationHandler
1571
+ {
1572
+ private static readonly EXAMPLE_GRID;
1573
+ private static readonly SEARCH_VARIANTS;
1574
+ /**
1575
+ * **Quest for Perfection: cell colors are final**
1576
+ */
1577
+ constructor();
1578
+ get id(): string;
1579
+ get explanation(): string;
1580
+ get configs(): readonly AnyConfig[] | null;
1581
+ createExampleGrid(): GridData;
1582
+ get searchVariants(): SearchVariant[];
1583
+ get necessaryForCompletion(): boolean;
1584
+ get isSingleton(): boolean;
1585
+ validateGrid(grid: GridData): RuleState;
1586
+ /**
1587
+ * If the grid passes validation but is different from the solution, indicate the error in the final state.
1588
+ */
1589
+ onFinalValidation(
1590
+ grid: GridData,
1591
+ solution: GridData | null,
1592
+ state: GridState
1593
+ ): GridState;
1594
+ private fixTiles;
1595
+ /**
1596
+ * Force all tiles to be fixed.
1597
+ *
1598
+ * If the grid is already wrong, prevent the player from changing it further.
1599
+ */
1600
+ onSetGrid(
1601
+ oldGrid: GridData,
1602
+ newGrid: GridData,
1603
+ solution: GridData | null
1604
+ ): GridData;
1605
+ copyWith(_: object): this;
1606
+ }
1493
1607
  export declare class RegionAreaRule extends Rule {
1494
1608
  readonly color: Color;
1495
1609
  readonly size: number;
@@ -1,5 +1,5 @@
1
1
  import GridData from './grid.js';
2
- import { Color, Comparison, Direction, DirectionToggle, Orientation, OrientationToggle } from './primitives.js';
2
+ import { Color, Comparison, Direction, DirectionToggle, Edge, Orientation, OrientationToggle, Position } from './primitives.js';
3
3
  import { ControlLine } from './rules/musicControlLine.js';
4
4
  export declare enum ConfigType {
5
5
  Boolean = "boolean",
@@ -18,7 +18,9 @@ export declare enum ConfigType {
18
18
  NullableGrid = "nullableGrid",
19
19
  Icon = "icon",
20
20
  ControlLines = "controlLines",
21
- NullableNote = "nullableNote"
21
+ NullableNote = "nullableNote",
22
+ SolvePath = "solvePath",
23
+ Edges = "edges"
22
24
  }
23
25
  export interface Config<T> {
24
26
  readonly type: ConfigType;
@@ -89,7 +91,13 @@ export interface ControlLinesConfig extends Config<ControlLine[]> {
89
91
  export interface NullableNoteConfig extends Config<string | null> {
90
92
  readonly type: ConfigType.NullableNote;
91
93
  }
92
- export type AnyConfig = BooleanConfig | NullableBooleanConfig | NumberConfig | NullableNumberConfig | StringConfig | ColorConfig | ComparisonConfig | DirectionConfig | DirectionToggleConfig | OrientationConfig | OrientationToggleConfig | TileConfig | GridConfig | NullableGridConfig | IconConfig | ControlLinesConfig | NullableNoteConfig;
94
+ export interface SolvePathConfig extends Config<Position[]> {
95
+ readonly type: ConfigType.SolvePath;
96
+ }
97
+ export interface EdgesConfig extends Config<Edge[]> {
98
+ readonly type: ConfigType.Edges;
99
+ }
100
+ export type AnyConfig = BooleanConfig | NullableBooleanConfig | NumberConfig | NullableNumberConfig | StringConfig | ColorConfig | ComparisonConfig | DirectionConfig | DirectionToggleConfig | OrientationConfig | OrientationToggleConfig | TileConfig | GridConfig | NullableGridConfig | IconConfig | ControlLinesConfig | NullableNoteConfig | SolvePathConfig | EdgesConfig;
93
101
  /**
94
102
  * Compare two config values for equality, using an appropriate method for the config type.
95
103
  *
@@ -1,3 +1,4 @@
1
+ import { isSameEdge } from './dataHelper.js';
1
2
  import { DIRECTIONS, ORIENTATIONS, } from './primitives.js';
2
3
  export var ConfigType;
3
4
  (function (ConfigType) {
@@ -18,6 +19,8 @@ export var ConfigType;
18
19
  ConfigType["Icon"] = "icon";
19
20
  ConfigType["ControlLines"] = "controlLines";
20
21
  ConfigType["NullableNote"] = "nullableNote";
22
+ ConfigType["SolvePath"] = "solvePath";
23
+ ConfigType["Edges"] = "edges";
21
24
  })(ConfigType || (ConfigType = {}));
22
25
  /**
23
26
  * Compare two config values for equality, using an appropriate method for the config type.
@@ -51,5 +54,19 @@ export function configEquals(type, a, b) {
51
54
  if (type === ConfigType.OrientationToggle) {
52
55
  return ORIENTATIONS.every(dir => a[dir] === b[dir]);
53
56
  }
57
+ if (type === ConfigType.SolvePath) {
58
+ const aPath = a;
59
+ const bPath = b;
60
+ if (aPath.length !== bPath.length)
61
+ return false;
62
+ return aPath.every((pos, i) => pos.x === bPath[i].x && pos.y === bPath[i].y);
63
+ }
64
+ if (type === ConfigType.Edges) {
65
+ const aEdges = a;
66
+ const bEdges = b;
67
+ if (aEdges.length !== bEdges.length)
68
+ return false;
69
+ return aEdges.every(aEdge => bEdges.some(bEdge => isSameEdge(aEdge, bEdge)));
70
+ }
54
71
  return a === b;
55
72
  }
@@ -1,4 +1,4 @@
1
- import { Direction, Orientation, Position } from './primitives.js';
1
+ import { Direction, Edge, Orientation, Position } from './primitives.js';
2
2
  /**
3
3
  * Offset the given position by a given step in the given direction.
4
4
  * @param position The position to offset.
@@ -10,6 +10,13 @@ export declare function move(position: Position, direction: Direction | Orientat
10
10
  x: number;
11
11
  y: number;
12
12
  };
13
+ /**
14
+ * Check if two edges are the same, regardless of direction.
15
+ * @param a The first edge.
16
+ * @param b The second edge.
17
+ * @returns Whether the edges are the same.
18
+ */
19
+ export declare function isSameEdge(a: Edge, b: Edge): boolean;
13
20
  /**
14
21
  * Convert the given direction to a rotation in degrees.
15
22
  * @param direction The direction to convert.
@@ -30,6 +30,16 @@ export function move(position, direction, step = 1) {
30
30
  return { x: position.x + step, y: position.y + step };
31
31
  }
32
32
  }
33
+ /**
34
+ * Check if two edges are the same, regardless of direction.
35
+ * @param a The first edge.
36
+ * @param b The second edge.
37
+ * @returns Whether the edges are the same.
38
+ */
39
+ export function isSameEdge(a, b) {
40
+ return ((a.x1 === b.x1 && a.y1 === b.y1 && a.x2 === b.x2 && a.y2 === b.y2) ||
41
+ (a.x1 === b.x2 && a.y1 === b.y2 && a.x2 === b.x1 && a.y2 === b.y1));
42
+ }
33
43
  /**
34
44
  * Convert the given direction to a rotation in degrees.
35
45
  * @param direction The direction to convert.
@@ -1,6 +1,6 @@
1
1
  import GridData from '../grid.js';
2
2
  import Instruction from '../instruction.js';
3
3
  export interface SetGridHandler {
4
- onSetGrid(oldGrid: GridData, newGrid: GridData): GridData;
4
+ onSetGrid(oldGrid: GridData, newGrid: GridData, solution: GridData | null): GridData;
5
5
  }
6
6
  export declare function handlesSetGrid<T extends Instruction>(val: T): val is T & SetGridHandler;
@@ -7,6 +7,7 @@ import TileData from './tile.js';
7
7
  import MusicGridRule from './rules/musicGridRule.js';
8
8
  import CompletePatternRule from './rules/completePatternRule.js';
9
9
  import UndercluedRule from './rules/undercluedRule.js';
10
+ export declare const NEIGHBOR_OFFSETS: Position[];
10
11
  export default class GridData {
11
12
  readonly width: number;
12
13
  readonly height: number;
@@ -321,6 +322,14 @@ export default class GridData {
321
322
  * @returns True if the grids are equal in size and tile colors, false otherwise.
322
323
  */
323
324
  colorEquals(grid: GridData): boolean;
325
+ /**
326
+ * Check if this grid conforms to the given solution, or an incomplete version of the solution.
327
+ * Symbols and rules are not validated.
328
+ *
329
+ * @param solution The solution to compare with.
330
+ * @returns True if the grid conforms to the solution, false otherwise.
331
+ */
332
+ solutionMatches(solution: GridData): boolean;
324
333
  /**
325
334
  * Check if this grid is equal to another grid in terms of size, tile colors, connections, symbols, and rules.
326
335
  *
package/dist/data/grid.js CHANGED
@@ -5,7 +5,7 @@ import GridConnections from './gridConnections.js';
5
5
  import { CachedAccess, array, move } from './dataHelper.js';
6
6
  import { Color, MajorRule, } from './primitives.js';
7
7
  import TileData from './tile.js';
8
- const NEIGHBOR_OFFSETS = [
8
+ export const NEIGHBOR_OFFSETS = [
9
9
  { x: -1, y: 0 },
10
10
  { x: 1, y: 0 },
11
11
  { x: 0, y: -1 },
@@ -573,10 +573,7 @@ export default class GridData {
573
573
  return ret;
574
574
  for (const offset of NEIGHBOR_OFFSETS) {
575
575
  const next = { x: x + offset.x, y: y + offset.y };
576
- if (next.x >= 0 &&
577
- next.x < this.width &&
578
- next.y >= 0 &&
579
- next.y < this.height) {
576
+ if (this.isPositionValid(next.x, next.y)) {
580
577
  const nextTile = this.getTile(next.x, next.y);
581
578
  if (nextTile.exists && predicate(nextTile))
582
579
  stack.push(next);
@@ -709,13 +706,13 @@ export default class GridData {
709
706
  this.symbols.forEach(list => {
710
707
  list.forEach(symbol => {
711
708
  if (handlesSetGrid(symbol)) {
712
- newGrid = symbol.onSetGrid(this, newGrid);
709
+ newGrid = symbol.onSetGrid(this, newGrid, null);
713
710
  }
714
711
  });
715
712
  });
716
713
  this.rules.forEach(rule => {
717
714
  if (handlesSetGrid(rule)) {
718
- newGrid = rule.onSetGrid(this, newGrid);
715
+ newGrid = rule.onSetGrid(this, newGrid, null);
719
716
  }
720
717
  });
721
718
  return newGrid;
@@ -801,6 +798,29 @@ export default class GridData {
801
798
  this.tiles.every((row, y) => row.every((tile, x) => (!tile.exists && !grid.getTile(x, y).exists) ||
802
799
  tile.color === grid.getTile(x, y).color)));
803
800
  }
801
+ /**
802
+ * Check if this grid conforms to the given solution, or an incomplete version of the solution.
803
+ * Symbols and rules are not validated.
804
+ *
805
+ * @param solution The solution to compare with.
806
+ * @returns True if the grid conforms to the solution, false otherwise.
807
+ */
808
+ solutionMatches(solution) {
809
+ if (this.width !== solution.width)
810
+ return false;
811
+ if (this.height !== solution.height)
812
+ return false;
813
+ return this.tiles.every((row, y) => row.every((tile, x) => {
814
+ const solutionTile = solution.getTile(x, y);
815
+ if (!solutionTile.exists)
816
+ return true;
817
+ if (tile.color === Color.Gray)
818
+ return true;
819
+ if (solutionTile.color !== tile.color)
820
+ return false;
821
+ return true;
822
+ }));
823
+ }
804
824
  /**
805
825
  * Check if this grid is equal to another grid in terms of size, tile colors, connections, symbols, and rules.
806
826
  *
@@ -1,8 +1,5 @@
1
+ import { isSameEdge } from './dataHelper.js';
1
2
  import TileConnections from './tileConnections.js';
2
- function isSameEdge(a, b) {
3
- return ((a.x1 === b.x1 && a.y1 === b.y1 && a.x2 === b.x2 && a.y2 === b.y2) ||
4
- (a.x1 === b.x2 && a.y1 === b.y2 && a.x2 === b.x1 && a.y2 === b.y1));
5
- }
6
3
  export default class GridConnections {
7
4
  constructor(edges) {
8
5
  Object.defineProperty(this, "edges", {
@@ -0,0 +1,33 @@
1
+ import { AnyConfig } from '../config.js';
2
+ import GridData from '../grid.js';
3
+ import { Color, Edge, RuleState } from '../primitives.js';
4
+ import Rule, { SearchVariant } from './rule.js';
5
+ export default class CellCountPerZoneRule extends Rule {
6
+ readonly color: Color;
7
+ readonly edges: readonly Edge[];
8
+ private static readonly CONFIGS;
9
+ private static readonly EXAMPLE_GRID_LIGHT;
10
+ private static readonly EXAMPLE_GRID_DARK;
11
+ private static readonly EXAMPLE_GRID_GRAY;
12
+ private static readonly SEARCH_VARIANTS;
13
+ /**
14
+ * **Every zone has the same number of &lt;color&gt; cells.**
15
+ *
16
+ * @param color - The color of the cells to count.
17
+ * @param edges - The edges of the zones to count.
18
+ */
19
+ constructor(color: Color, edges: readonly Edge[]);
20
+ get id(): string;
21
+ get explanation(): string;
22
+ get configs(): readonly AnyConfig[] | null;
23
+ createExampleGrid(): GridData;
24
+ get searchVariants(): SearchVariant[];
25
+ validateGrid(grid: GridData): RuleState;
26
+ copyWith({ color, edges, }: {
27
+ color?: Color;
28
+ edges?: readonly Edge[];
29
+ }): this;
30
+ withColor(color: Color): this;
31
+ withEdges(edges: readonly Edge[] | ((edges: readonly Edge[]) => readonly Edge[])): this;
32
+ }
33
+ export declare const instance: CellCountPerZoneRule;
@@ -0,0 +1,183 @@
1
+ import { ConfigType } from '../config.js';
2
+ import { array } from '../dataHelper.js';
3
+ import GridData, { NEIGHBOR_OFFSETS } from '../grid.js';
4
+ import GridConnections from '../gridConnections.js';
5
+ import { Color, State } from '../primitives.js';
6
+ import Rule from './rule.js';
7
+ class CellCountPerZoneRule extends Rule {
8
+ /**
9
+ * **Every zone has the same number of &lt;color&gt; cells.**
10
+ *
11
+ * @param color - The color of the cells to count.
12
+ * @param edges - The edges of the zones to count.
13
+ */
14
+ constructor(color, edges) {
15
+ super();
16
+ Object.defineProperty(this, "color", {
17
+ enumerable: true,
18
+ configurable: true,
19
+ writable: true,
20
+ value: color
21
+ });
22
+ Object.defineProperty(this, "edges", {
23
+ enumerable: true,
24
+ configurable: true,
25
+ writable: true,
26
+ value: edges
27
+ });
28
+ this.color = color;
29
+ this.edges = GridConnections.deduplicateEdges(edges);
30
+ }
31
+ get id() {
32
+ return `zone_cell_count`;
33
+ }
34
+ get explanation() {
35
+ return `Every zone has the same number of ${this.color} cells`;
36
+ }
37
+ get configs() {
38
+ return CellCountPerZoneRule.CONFIGS;
39
+ }
40
+ createExampleGrid() {
41
+ if (this.color === Color.Light) {
42
+ return CellCountPerZoneRule.EXAMPLE_GRID_LIGHT;
43
+ }
44
+ else if (this.color === Color.Dark) {
45
+ return CellCountPerZoneRule.EXAMPLE_GRID_DARK;
46
+ }
47
+ else {
48
+ return CellCountPerZoneRule.EXAMPLE_GRID_GRAY;
49
+ }
50
+ }
51
+ get searchVariants() {
52
+ return CellCountPerZoneRule.SEARCH_VARIANTS;
53
+ }
54
+ validateGrid(grid) {
55
+ let complete = true;
56
+ const visited = array(grid.width, grid.height, (i, j) => !grid.getTile(i, j).exists);
57
+ const zones = [];
58
+ while (true) {
59
+ const seed = grid.find((_tile, x, y) => !visited[y][x]);
60
+ if (!seed)
61
+ break;
62
+ const zone = {
63
+ positions: [],
64
+ completed: 0,
65
+ possible: 0,
66
+ };
67
+ const stack = [seed];
68
+ while (stack.length > 0) {
69
+ const { x, y } = stack.pop();
70
+ if (visited[y][x])
71
+ continue;
72
+ visited[y][x] = true;
73
+ zone.positions.push({ x, y });
74
+ if (grid.getTile(x, y).color === this.color) {
75
+ zone.completed++;
76
+ }
77
+ else if (grid.getTile(x, y).color === Color.Gray) {
78
+ zone.possible++;
79
+ complete = false;
80
+ }
81
+ for (const offset of NEIGHBOR_OFFSETS) {
82
+ const next = { x: x + offset.x, y: y + offset.y };
83
+ if (!this.edges.some(e => (e.x1 === x &&
84
+ e.y1 === y &&
85
+ e.x2 === next.x &&
86
+ e.y2 === next.y) ||
87
+ (e.x1 === next.x && e.y1 === next.y && e.x2 === x && e.y2 === y))) {
88
+ const nextTile = grid.getTile(next.x, next.y);
89
+ if (nextTile.exists) {
90
+ stack.push(next);
91
+ }
92
+ }
93
+ }
94
+ }
95
+ zones.push(zone);
96
+ }
97
+ if (zones.length <= 1) {
98
+ return { state: complete ? State.Satisfied : State.Incomplete };
99
+ }
100
+ else {
101
+ const errorZone = zones.find(z => zones.some(zz => zz !== z &&
102
+ (zz.completed > z.completed + z.possible ||
103
+ zz.completed + zz.possible < z.completed)));
104
+ if (errorZone) {
105
+ return {
106
+ state: State.Error,
107
+ positions: errorZone.positions,
108
+ };
109
+ }
110
+ else {
111
+ return { state: complete ? State.Satisfied : State.Incomplete };
112
+ }
113
+ }
114
+ }
115
+ copyWith({ color, edges, }) {
116
+ return new CellCountPerZoneRule(color ?? this.color, edges ?? this.edges);
117
+ }
118
+ withColor(color) {
119
+ return this.copyWith({ color });
120
+ }
121
+ withEdges(edges) {
122
+ return this.copyWith({
123
+ edges: typeof edges === 'function' ? edges(this.edges) : edges,
124
+ });
125
+ }
126
+ }
127
+ Object.defineProperty(CellCountPerZoneRule, "CONFIGS", {
128
+ enumerable: true,
129
+ configurable: true,
130
+ writable: true,
131
+ value: Object.freeze([
132
+ {
133
+ type: ConfigType.Color,
134
+ default: Color.Light,
135
+ allowGray: true,
136
+ field: 'color',
137
+ description: 'Color',
138
+ configurable: true,
139
+ },
140
+ {
141
+ type: ConfigType.Edges,
142
+ default: [],
143
+ field: 'edges',
144
+ description: 'Edges',
145
+ configurable: false,
146
+ },
147
+ ])
148
+ });
149
+ Object.defineProperty(CellCountPerZoneRule, "EXAMPLE_GRID_LIGHT", {
150
+ enumerable: true,
151
+ configurable: true,
152
+ writable: true,
153
+ value: Object.freeze(GridData.create(['bwbbb', 'wbbwb', 'bbbwb', 'bwbwb']).addRule(new CellCountPerZoneRule(Color.Light, [
154
+ { x1: 0, y1: 1, x2: 0, y2: 2 },
155
+ { x1: 1, y1: 1, x2: 1, y2: 2 },
156
+ { x1: 2, y1: 1, x2: 2, y2: 2 },
157
+ { x1: 3, y1: 1, x2: 3, y2: 2 },
158
+ { x1: 4, y1: 1, x2: 4, y2: 2 },
159
+ ])))
160
+ });
161
+ Object.defineProperty(CellCountPerZoneRule, "EXAMPLE_GRID_DARK", {
162
+ enumerable: true,
163
+ configurable: true,
164
+ writable: true,
165
+ value: Object.freeze(CellCountPerZoneRule.EXAMPLE_GRID_LIGHT.withTiles(tiles => tiles.map(row => row.map(tile => tile.withColor(tile.color === Color.Dark ? Color.Light : Color.Dark)))))
166
+ });
167
+ Object.defineProperty(CellCountPerZoneRule, "EXAMPLE_GRID_GRAY", {
168
+ enumerable: true,
169
+ configurable: true,
170
+ writable: true,
171
+ value: Object.freeze(CellCountPerZoneRule.EXAMPLE_GRID_LIGHT.withTiles(tiles => tiles.map(row => row.map(tile => tile.withColor(tile.color === Color.Light ? Color.Gray : tile.color)))))
172
+ });
173
+ Object.defineProperty(CellCountPerZoneRule, "SEARCH_VARIANTS", {
174
+ enumerable: true,
175
+ configurable: true,
176
+ writable: true,
177
+ value: [
178
+ new CellCountPerZoneRule(Color.Light, []).searchVariant(),
179
+ new CellCountPerZoneRule(Color.Dark, []).searchVariant(),
180
+ ]
181
+ });
182
+ export default CellCountPerZoneRule;
183
+ export const instance = new CellCountPerZoneRule(Color.Light, []);
@@ -1,18 +1,19 @@
1
1
  import { AnyConfig } from '../config.js';
2
2
  import GridData from '../grid.js';
3
- import { RuleState } from '../primitives.js';
3
+ import { RuleState, Position } from '../primitives.js';
4
4
  import Rule, { SearchVariant } from './rule.js';
5
5
  export default class ForesightRule extends Rule {
6
6
  readonly count: number;
7
7
  readonly regenInterval: number;
8
8
  readonly startFull: boolean;
9
+ readonly solvePath: Position[];
9
10
  private static readonly EXAMPLE_GRID;
10
11
  private static readonly CONFIGS;
11
12
  private static readonly SEARCH_VARIANTS;
12
13
  /**
13
14
  * **Foresight: Show hints**
14
15
  */
15
- constructor(count: number, regenInterval: number, startFull: boolean);
16
+ constructor(count: number, regenInterval: number, startFull: boolean, solvePath?: Position[]);
16
17
  get id(): string;
17
18
  get explanation(): string;
18
19
  get visibleWhenSolving(): boolean;
@@ -22,10 +23,11 @@ export default class ForesightRule extends Rule {
22
23
  validateGrid(_grid: GridData): RuleState;
23
24
  get necessaryForCompletion(): boolean;
24
25
  get isSingleton(): boolean;
25
- copyWith({ count, regenInterval, startFull, }: {
26
+ copyWith({ count, regenInterval, startFull, solvePath, }: {
26
27
  count?: number;
27
28
  regenInterval?: number;
28
29
  startFull?: boolean;
30
+ solvePath?: Position[];
29
31
  }): this;
30
32
  }
31
33
  export declare const instance: ForesightRule;
@@ -7,7 +7,7 @@ class ForesightRule extends Rule {
7
7
  /**
8
8
  * **Foresight: Show hints**
9
9
  */
10
- constructor(count, regenInterval, startFull) {
10
+ constructor(count, regenInterval, startFull, solvePath = []) {
11
11
  super();
12
12
  Object.defineProperty(this, "count", {
13
13
  enumerable: true,
@@ -27,9 +27,16 @@ class ForesightRule extends Rule {
27
27
  writable: true,
28
28
  value: startFull
29
29
  });
30
+ Object.defineProperty(this, "solvePath", {
31
+ enumerable: true,
32
+ configurable: true,
33
+ writable: true,
34
+ value: solvePath
35
+ });
30
36
  this.count = count;
31
37
  this.regenInterval = regenInterval;
32
38
  this.startFull = startFull;
39
+ this.solvePath = solvePath;
33
40
  }
34
41
  get id() {
35
42
  return `foresight`;
@@ -58,8 +65,8 @@ class ForesightRule extends Rule {
58
65
  get isSingleton() {
59
66
  return true;
60
67
  }
61
- copyWith({ count, regenInterval, startFull, }) {
62
- return new ForesightRule(count ?? this.count, regenInterval ?? this.regenInterval, startFull ?? this.startFull);
68
+ copyWith({ count, regenInterval, startFull, solvePath, }) {
69
+ return new ForesightRule(count ?? this.count, regenInterval ?? this.regenInterval, startFull ?? this.startFull, solvePath ?? this.solvePath);
63
70
  }
64
71
  }
65
72
  Object.defineProperty(ForesightRule, "EXAMPLE_GRID", {
@@ -96,6 +103,13 @@ Object.defineProperty(ForesightRule, "CONFIGS", {
96
103
  description: 'Start with full foresight',
97
104
  configurable: true,
98
105
  },
106
+ {
107
+ type: ConfigType.SolvePath,
108
+ default: [],
109
+ field: 'solvePath',
110
+ description: 'Intended solve path',
111
+ configurable: true,
112
+ },
99
113
  ])
100
114
  });
101
115
  Object.defineProperty(ForesightRule, "SEARCH_VARIANTS", {
@@ -24,7 +24,7 @@ export default class MusicGridRule extends Rule implements GridChangeHandler, Se
24
24
  createExampleGrid(): GridData;
25
25
  get searchVariants(): SearchVariant[];
26
26
  validateGrid(_grid: GridData): RuleState;
27
- onSetGrid(_oldGrid: GridData, newGrid: GridData): GridData;
27
+ onSetGrid(_oldGrid: GridData, newGrid: GridData, _solution: GridData | null): GridData;
28
28
  onGridChange(newGrid: GridData): this;
29
29
  onGridResize(_grid: GridData, mode: 'insert' | 'remove', direction: 'row' | 'column', index: number): this | null;
30
30
  /**
@@ -56,7 +56,7 @@ class MusicGridRule extends Rule {
56
56
  validateGrid(_grid) {
57
57
  return { state: State.Incomplete };
58
58
  }
59
- onSetGrid(_oldGrid, newGrid) {
59
+ onSetGrid(_oldGrid, newGrid, _solution) {
60
60
  if (newGrid.getTileCount(true, undefined, Color.Gray) === 0)
61
61
  return newGrid;
62
62
  const tiles = newGrid.tiles.map(row => row.map(tile => tile.color === Color.Gray ? tile.withColor(Color.Light) : tile));
@@ -0,0 +1,35 @@
1
+ import { AnyConfig } from '../config.js';
2
+ import GridData from '../grid.js';
3
+ import { GridState, RuleState } from '../primitives.js';
4
+ import Rule, { SearchVariant } from './rule.js';
5
+ import { SetGridHandler } from '../events/onSetGrid.js';
6
+ import { FinalValidationHandler } from '../events/onFinalValidation.js';
7
+ export default class PerfectionRule extends Rule implements SetGridHandler, FinalValidationHandler {
8
+ private static readonly EXAMPLE_GRID;
9
+ private static readonly SEARCH_VARIANTS;
10
+ /**
11
+ * **Quest for Perfection: cell colors are final**
12
+ */
13
+ constructor();
14
+ get id(): string;
15
+ get explanation(): string;
16
+ get configs(): readonly AnyConfig[] | null;
17
+ createExampleGrid(): GridData;
18
+ get searchVariants(): SearchVariant[];
19
+ get necessaryForCompletion(): boolean;
20
+ get isSingleton(): boolean;
21
+ validateGrid(grid: GridData): RuleState;
22
+ /**
23
+ * If the grid passes validation but is different from the solution, indicate the error in the final state.
24
+ */
25
+ onFinalValidation(grid: GridData, solution: GridData | null, state: GridState): GridState;
26
+ private fixTiles;
27
+ /**
28
+ * Force all tiles to be fixed.
29
+ *
30
+ * If the grid is already wrong, prevent the player from changing it further.
31
+ */
32
+ onSetGrid(oldGrid: GridData, newGrid: GridData, solution: GridData | null): GridData;
33
+ copyWith(_: object): this;
34
+ }
35
+ export declare const instance: PerfectionRule;
@@ -0,0 +1,113 @@
1
+ import GridData from '../grid.js';
2
+ import { Color, State } from '../primitives.js';
3
+ import Rule from './rule.js';
4
+ import CustomIconSymbol from '../symbols/customIconSymbol.js';
5
+ class PerfectionRule extends Rule {
6
+ /**
7
+ * **Quest for Perfection: cell colors are final**
8
+ */
9
+ constructor() {
10
+ super();
11
+ }
12
+ get id() {
13
+ return `perfection`;
14
+ }
15
+ get explanation() {
16
+ return `*Quest for Perfection*: cell colors are final`;
17
+ }
18
+ get configs() {
19
+ return null;
20
+ }
21
+ createExampleGrid() {
22
+ return PerfectionRule.EXAMPLE_GRID;
23
+ }
24
+ get searchVariants() {
25
+ return PerfectionRule.SEARCH_VARIANTS;
26
+ }
27
+ get necessaryForCompletion() {
28
+ return false;
29
+ }
30
+ get isSingleton() {
31
+ return true;
32
+ }
33
+ validateGrid(grid) {
34
+ if (grid.getTileCount(true, undefined, Color.Gray) > 0) {
35
+ return { state: State.Incomplete };
36
+ }
37
+ else {
38
+ return { state: State.Satisfied };
39
+ }
40
+ }
41
+ /**
42
+ * If the grid passes validation but is different from the solution, indicate the error in the final state.
43
+ */
44
+ onFinalValidation(grid, solution, state) {
45
+ if (state.final === State.Error)
46
+ return state;
47
+ if (solution === null)
48
+ return state;
49
+ const positions = [];
50
+ grid.tiles.forEach((row, y) => row.forEach((t, x) => {
51
+ if (t.exists &&
52
+ t.color !== Color.Gray &&
53
+ t.color !== solution.getTile(x, y).color) {
54
+ positions.push({ x, y });
55
+ }
56
+ }));
57
+ if (positions.length > 0) {
58
+ const ruleId = grid.rules.indexOf(this);
59
+ return {
60
+ final: State.Error,
61
+ rules: state.rules.map((r, idx) => {
62
+ if (idx === ruleId) {
63
+ return { state: State.Error, positions };
64
+ }
65
+ else {
66
+ return r;
67
+ }
68
+ }),
69
+ symbols: state.symbols,
70
+ };
71
+ }
72
+ return state;
73
+ }
74
+ fixTiles(grid) {
75
+ if (grid.getTileCount(true, false, Color.Light) > 0 ||
76
+ grid.getTileCount(true, false, Color.Dark) > 0) {
77
+ return grid.withTiles(tiles => tiles.map(row => row.map(t => t.exists && t.color !== Color.Gray ? t.withFixed(true) : t)));
78
+ }
79
+ return grid;
80
+ }
81
+ /**
82
+ * Force all tiles to be fixed.
83
+ *
84
+ * If the grid is already wrong, prevent the player from changing it further.
85
+ */
86
+ onSetGrid(oldGrid, newGrid, solution) {
87
+ if (!solution) {
88
+ return this.fixTiles(newGrid);
89
+ }
90
+ if (!oldGrid.solutionMatches(solution) &&
91
+ !newGrid.solutionMatches(solution)) {
92
+ return this.fixTiles(oldGrid);
93
+ }
94
+ return this.fixTiles(newGrid);
95
+ }
96
+ copyWith(_) {
97
+ return new PerfectionRule();
98
+ }
99
+ }
100
+ Object.defineProperty(PerfectionRule, "EXAMPLE_GRID", {
101
+ enumerable: true,
102
+ configurable: true,
103
+ writable: true,
104
+ value: Object.freeze(GridData.create(['w']).addSymbol(new CustomIconSymbol('', GridData.create(['w']), 0, 0, 'MdStars')))
105
+ });
106
+ Object.defineProperty(PerfectionRule, "SEARCH_VARIANTS", {
107
+ enumerable: true,
108
+ configurable: true,
109
+ writable: true,
110
+ value: []
111
+ }); // this rule is not searchable
112
+ export default PerfectionRule;
113
+ export const instance = new PerfectionRule();
@@ -1,4 +1,5 @@
1
1
  export { instance as BanPatternRule } from './banPatternRule.js';
2
+ export { instance as CellCountPerZoneRule } from './cellCountPerZoneRule.js';
2
3
  export { instance as CellCountRule } from './cellCountRule.js';
3
4
  export { instance as CompletePatternRule } from './completePatternRule.js';
4
5
  export { instance as ConnectAllRule } from './connectAllRule.js';
@@ -7,6 +8,7 @@ export { instance as ForesightRule } from './foresightRule.js';
7
8
  export { instance as MusicGridRule } from './musicGridRule.js';
8
9
  export { instance as MysteryRule } from './mysteryRule.js';
9
10
  export { instance as OffByXRule } from './offByXRule.js';
11
+ export { instance as PerfectionRule } from './perfectionRule.js';
10
12
  export { instance as RegionAreaRule } from './regionAreaRule.js';
11
13
  export { instance as SameShapeRule } from './sameShapeRule.js';
12
14
  export { instance as SymbolsPerRegionRule } from './symbolsPerRegionRule.js';
@@ -3,6 +3,7 @@
3
3
  // @ts-nocheck
4
4
  // noinspection JSUnusedGlobalSymbols
5
5
  export { instance as BanPatternRule } from './banPatternRule.js';
6
+ export { instance as CellCountPerZoneRule } from './cellCountPerZoneRule.js';
6
7
  export { instance as CellCountRule } from './cellCountRule.js';
7
8
  export { instance as CompletePatternRule } from './completePatternRule.js';
8
9
  export { instance as ConnectAllRule } from './connectAllRule.js';
@@ -11,6 +12,7 @@ export { instance as ForesightRule } from './foresightRule.js';
11
12
  export { instance as MusicGridRule } from './musicGridRule.js';
12
13
  export { instance as MysteryRule } from './mysteryRule.js';
13
14
  export { instance as OffByXRule } from './offByXRule.js';
15
+ export { instance as PerfectionRule } from './perfectionRule.js';
14
16
  export { instance as RegionAreaRule } from './regionAreaRule.js';
15
17
  export { instance as SameShapeRule } from './sameShapeRule.js';
16
18
  export { instance as SymbolsPerRegionRule } from './symbolsPerRegionRule.js';
@@ -161,6 +161,18 @@ export default class SerializerV0 extends SerializerBase {
161
161
  escape(instruction[config.field]
162
162
  .map(line => this.stringifyControlLine(line))
163
163
  .join(':')));
164
+ case ConfigType.SolvePath:
165
+ return (config.field +
166
+ '=' +
167
+ escape(instruction[config.field]
168
+ ?.map(pos => `${pos.x}_${pos.y}`)
169
+ .join('/') ?? ''));
170
+ case ConfigType.Edges:
171
+ return (config.field +
172
+ '=' +
173
+ instruction[config.field]
174
+ .map(edge => `${edge.x1}_${edge.y1}_${edge.x2}_${edge.y2}`)
175
+ .join('/'));
164
176
  }
165
177
  }
166
178
  parseConfig(configs, entry) {
@@ -219,6 +231,31 @@ export default class SerializerV0 extends SerializerBase {
219
231
  ];
220
232
  case ConfigType.NullableNote:
221
233
  return [config.field, value === '' ? null : unescape(value)];
234
+ case ConfigType.SolvePath:
235
+ return [
236
+ config.field,
237
+ value === ''
238
+ ? []
239
+ : value.split('/').map(pos => {
240
+ const [x, y] = pos.split('_');
241
+ return { x: Number(x), y: Number(y) };
242
+ }),
243
+ ];
244
+ case ConfigType.Edges:
245
+ return [
246
+ config.field,
247
+ value === ''
248
+ ? []
249
+ : value.split('/').map(edge => {
250
+ const [x1, y1, x2, y2] = edge.split('_');
251
+ return {
252
+ x1: Number(x1),
253
+ y1: Number(y1),
254
+ x2: Number(x2),
255
+ y2: Number(y2),
256
+ };
257
+ }),
258
+ ];
222
259
  }
223
260
  }
224
261
  stringifyInstruction(instruction) {
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ConfigType, configEquals } from './data/config.js';
2
2
  import Configurable from './data/configurable.js';
3
- import { CachedAccess, allEqual, array, directionToRotation, escape, maxBy, minBy, move, orientationToRotation, resize, unescape } from './data/dataHelper.js';
3
+ import { CachedAccess, allEqual, array, directionToRotation, escape, isSameEdge, maxBy, minBy, move, orientationToRotation, resize, unescape } from './data/dataHelper.js';
4
4
  import { isEventHandler } from './data/events/eventHelper.js';
5
5
  import { handlesFinalValidation } from './data/events/onFinalValidation.js';
6
6
  import { handlesGridChange } from './data/events/onGridChange.js';
@@ -8,12 +8,13 @@ import { handlesGridResize } from './data/events/onGridResize.js';
8
8
  import { handlesSetGrid } from './data/events/onSetGrid.js';
9
9
  import { handlesSymbolDisplay } from './data/events/onSymbolDisplay.js';
10
10
  import { handlesSymbolValidation } from './data/events/onSymbolValidation.js';
11
- import GridData from './data/grid.js';
11
+ import GridData, { NEIGHBOR_OFFSETS } from './data/grid.js';
12
12
  import GridConnections from './data/gridConnections.js';
13
13
  import Instruction from './data/instruction.js';
14
14
  import { COMPARISONS, Color, Comparison, DIRECTIONS, Direction, MajorRule, Mode, ORIENTATIONS, Orientation, State, directionToggle, orientationToggle } from './data/primitives.js';
15
15
  import { MetadataSchema, PuzzleSchema } from './data/puzzle.js';
16
16
  import BanPatternRule from './data/rules/banPatternRule.js';
17
+ import CellCountPerZoneRule from './data/rules/cellCountPerZoneRule.js';
17
18
  import CellCountRule from './data/rules/cellCountRule.js';
18
19
  import CompletePatternRule from './data/rules/completePatternRule.js';
19
20
  import ConnectAllRule from './data/rules/connectAllRule.js';
@@ -24,6 +25,7 @@ import { ControlLine, Row } from './data/rules/musicControlLine.js';
24
25
  import MusicGridRule from './data/rules/musicGridRule.js';
25
26
  import MysteryRule from './data/rules/mysteryRule.js';
26
27
  import OffByXRule from './data/rules/offByXRule.js';
28
+ import PerfectionRule from './data/rules/perfectionRule.js';
27
29
  import RegionAreaRule from './data/rules/regionAreaRule.js';
28
30
  import RegionShapeRule from './data/rules/regionShapeRule.js';
29
31
  import Rule from './data/rules/rule.js';
@@ -95,4 +97,4 @@ import ViewpointSymbol from './data/symbols/viewpointSymbol.js';
95
97
  import TileData from './data/tile.js';
96
98
  import TileConnections from './data/tileConnections.js';
97
99
  import validateGrid, { aggregateState, applyFinalOverrides } from './data/validate.js';
98
- export { ConfigType, configEquals, Configurable, CachedAccess, allEqual, array, directionToRotation, escape, maxBy, minBy, move, orientationToRotation, resize, unescape, isEventHandler, handlesFinalValidation, handlesGridChange, handlesGridResize, handlesSetGrid, handlesSymbolDisplay, handlesSymbolValidation, GridData, GridConnections, Instruction, COMPARISONS, Color, Comparison, DIRECTIONS, Direction, MajorRule, Mode, ORIENTATIONS, Orientation, State, directionToggle, orientationToggle, MetadataSchema, PuzzleSchema, BanPatternRule, CellCountRule, CompletePatternRule, ConnectAllRule, CustomRule, ForesightRule, allRules, ControlLine, Row, MusicGridRule, MysteryRule, OffByXRule, RegionAreaRule, RegionShapeRule, Rule, SameShapeRule, SymbolsPerRegionRule, UndercluedRule, UniqueShapeRule, 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, Solver, UndercluedSolver, 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, };
100
+ export { ConfigType, configEquals, Configurable, CachedAccess, allEqual, array, directionToRotation, escape, isSameEdge, maxBy, minBy, move, orientationToRotation, resize, unescape, isEventHandler, handlesFinalValidation, handlesGridChange, handlesGridResize, handlesSetGrid, handlesSymbolDisplay, handlesSymbolValidation, GridData, NEIGHBOR_OFFSETS, GridConnections, Instruction, COMPARISONS, Color, Comparison, DIRECTIONS, Direction, MajorRule, Mode, ORIENTATIONS, Orientation, State, directionToggle, orientationToggle, MetadataSchema, PuzzleSchema, BanPatternRule, CellCountPerZoneRule, CellCountRule, CompletePatternRule, ConnectAllRule, CustomRule, ForesightRule, allRules, ControlLine, Row, MusicGridRule, MysteryRule, OffByXRule, PerfectionRule, RegionAreaRule, RegionShapeRule, Rule, SameShapeRule, SymbolsPerRegionRule, UndercluedRule, UniqueShapeRule, 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, Solver, UndercluedSolver, 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, };
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // noinspection JSUnusedGlobalSymbols
4
4
  import { ConfigType, configEquals } from './data/config.js';
5
5
  import Configurable from './data/configurable.js';
6
- import { CachedAccess, allEqual, array, directionToRotation, escape, maxBy, minBy, move, orientationToRotation, resize, unescape } from './data/dataHelper.js';
6
+ import { CachedAccess, allEqual, array, directionToRotation, escape, isSameEdge, maxBy, minBy, move, orientationToRotation, resize, unescape } from './data/dataHelper.js';
7
7
  import { isEventHandler } from './data/events/eventHelper.js';
8
8
  import { handlesFinalValidation } from './data/events/onFinalValidation.js';
9
9
  import { handlesGridChange } from './data/events/onGridChange.js';
@@ -11,12 +11,13 @@ import { handlesGridResize } from './data/events/onGridResize.js';
11
11
  import { handlesSetGrid } from './data/events/onSetGrid.js';
12
12
  import { handlesSymbolDisplay } from './data/events/onSymbolDisplay.js';
13
13
  import { handlesSymbolValidation } from './data/events/onSymbolValidation.js';
14
- import GridData from './data/grid.js';
14
+ import GridData, { NEIGHBOR_OFFSETS } from './data/grid.js';
15
15
  import GridConnections from './data/gridConnections.js';
16
16
  import Instruction from './data/instruction.js';
17
17
  import { COMPARISONS, Color, Comparison, DIRECTIONS, Direction, MajorRule, Mode, ORIENTATIONS, Orientation, State, directionToggle, orientationToggle } from './data/primitives.js';
18
18
  import { MetadataSchema, PuzzleSchema } from './data/puzzle.js';
19
19
  import BanPatternRule from './data/rules/banPatternRule.js';
20
+ import CellCountPerZoneRule from './data/rules/cellCountPerZoneRule.js';
20
21
  import CellCountRule from './data/rules/cellCountRule.js';
21
22
  import CompletePatternRule from './data/rules/completePatternRule.js';
22
23
  import ConnectAllRule from './data/rules/connectAllRule.js';
@@ -27,6 +28,7 @@ import { ControlLine, Row } from './data/rules/musicControlLine.js';
27
28
  import MusicGridRule from './data/rules/musicGridRule.js';
28
29
  import MysteryRule from './data/rules/mysteryRule.js';
29
30
  import OffByXRule from './data/rules/offByXRule.js';
31
+ import PerfectionRule from './data/rules/perfectionRule.js';
30
32
  import RegionAreaRule from './data/rules/regionAreaRule.js';
31
33
  import RegionShapeRule from './data/rules/regionShapeRule.js';
32
34
  import Rule from './data/rules/rule.js';
@@ -98,4 +100,4 @@ import ViewpointSymbol from './data/symbols/viewpointSymbol.js';
98
100
  import TileData from './data/tile.js';
99
101
  import TileConnections from './data/tileConnections.js';
100
102
  import validateGrid, { aggregateState, applyFinalOverrides } from './data/validate.js';
101
- export { ConfigType, configEquals, Configurable, CachedAccess, allEqual, array, directionToRotation, escape, maxBy, minBy, move, orientationToRotation, resize, unescape, isEventHandler, handlesFinalValidation, handlesGridChange, handlesGridResize, handlesSetGrid, handlesSymbolDisplay, handlesSymbolValidation, GridData, GridConnections, Instruction, COMPARISONS, Color, Comparison, DIRECTIONS, Direction, MajorRule, Mode, ORIENTATIONS, Orientation, State, directionToggle, orientationToggle, MetadataSchema, PuzzleSchema, BanPatternRule, CellCountRule, CompletePatternRule, ConnectAllRule, CustomRule, ForesightRule, allRules, ControlLine, Row, MusicGridRule, MysteryRule, OffByXRule, RegionAreaRule, RegionShapeRule, Rule, SameShapeRule, SymbolsPerRegionRule, UndercluedRule, UniqueShapeRule, 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, Solver, UndercluedSolver, 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, };
103
+ export { ConfigType, configEquals, Configurable, CachedAccess, allEqual, array, directionToRotation, escape, isSameEdge, maxBy, minBy, move, orientationToRotation, resize, unescape, isEventHandler, handlesFinalValidation, handlesGridChange, handlesGridResize, handlesSetGrid, handlesSymbolDisplay, handlesSymbolValidation, GridData, NEIGHBOR_OFFSETS, GridConnections, Instruction, COMPARISONS, Color, Comparison, DIRECTIONS, Direction, MajorRule, Mode, ORIENTATIONS, Orientation, State, directionToggle, orientationToggle, MetadataSchema, PuzzleSchema, BanPatternRule, CellCountPerZoneRule, CellCountRule, CompletePatternRule, ConnectAllRule, CustomRule, ForesightRule, allRules, ControlLine, Row, MusicGridRule, MysteryRule, OffByXRule, PerfectionRule, RegionAreaRule, RegionShapeRule, Rule, SameShapeRule, SymbolsPerRegionRule, UndercluedRule, UniqueShapeRule, 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, Solver, UndercluedSolver, 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, };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logic-pad/core",
3
- "version": "0.2.2",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",