@logic-pad/core 0.25.1 → 0.25.3

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.
@@ -746,6 +746,7 @@ declare global {
746
746
  get isSingleton(): boolean;
747
747
  }
748
748
  export declare const NEIGHBOR_OFFSETS: Position$1[];
749
+ export declare const NEIGHBOR_OFFSETS_8: Position$1[];
749
750
  export declare class GridData {
750
751
  readonly width: number;
751
752
  readonly height: number;
@@ -10,6 +10,7 @@ import UndercluedRule from './rules/undercluedRule.js';
10
10
  import GridZones from './gridZones.js';
11
11
  import WrapAroundRule from './rules/wrapAroundRule.js';
12
12
  export declare const NEIGHBOR_OFFSETS: Position[];
13
+ export declare const NEIGHBOR_OFFSETS_8: Position[];
13
14
  export default class GridData {
14
15
  readonly width: number;
15
16
  readonly height: number;
@@ -12,6 +12,16 @@ export const NEIGHBOR_OFFSETS = [
12
12
  { x: 0, y: -1 },
13
13
  { x: 0, y: 1 },
14
14
  ];
15
+ export const NEIGHBOR_OFFSETS_8 = [
16
+ { x: -1, y: 0 },
17
+ { x: 1, y: 0 },
18
+ { x: 0, y: -1 },
19
+ { x: 0, y: 1 },
20
+ { x: -1, y: -1 },
21
+ { x: 1, y: -1 },
22
+ { x: -1, y: 1 },
23
+ { x: 1, y: 1 },
24
+ ];
15
25
  export default class GridData {
16
26
  width;
17
27
  height;
@@ -1,5 +1,5 @@
1
1
  import { ConfigType } from '../config.js';
2
- import GridData, { NEIGHBOR_OFFSETS } from '../grid.js';
2
+ import GridData, { NEIGHBOR_OFFSETS, NEIGHBOR_OFFSETS_8 } from '../grid.js';
3
3
  import { array } from '../dataHelper.js';
4
4
  import { Color, State } from '../primitives.js';
5
5
  import Rule from './rule.js';
@@ -60,45 +60,152 @@ export default class NoLoopsRule extends Rule {
60
60
  return NoLoopsRule.SEARCH_VARIANTS;
61
61
  }
62
62
  validateGrid(grid) {
63
- const visited = array(grid.width, grid.height, (i, j) => !grid.getTile(i, j).exists);
63
+ // wrap-around grids require special consideration because the "islands" assumption of the
64
+ // algorithm below does not hold
65
+ if (grid.wrapAround.value) {
66
+ const visited = array(grid.width, grid.height, (i, j) => {
67
+ const tile = grid.getTile(i, j);
68
+ return (!tile.exists ||
69
+ (tile.color !== this.color && tile.color !== Color.Gray));
70
+ });
71
+ while (true) {
72
+ const seed = grid.find((tile, x, y) => !visited[y][x] && tile.color === this.color);
73
+ if (!seed)
74
+ break;
75
+ let invalid = false;
76
+ const positions = [];
77
+ const stack = [[seed, null]];
78
+ while (stack.length > 0) {
79
+ const [{ x, y }, from] = stack.pop();
80
+ const { x: arrX, y: arrY } = grid.toArrayCoordinates(x, y);
81
+ positions.push({ x: arrX, y: arrY });
82
+ if (visited[arrY][arrX]) {
83
+ invalid = true;
84
+ continue;
85
+ }
86
+ visited[arrY][arrX] = true;
87
+ for (const offset of NEIGHBOR_OFFSETS) {
88
+ if (-offset.x === from?.x && -offset.y === from?.y)
89
+ continue;
90
+ const next = { x: x + offset.x, y: y + offset.y };
91
+ if (grid.isPositionValid(next.x, next.y)) {
92
+ const nextTile = grid.getTile(next.x, next.y);
93
+ if (nextTile.exists && nextTile.color === this.color)
94
+ stack.push([next, offset]);
95
+ }
96
+ }
97
+ }
98
+ if (invalid) {
99
+ return {
100
+ state: State.Error,
101
+ positions,
102
+ };
103
+ }
104
+ }
105
+ return {
106
+ state: visited.some(row => row.some(v => !v))
107
+ ? State.Incomplete
108
+ : State.Satisfied,
109
+ };
110
+ }
111
+ // special handling for 2x2 loops
112
+ for (let y = 0; y < grid.height; y++) {
113
+ for (let x = 0; x < grid.width; x++) {
114
+ const tlTile = grid.getTile(x, y);
115
+ const trTile = grid.getTile(x + 1, y);
116
+ const blTile = grid.getTile(x, y + 1);
117
+ const brTile = grid.getTile(x + 1, y + 1);
118
+ if (tlTile.exists &&
119
+ tlTile.color === this.color &&
120
+ trTile.exists &&
121
+ trTile.color === this.color &&
122
+ blTile.exists &&
123
+ blTile.color === this.color &&
124
+ brTile.exists &&
125
+ brTile.color === this.color) {
126
+ const positions = [
127
+ grid.toArrayCoordinates(x, y),
128
+ grid.toArrayCoordinates(x + 1, y),
129
+ grid.toArrayCoordinates(x, y + 1),
130
+ grid.toArrayCoordinates(x + 1, y + 1),
131
+ ];
132
+ return {
133
+ state: State.Error,
134
+ positions,
135
+ };
136
+ }
137
+ }
138
+ }
139
+ // general case for non-wrap-around grids: a loop must form an elcosed island that does not touch the grid edge
140
+ const visited = array(grid.width, grid.height, (i, j) => {
141
+ const tile = grid.getTile(i, j);
142
+ return tile.exists && tile.color === this.color;
143
+ });
144
+ const shape = array(grid.width, grid.height, () => false);
145
+ let complete = true;
64
146
  while (true) {
65
- const seed = grid.find((tile, x, y) => !visited[y][x] && tile.color === this.color);
147
+ const seed = grid.find((tile, x, y) => !visited[y][x] && (!tile.exists || tile.color !== this.color));
148
+ shape.forEach(row => row.fill(false));
66
149
  if (!seed)
67
150
  break;
68
- let invalid = false;
69
- const positions = [];
70
- const stack = [[seed, null]];
151
+ let isIsland = true;
152
+ const stack = [seed];
71
153
  while (stack.length > 0) {
72
- const [{ x, y }, from] = stack.pop();
154
+ const { x, y } = stack.pop();
73
155
  const { x: arrX, y: arrY } = grid.toArrayCoordinates(x, y);
74
- positions.push({ x, y });
156
+ const tile = grid.getTile(x, y);
157
+ if (tile.exists && tile.color === Color.Gray) {
158
+ complete = false;
159
+ }
75
160
  if (visited[arrY][arrX]) {
76
- invalid = true;
77
161
  continue;
78
162
  }
79
163
  visited[arrY][arrX] = true;
80
- for (const offset of NEIGHBOR_OFFSETS) {
81
- if (-offset.x === from?.x && -offset.y === from?.y)
82
- continue;
164
+ for (const offset of NEIGHBOR_OFFSETS_8) {
83
165
  const next = { x: x + offset.x, y: y + offset.y };
166
+ const arrPos = grid.toArrayCoordinates(next.x, next.y);
84
167
  if (grid.isPositionValid(next.x, next.y)) {
85
168
  const nextTile = grid.getTile(next.x, next.y);
86
- if (nextTile.exists && nextTile.color === this.color)
87
- stack.push([next, offset]);
169
+ shape[arrPos.y][arrPos.x] = true;
170
+ if (!nextTile.exists || nextTile.color !== this.color) {
171
+ stack.push(arrPos);
172
+ }
173
+ }
174
+ else {
175
+ isIsland = false;
88
176
  }
89
177
  }
90
178
  }
91
- if (invalid) {
179
+ if (isIsland) {
180
+ const loopPositions = [];
181
+ for (let y = 0; y < grid.height; y++) {
182
+ for (let x = 0; x < grid.width; x++) {
183
+ if (shape[y][x]) {
184
+ if (x > 0 &&
185
+ y > 0 &&
186
+ x < grid.width - 1 &&
187
+ y < grid.height - 1 &&
188
+ shape[y][x - 1] &&
189
+ shape[y - 1][x] &&
190
+ shape[y][x + 1] &&
191
+ shape[y + 1][x] &&
192
+ shape[y - 1][x - 1] &&
193
+ shape[y - 1][x + 1] &&
194
+ shape[y + 1][x - 1] &&
195
+ shape[y + 1][x + 1])
196
+ continue;
197
+ loopPositions.push({ x, y });
198
+ }
199
+ }
200
+ }
92
201
  return {
93
202
  state: State.Error,
94
- positions,
203
+ positions: loopPositions,
95
204
  };
96
205
  }
97
206
  }
98
207
  return {
99
- state: visited.some(row => row.some(v => !v))
100
- ? State.Incomplete
101
- : State.Satisfied,
208
+ state: complete ? State.Satisfied : State.Incomplete,
102
209
  };
103
210
  }
104
211
  copyWith({ color }) {
@@ -10,7 +10,7 @@ import { handlesSetGrid, invokeSetGrid } from './data/events/onSetGrid.js';
10
10
  import { handlesSymbolDisplay } from './data/events/onSymbolDisplay.js';
11
11
  import { handlesSymbolMerge } from './data/events/onSymbolMerge.js';
12
12
  import { handlesSymbolValidation } from './data/events/onSymbolValidation.js';
13
- import GridData, { NEIGHBOR_OFFSETS } from './data/grid.js';
13
+ import GridData, { NEIGHBOR_OFFSETS, NEIGHBOR_OFFSETS_8 } from './data/grid.js';
14
14
  import GridConnections from './data/gridConnections.js';
15
15
  import GridZones from './data/gridZones.js';
16
16
  import Instruction from './data/instruction.js';
@@ -118,4 +118,4 @@ import TileData from './data/tile.js';
118
118
  import TileConnections from './data/tileConnections.js';
119
119
  import validateGrid, { aggregateState, applyFinalOverrides } from './data/validate.js';
120
120
  import { GridValidator } from './data/validateAsync.js';
121
- export { ConfigType, configEquals, Configurable, CachedAccess, allEqual, array, directionToRotation, escape, isSameEdge, maxBy, minBy, move, orientationToRotation, resize, unescape, isEventHandler, handlesFinalValidation, handlesGetTile, handlesGridChange, handlesGridResize, handlesSetGrid, invokeSetGrid, handlesSymbolDisplay, handlesSymbolMerge, 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, ConnectZonesRule, ContainsShapeRule, CustomRule, DifferentCountPerZoneRule, ExactCountPerZoneRule, ForesightRule, allRules, LyingSymbolRule, ControlLine, Row, MusicGridRule, MysteryRule, NoLoopsRule, 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, EveryLetterSymbol, FocusSymbol, GalaxySymbol, HiddenSymbol, HouseSymbol, allSymbols, LetterSymbol, LotusSymbol, MinesweeperSymbol, MyopiaSymbol, NumberSymbol, Symbol, ViewpointSymbol, TileData, TileConnections, validateGrid, aggregateState, applyFinalOverrides, GridValidator, };
121
+ export { ConfigType, configEquals, Configurable, CachedAccess, allEqual, array, directionToRotation, escape, isSameEdge, maxBy, minBy, move, orientationToRotation, resize, unescape, isEventHandler, handlesFinalValidation, handlesGetTile, handlesGridChange, handlesGridResize, handlesSetGrid, invokeSetGrid, handlesSymbolDisplay, handlesSymbolMerge, handlesSymbolValidation, GridData, NEIGHBOR_OFFSETS, NEIGHBOR_OFFSETS_8, 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, ConnectZonesRule, ContainsShapeRule, CustomRule, DifferentCountPerZoneRule, ExactCountPerZoneRule, ForesightRule, allRules, LyingSymbolRule, ControlLine, Row, MusicGridRule, MysteryRule, NoLoopsRule, 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, EveryLetterSymbol, FocusSymbol, GalaxySymbol, HiddenSymbol, HouseSymbol, allSymbols, LetterSymbol, LotusSymbol, MinesweeperSymbol, MyopiaSymbol, NumberSymbol, Symbol, ViewpointSymbol, TileData, TileConnections, validateGrid, aggregateState, applyFinalOverrides, GridValidator, };
package/dist/src/index.js CHANGED
@@ -13,7 +13,7 @@ import { handlesSetGrid, invokeSetGrid } from './data/events/onSetGrid.js';
13
13
  import { handlesSymbolDisplay } from './data/events/onSymbolDisplay.js';
14
14
  import { handlesSymbolMerge } from './data/events/onSymbolMerge.js';
15
15
  import { handlesSymbolValidation } from './data/events/onSymbolValidation.js';
16
- import GridData, { NEIGHBOR_OFFSETS } from './data/grid.js';
16
+ import GridData, { NEIGHBOR_OFFSETS, NEIGHBOR_OFFSETS_8 } from './data/grid.js';
17
17
  import GridConnections from './data/gridConnections.js';
18
18
  import GridZones from './data/gridZones.js';
19
19
  import Instruction from './data/instruction.js';
@@ -121,4 +121,4 @@ import TileData from './data/tile.js';
121
121
  import TileConnections from './data/tileConnections.js';
122
122
  import validateGrid, { aggregateState, applyFinalOverrides } from './data/validate.js';
123
123
  import { GridValidator } from './data/validateAsync.js';
124
- export { ConfigType, configEquals, Configurable, CachedAccess, allEqual, array, directionToRotation, escape, isSameEdge, maxBy, minBy, move, orientationToRotation, resize, unescape, isEventHandler, handlesFinalValidation, handlesGetTile, handlesGridChange, handlesGridResize, handlesSetGrid, invokeSetGrid, handlesSymbolDisplay, handlesSymbolMerge, 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, ConnectZonesRule, ContainsShapeRule, CustomRule, DifferentCountPerZoneRule, ExactCountPerZoneRule, ForesightRule, allRules, LyingSymbolRule, ControlLine, Row, MusicGridRule, MysteryRule, NoLoopsRule, 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, EveryLetterSymbol, FocusSymbol, GalaxySymbol, HiddenSymbol, HouseSymbol, allSymbols, LetterSymbol, LotusSymbol, MinesweeperSymbol, MyopiaSymbol, NumberSymbol, Symbol, ViewpointSymbol, TileData, TileConnections, validateGrid, aggregateState, applyFinalOverrides, GridValidator, };
124
+ export { ConfigType, configEquals, Configurable, CachedAccess, allEqual, array, directionToRotation, escape, isSameEdge, maxBy, minBy, move, orientationToRotation, resize, unescape, isEventHandler, handlesFinalValidation, handlesGetTile, handlesGridChange, handlesGridResize, handlesSetGrid, invokeSetGrid, handlesSymbolDisplay, handlesSymbolMerge, handlesSymbolValidation, GridData, NEIGHBOR_OFFSETS, NEIGHBOR_OFFSETS_8, 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, ConnectZonesRule, ContainsShapeRule, CustomRule, DifferentCountPerZoneRule, ExactCountPerZoneRule, ForesightRule, allRules, LyingSymbolRule, ControlLine, Row, MusicGridRule, MysteryRule, NoLoopsRule, 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, EveryLetterSymbol, FocusSymbol, GalaxySymbol, HiddenSymbol, HouseSymbol, allSymbols, LetterSymbol, LotusSymbol, MinesweeperSymbol, 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.25.1",
3
+ "version": "0.25.3",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",