@logic-pad/core 0.26.1 → 0.26.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (241) hide show
  1. package/README.md +31 -31
  2. package/assets/logic-core.global.d.ts +3264 -3262
  3. package/package.json +4 -1
  4. package/dist/benchmark/helper.d.ts +0 -21
  5. package/dist/benchmark/helper.js +0 -34
  6. package/dist/benchmark/prepareBench.d.ts +0 -1
  7. package/dist/benchmark/prepareBench.js +0 -140
  8. package/dist/benchmark/runBench.d.ts +0 -1
  9. package/dist/benchmark/runBench.js +0 -206
  10. package/dist/src/data/config.d.ts +0 -119
  11. package/dist/src/data/config.js +0 -72
  12. package/dist/src/data/configurable.d.ts +0 -14
  13. package/dist/src/data/configurable.js +0 -26
  14. package/dist/src/data/dataHelper.d.ts +0 -92
  15. package/dist/src/data/dataHelper.js +0 -217
  16. package/dist/src/data/events/eventHelper.d.ts +0 -1
  17. package/dist/src/data/events/eventHelper.js +0 -6
  18. package/dist/src/data/events/onFinalValidation.d.ts +0 -14
  19. package/dist/src/data/events/onFinalValidation.js +0 -4
  20. package/dist/src/data/events/onGetTile.d.ts +0 -7
  21. package/dist/src/data/events/onGetTile.js +0 -4
  22. package/dist/src/data/events/onGridChange.d.ts +0 -6
  23. package/dist/src/data/events/onGridChange.js +0 -4
  24. package/dist/src/data/events/onGridResize.d.ts +0 -9
  25. package/dist/src/data/events/onGridResize.js +0 -4
  26. package/dist/src/data/events/onSetGrid.d.ts +0 -7
  27. package/dist/src/data/events/onSetGrid.js +0 -19
  28. package/dist/src/data/events/onSymbolDisplay.d.ts +0 -16
  29. package/dist/src/data/events/onSymbolDisplay.js +0 -4
  30. package/dist/src/data/events/onSymbolMerge.d.ts +0 -10
  31. package/dist/src/data/events/onSymbolMerge.js +0 -4
  32. package/dist/src/data/events/onSymbolValidation.d.ts +0 -18
  33. package/dist/src/data/events/onSymbolValidation.js +0 -4
  34. package/dist/src/data/grid.d.ts +0 -410
  35. package/dist/src/data/grid.js +0 -1106
  36. package/dist/src/data/gridConnections.d.ts +0 -25
  37. package/dist/src/data/gridConnections.js +0 -309
  38. package/dist/src/data/gridZones.d.ts +0 -26
  39. package/dist/src/data/gridZones.js +0 -117
  40. package/dist/src/data/instruction.d.ts +0 -26
  41. package/dist/src/data/instruction.js +0 -29
  42. package/dist/src/data/primitives.d.ts +0 -138
  43. package/dist/src/data/primitives.js +0 -177
  44. package/dist/src/data/puzzle.d.ts +0 -73
  45. package/dist/src/data/puzzle.js +0 -105
  46. package/dist/src/data/rules/banPatternRule.d.ts +0 -30
  47. package/dist/src/data/rules/banPatternRule.js +0 -125
  48. package/dist/src/data/rules/cellCountPerZoneRule.d.ts +0 -23
  49. package/dist/src/data/rules/cellCountPerZoneRule.js +0 -39
  50. package/dist/src/data/rules/cellCountRule.d.ts +0 -33
  51. package/dist/src/data/rules/cellCountRule.js +0 -138
  52. package/dist/src/data/rules/completePatternRule.d.ts +0 -24
  53. package/dist/src/data/rules/completePatternRule.js +0 -46
  54. package/dist/src/data/rules/connectAllRule.d.ts +0 -29
  55. package/dist/src/data/rules/connectAllRule.js +0 -88
  56. package/dist/src/data/rules/connectZonesRule.d.ts +0 -29
  57. package/dist/src/data/rules/connectZonesRule.js +0 -111
  58. package/dist/src/data/rules/containsShapeRule.d.ts +0 -34
  59. package/dist/src/data/rules/containsShapeRule.js +0 -125
  60. package/dist/src/data/rules/customRule.d.ts +0 -34
  61. package/dist/src/data/rules/customRule.js +0 -74
  62. package/dist/src/data/rules/differentCountPerZoneRule.d.ts +0 -30
  63. package/dist/src/data/rules/differentCountPerZoneRule.js +0 -96
  64. package/dist/src/data/rules/exactCountPerZoneRule.d.ts +0 -33
  65. package/dist/src/data/rules/exactCountPerZoneRule.js +0 -99
  66. package/dist/src/data/rules/foresightRule.d.ts +0 -36
  67. package/dist/src/data/rules/foresightRule.js +0 -107
  68. package/dist/src/data/rules/index.d.ts +0 -3
  69. package/dist/src/data/rules/index.js +0 -10
  70. package/dist/src/data/rules/lyingSymbolRule.d.ts +0 -31
  71. package/dist/src/data/rules/lyingSymbolRule.js +0 -207
  72. package/dist/src/data/rules/musicControlLine.d.ts +0 -82
  73. package/dist/src/data/rules/musicControlLine.js +0 -167
  74. package/dist/src/data/rules/musicGridRule.d.ts +0 -51
  75. package/dist/src/data/rules/musicGridRule.js +0 -212
  76. package/dist/src/data/rules/mysteryRule.d.ts +0 -39
  77. package/dist/src/data/rules/mysteryRule.js +0 -146
  78. package/dist/src/data/rules/noLoopsRule.d.ts +0 -29
  79. package/dist/src/data/rules/noLoopsRule.js +0 -218
  80. package/dist/src/data/rules/offByXRule.d.ts +0 -32
  81. package/dist/src/data/rules/offByXRule.js +0 -124
  82. package/dist/src/data/rules/perfectionRule.d.ts +0 -45
  83. package/dist/src/data/rules/perfectionRule.js +0 -158
  84. package/dist/src/data/rules/regionAreaRule.d.ts +0 -34
  85. package/dist/src/data/rules/regionAreaRule.js +0 -149
  86. package/dist/src/data/rules/regionShapeRule.d.ts +0 -22
  87. package/dist/src/data/rules/regionShapeRule.js +0 -58
  88. package/dist/src/data/rules/rule.d.ts +0 -18
  89. package/dist/src/data/rules/rule.js +0 -19
  90. package/dist/src/data/rules/rules.gen.d.ts +0 -23
  91. package/dist/src/data/rules/rules.gen.js +0 -27
  92. package/dist/src/data/rules/sameCountPerZoneRule.d.ts +0 -30
  93. package/dist/src/data/rules/sameCountPerZoneRule.js +0 -95
  94. package/dist/src/data/rules/sameShapeRule.d.ts +0 -28
  95. package/dist/src/data/rules/sameShapeRule.js +0 -68
  96. package/dist/src/data/rules/symbolsPerRegionRule.d.ts +0 -38
  97. package/dist/src/data/rules/symbolsPerRegionRule.js +0 -181
  98. package/dist/src/data/rules/undercluedRule.d.ts +0 -24
  99. package/dist/src/data/rules/undercluedRule.js +0 -53
  100. package/dist/src/data/rules/uniqueShapeRule.d.ts +0 -28
  101. package/dist/src/data/rules/uniqueShapeRule.js +0 -65
  102. package/dist/src/data/rules/wrapAroundRule.d.ts +0 -36
  103. package/dist/src/data/rules/wrapAroundRule.js +0 -241
  104. package/dist/src/data/serializer/allSerializers.d.ts +0 -32
  105. package/dist/src/data/serializer/allSerializers.js +0 -71
  106. package/dist/src/data/serializer/compressor/allCompressors.d.ts +0 -14
  107. package/dist/src/data/serializer/compressor/allCompressors.js +0 -43
  108. package/dist/src/data/serializer/compressor/checksumCompressor.d.ts +0 -6
  109. package/dist/src/data/serializer/compressor/checksumCompressor.js +0 -21
  110. package/dist/src/data/serializer/compressor/compressorBase.d.ts +0 -16
  111. package/dist/src/data/serializer/compressor/compressorBase.js +0 -2
  112. package/dist/src/data/serializer/compressor/deflateCompressor.d.ts +0 -7
  113. package/dist/src/data/serializer/compressor/deflateCompressor.js +0 -17
  114. package/dist/src/data/serializer/compressor/gzipCompressor.d.ts +0 -5
  115. package/dist/src/data/serializer/compressor/gzipCompressor.js +0 -9
  116. package/dist/src/data/serializer/compressor/streamCompressor.d.ts +0 -6
  117. package/dist/src/data/serializer/compressor/streamCompressor.js +0 -41
  118. package/dist/src/data/serializer/serializerBase.d.ts +0 -32
  119. package/dist/src/data/serializer/serializerBase.js +0 -2
  120. package/dist/src/data/serializer/serializer_checksum.d.ts +0 -35
  121. package/dist/src/data/serializer/serializer_checksum.js +0 -179
  122. package/dist/src/data/serializer/serializer_v0.d.ts +0 -55
  123. package/dist/src/data/serializer/serializer_v0.js +0 -484
  124. package/dist/src/data/shapes.d.ts +0 -19
  125. package/dist/src/data/shapes.js +0 -137
  126. package/dist/src/data/solver/allSolvers.d.ts +0 -3
  127. package/dist/src/data/solver/allSolvers.js +0 -13
  128. package/dist/src/data/solver/auto/autoSolver.d.ts +0 -18
  129. package/dist/src/data/solver/auto/autoSolver.js +0 -156
  130. package/dist/src/data/solver/backtrack/backtrackSolver.d.ts +0 -11
  131. package/dist/src/data/solver/backtrack/backtrackSolver.js +0 -54
  132. package/dist/src/data/solver/backtrack/backtrackWorker.d.ts +0 -1
  133. package/dist/src/data/solver/backtrack/backtrackWorker.js +0 -312
  134. package/dist/src/data/solver/backtrack/data.d.ts +0 -47
  135. package/dist/src/data/solver/backtrack/data.js +0 -151
  136. package/dist/src/data/solver/backtrack/rules/banPattern.d.ts +0 -9
  137. package/dist/src/data/solver/backtrack/rules/banPattern.js +0 -77
  138. package/dist/src/data/solver/backtrack/rules/cellCount.d.ts +0 -7
  139. package/dist/src/data/solver/backtrack/rules/cellCount.js +0 -25
  140. package/dist/src/data/solver/backtrack/rules/connectAll.d.ts +0 -7
  141. package/dist/src/data/solver/backtrack/rules/connectAll.js +0 -44
  142. package/dist/src/data/solver/backtrack/rules/regionArea.d.ts +0 -8
  143. package/dist/src/data/solver/backtrack/rules/regionArea.js +0 -71
  144. package/dist/src/data/solver/backtrack/rules/regionShape.d.ts +0 -8
  145. package/dist/src/data/solver/backtrack/rules/regionShape.js +0 -57
  146. package/dist/src/data/solver/backtrack/rules/sameShape.d.ts +0 -8
  147. package/dist/src/data/solver/backtrack/rules/sameShape.js +0 -14
  148. package/dist/src/data/solver/backtrack/rules/symbolsPerRegion.d.ts +0 -10
  149. package/dist/src/data/solver/backtrack/rules/symbolsPerRegion.js +0 -82
  150. package/dist/src/data/solver/backtrack/rules/uniqueShape.d.ts +0 -8
  151. package/dist/src/data/solver/backtrack/rules/uniqueShape.js +0 -14
  152. package/dist/src/data/solver/backtrack/symbols/areaNumber.d.ts +0 -9
  153. package/dist/src/data/solver/backtrack/symbols/areaNumber.js +0 -75
  154. package/dist/src/data/solver/backtrack/symbols/dart.d.ts +0 -8
  155. package/dist/src/data/solver/backtrack/symbols/dart.js +0 -45
  156. package/dist/src/data/solver/backtrack/symbols/directionLinker.d.ts +0 -11
  157. package/dist/src/data/solver/backtrack/symbols/directionLinker.js +0 -121
  158. package/dist/src/data/solver/backtrack/symbols/focus.d.ts +0 -9
  159. package/dist/src/data/solver/backtrack/symbols/focus.js +0 -48
  160. package/dist/src/data/solver/backtrack/symbols/galaxy.d.ts +0 -9
  161. package/dist/src/data/solver/backtrack/symbols/galaxy.js +0 -14
  162. package/dist/src/data/solver/backtrack/symbols/letter.d.ts +0 -9
  163. package/dist/src/data/solver/backtrack/symbols/letter.js +0 -95
  164. package/dist/src/data/solver/backtrack/symbols/lotus.d.ts +0 -11
  165. package/dist/src/data/solver/backtrack/symbols/lotus.js +0 -55
  166. package/dist/src/data/solver/backtrack/symbols/minesweeper.d.ts +0 -9
  167. package/dist/src/data/solver/backtrack/symbols/minesweeper.js +0 -44
  168. package/dist/src/data/solver/backtrack/symbols/myopia.d.ts +0 -7
  169. package/dist/src/data/solver/backtrack/symbols/myopia.js +0 -73
  170. package/dist/src/data/solver/backtrack/symbols/viewpoint.d.ts +0 -7
  171. package/dist/src/data/solver/backtrack/symbols/viewpoint.js +0 -51
  172. package/dist/src/data/solver/cspuz/cspuzSolver.d.ts +0 -13
  173. package/dist/src/data/solver/cspuz/cspuzSolver.js +0 -124
  174. package/dist/src/data/solver/cspuz/cspuzWorker.d.ts +0 -1
  175. package/dist/src/data/solver/cspuz/cspuzWorker.js +0 -82
  176. package/dist/src/data/solver/cspuz/jsonify.d.ts +0 -3
  177. package/dist/src/data/solver/cspuz/jsonify.js +0 -215
  178. package/dist/src/data/solver/eventIteratingSolver.d.ts +0 -8
  179. package/dist/src/data/solver/eventIteratingSolver.js +0 -54
  180. package/dist/src/data/solver/solver.d.ts +0 -77
  181. package/dist/src/data/solver/solver.js +0 -59
  182. package/dist/src/data/solver/universal/universalSolver.d.ts +0 -7
  183. package/dist/src/data/solver/universal/universalSolver.js +0 -13
  184. package/dist/src/data/solver/universal/universalWorker.d.ts +0 -1
  185. package/dist/src/data/solver/universal/universalWorker.js +0 -128
  186. package/dist/src/data/symbols/areaNumberSymbol.d.ts +0 -31
  187. package/dist/src/data/symbols/areaNumberSymbol.js +0 -80
  188. package/dist/src/data/symbols/customIconSymbol.d.ts +0 -35
  189. package/dist/src/data/symbols/customIconSymbol.js +0 -94
  190. package/dist/src/data/symbols/customSymbol.d.ts +0 -25
  191. package/dist/src/data/symbols/customSymbol.js +0 -45
  192. package/dist/src/data/symbols/customTextSymbol.d.ts +0 -35
  193. package/dist/src/data/symbols/customTextSymbol.js +0 -95
  194. package/dist/src/data/symbols/dartSymbol.d.ts +0 -36
  195. package/dist/src/data/symbols/dartSymbol.js +0 -96
  196. package/dist/src/data/symbols/directionLinkerSymbol.d.ts +0 -29
  197. package/dist/src/data/symbols/directionLinkerSymbol.js +0 -232
  198. package/dist/src/data/symbols/everyLetterSymbol.d.ts +0 -32
  199. package/dist/src/data/symbols/everyLetterSymbol.js +0 -119
  200. package/dist/src/data/symbols/focusSymbol.d.ts +0 -40
  201. package/dist/src/data/symbols/focusSymbol.js +0 -159
  202. package/dist/src/data/symbols/galaxySymbol.d.ts +0 -27
  203. package/dist/src/data/symbols/galaxySymbol.js +0 -61
  204. package/dist/src/data/symbols/hiddenSymbol.d.ts +0 -38
  205. package/dist/src/data/symbols/hiddenSymbol.js +0 -113
  206. package/dist/src/data/symbols/houseSymbol.d.ts +0 -33
  207. package/dist/src/data/symbols/houseSymbol.js +0 -104
  208. package/dist/src/data/symbols/index.d.ts +0 -3
  209. package/dist/src/data/symbols/index.js +0 -10
  210. package/dist/src/data/symbols/letterSymbol.d.ts +0 -32
  211. package/dist/src/data/symbols/letterSymbol.js +0 -118
  212. package/dist/src/data/symbols/lotusSymbol.d.ts +0 -30
  213. package/dist/src/data/symbols/lotusSymbol.js +0 -132
  214. package/dist/src/data/symbols/minesweeperSymbol.d.ts +0 -33
  215. package/dist/src/data/symbols/minesweeperSymbol.js +0 -106
  216. package/dist/src/data/symbols/myopiaSymbol.d.ts +0 -37
  217. package/dist/src/data/symbols/myopiaSymbol.js +0 -182
  218. package/dist/src/data/symbols/numberSymbol.d.ts +0 -19
  219. package/dist/src/data/symbols/numberSymbol.js +0 -32
  220. package/dist/src/data/symbols/symbol.d.ts +0 -29
  221. package/dist/src/data/symbols/symbol.js +0 -87
  222. package/dist/src/data/symbols/symbols.gen.d.ts +0 -15
  223. package/dist/src/data/symbols/symbols.gen.js +0 -19
  224. package/dist/src/data/symbols/unsupportedSymbol.d.ts +0 -23
  225. package/dist/src/data/symbols/unsupportedSymbol.js +0 -47
  226. package/dist/src/data/symbols/viewpointSymbol.d.ts +0 -32
  227. package/dist/src/data/symbols/viewpointSymbol.js +0 -95
  228. package/dist/src/data/tile.d.ts +0 -26
  229. package/dist/src/data/tile.js +0 -56
  230. package/dist/src/data/tileConnections.d.ts +0 -25
  231. package/dist/src/data/tileConnections.js +0 -74
  232. package/dist/src/data/validate.d.ts +0 -5
  233. package/dist/src/data/validate.js +0 -131
  234. package/dist/src/data/validateAsync.d.ts +0 -15
  235. package/dist/src/data/validateAsync.js +0 -71
  236. package/dist/src/data/validateAsyncWorker.d.ts +0 -1
  237. package/dist/src/data/validateAsyncWorker.js +0 -9
  238. package/dist/src/index.d.ts +0 -109
  239. package/dist/src/index.js +0 -112
  240. package/dist/src/polyfill/streamPolyfill.d.ts +0 -2
  241. package/dist/src/polyfill/streamPolyfill.js +0 -1
@@ -1,1106 +0,0 @@
1
- import { handlesGridChange } from './events/onGridChange.js';
2
- import { handlesGridResize } from './events/onGridResize.js';
3
- import { handlesSetGrid } from './events/onSetGrid.js';
4
- import GridConnections from './gridConnections.js';
5
- import { CachedAccess, array, move } from './dataHelper.js';
6
- import { Color, MajorRule, } from './primitives.js';
7
- import TileData from './tile.js';
8
- import GridZones from './gridZones.js';
9
- export const NEIGHBOR_OFFSETS = [
10
- { x: -1, y: 0 },
11
- { x: 1, y: 0 },
12
- { x: 0, y: -1 },
13
- { x: 0, y: 1 },
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
- ];
25
- export default class GridData {
26
- width;
27
- height;
28
- tiles;
29
- connections;
30
- zones;
31
- symbols;
32
- rules;
33
- // Important rules are cached for quick access
34
- /* eslint-disable @typescript-eslint/no-unsafe-enum-comparison */
35
- musicGrid = CachedAccess.of(() => this.findRule(rule => rule.id === MajorRule.MusicGrid));
36
- completePattern = CachedAccess.of(() => this.findRule(rule => rule.id === MajorRule.CompletePattern));
37
- underclued = CachedAccess.of(() => this.findRule(rule => rule.id === MajorRule.Underclued));
38
- wrapAround = CachedAccess.of(() => this.findRule(rule => rule.id === MajorRule.WrapAround));
39
- /* eslint-enable @typescript-eslint/no-unsafe-enum-comparison */
40
- /**
41
- * Create a new grid with tiles, connections, symbols and rules.
42
- *
43
- * @param width The width of the grid.
44
- * @param height The height of the grid.
45
- * @param tiles The tiles of the grid.
46
- * @param connections The connections of the grid, which determines which tiles are merged.
47
- * @param zones The zones of the grid.
48
- * @param symbols The symbols in the grid.
49
- * @param rules The rules of the grid.
50
- */
51
- constructor(width, height, tiles, connections, zones, symbols, rules) {
52
- this.width = width;
53
- this.height = height;
54
- this.width = width;
55
- this.height = height;
56
- this.tiles = tiles ?? array(width, height, () => TileData.empty());
57
- this.connections = connections ?? new GridConnections();
58
- this.zones = zones ?? new GridZones();
59
- this.symbols = symbols ?? new Map();
60
- this.rules = rules ?? [];
61
- }
62
- static create(arrayOrWidth, height, tiles, connections, zones, symbols, rules, sanitize, triggerEvents) {
63
- if (typeof arrayOrWidth === 'number') {
64
- let hasGridChangeSymbols = false;
65
- let hasGridChangeRules = false;
66
- if (triggerEvents) {
67
- symbols?.forEach(list => {
68
- list.forEach(sym => {
69
- if (handlesGridChange(sym)) {
70
- hasGridChangeSymbols = true;
71
- }
72
- });
73
- });
74
- rules?.forEach(rule => {
75
- if (handlesGridChange(rule)) {
76
- hasGridChangeRules = true;
77
- }
78
- });
79
- }
80
- const newSymbols = symbols
81
- ? sanitize
82
- ? GridData.deduplicateSymbols(symbols)
83
- : triggerEvents && hasGridChangeSymbols
84
- ? new Map([...symbols.entries()].map(([id, list]) => [id, list.slice()]))
85
- : symbols
86
- : new Map();
87
- // do not deduplicate all rules because it makes for bad editor experience
88
- const newRules = rules
89
- ? sanitize
90
- ? GridData.deduplicateSingletonRules(rules)
91
- : triggerEvents && hasGridChangeRules
92
- ? rules.slice()
93
- : rules
94
- : [];
95
- const newGrid = new GridData(arrayOrWidth, height, tiles, connections
96
- ? sanitize
97
- ? GridConnections.validateEdges(connections, arrayOrWidth, height)
98
- : connections
99
- : undefined, zones
100
- ? sanitize
101
- ? GridZones.validateEdges(zones, arrayOrWidth, height)
102
- : zones
103
- : undefined, newSymbols, newRules);
104
- if (triggerEvents) {
105
- newSymbols.forEach(list => {
106
- list.forEach((sym, i) => {
107
- if (handlesGridChange(sym)) {
108
- list[i] = sym.onGridChange(newGrid);
109
- }
110
- });
111
- });
112
- newRules.forEach((rule, i) => {
113
- if (handlesGridChange(rule)) {
114
- newRules[i] = rule.onGridChange(newGrid);
115
- }
116
- });
117
- }
118
- return newGrid;
119
- }
120
- else {
121
- const tiles = GridData.createTiles(arrayOrWidth);
122
- return GridData.create(tiles[0]?.length ?? 0, tiles.length, tiles);
123
- }
124
- }
125
- /**
126
- * Copy the current grid while modifying the provided properties.
127
- * @param param0 The properties to modify.
128
- * @returns The new grid with the modified properties.
129
- */
130
- copyWith({ width, height, tiles, connections, zones, symbols, rules, }, sanitize = true, triggerEvents = true) {
131
- return GridData.create(width ?? this.width, height ?? this.height, tiles ?? this.tiles, connections ?? this.connections, zones ?? this.zones, symbols ?? this.symbols, rules ?? this.rules, sanitize, triggerEvents);
132
- }
133
- toArrayCoordinates(x, y) {
134
- // // This is the preferred way to compute tile coordinates, but for performance reasons we will just access the
135
- // // wrap-around rule directly.
136
- // this.rules.forEach(rule => {
137
- // if (handlesGetTile(rule)) {
138
- // ({ x, y } = rule.onGetTile(x, y));
139
- // }
140
- // });
141
- // this.symbols.forEach(list =>
142
- // list.forEach(symbol => {
143
- // if (handlesGetTile(symbol)) {
144
- // ({ x, y } = symbol.onGetTile(x, y));
145
- // }
146
- // })
147
- // );
148
- if (this.wrapAround.value) {
149
- return this.wrapAround.value.onGetTile(x, y, this);
150
- }
151
- else {
152
- return { x, y };
153
- }
154
- }
155
- isPositionValid(x, y) {
156
- ({ x, y } = this.toArrayCoordinates(x, y));
157
- return x >= 0 && x < this.width && y >= 0 && y < this.height;
158
- }
159
- /**
160
- * Safely get the tile at the given position.
161
- * @param x The x-coordinate of the tile.
162
- * @param y The y-coordinate of the tile.
163
- * @returns The tile at the given position, or a non-existent tile if the position is invalid.
164
- */
165
- getTile(x, y) {
166
- ({ x, y } = this.toArrayCoordinates(x, y));
167
- if (x < 0 || x >= this.width || y < 0 || y >= this.height)
168
- return TileData.doesNotExist();
169
- return this.tiles[y][x];
170
- }
171
- /**
172
- * Safely set the tile at the given position.
173
- * If the position is invalid, the tile array is returned unchanged.
174
- * If the tile is merged with other tiles, the colors of all connected tiles are changed.
175
- *
176
- * @param x The x-coordinate of the tile.
177
- * @param y The y-coordinate of the tile.
178
- * @param tile The new tile to set.
179
- * @returns The new tile array with updated tiles.
180
- */
181
- setTile(x, y, tile) {
182
- ({ x, y } = this.toArrayCoordinates(x, y));
183
- if (x < 0 || x >= this.width || y < 0 || y >= this.height) {
184
- return this.tiles;
185
- }
186
- const changing = this.connections.getConnectedTiles({ x, y });
187
- const tiles = this.tiles.map(row => [...row]);
188
- const newTile = typeof tile === 'function' ? tile(tiles[y][x]) : tile;
189
- changing.forEach(({ x, y }) => {
190
- ({ x, y } = this.toArrayCoordinates(x, y));
191
- tiles[y][x] = tiles[y][x].withColor(newTile.color);
192
- });
193
- tiles[y][x] = newTile;
194
- return tiles;
195
- }
196
- /**
197
- * Replace or modify all tiles in the grid.
198
- *
199
- * @param tiles The new tile array or a function to mutate the existing tile array.
200
- * @returns The new grid with the new tiles.
201
- */
202
- withTiles(tiles) {
203
- return this.copyWith({
204
- tiles: typeof tiles === 'function'
205
- ? tiles(this.tiles.map(row => row.slice()))
206
- : tiles,
207
- });
208
- }
209
- /**
210
- * Add or modify the connections in the grid.
211
- * @param connections The new connections to add or modify.
212
- * @returns The new grid with the new connections.
213
- */
214
- withConnections(connections) {
215
- return this.copyWith({
216
- connections: typeof connections === 'function'
217
- ? connections(this.connections)
218
- : connections,
219
- });
220
- }
221
- /**
222
- * Add or modify the zones in the grid.
223
- * @param zones The new zones to add or modify.
224
- * @returns The new grid with the new zones.
225
- */
226
- withZones(zones) {
227
- return this.copyWith({
228
- zones: typeof zones === 'function' ? zones(this.zones) : zones,
229
- });
230
- }
231
- /**
232
- * Add or modify the symbols in the grid.
233
- * @param symbols The new symbols to add or modify.
234
- * @returns The new grid with the new symbols.
235
- */
236
- withSymbols(symbols) {
237
- if (symbols instanceof Array) {
238
- const map = new Map();
239
- for (const symbol of symbols) {
240
- if (map.has(symbol.id)) {
241
- map.set(symbol.id, [...map.get(symbol.id), symbol]);
242
- }
243
- else {
244
- map.set(symbol.id, [symbol]);
245
- }
246
- }
247
- return this.copyWith({ symbols: map });
248
- }
249
- return this.copyWith({
250
- symbols: typeof symbols === 'function'
251
- ? symbols(new Map(this.symbols))
252
- : symbols,
253
- });
254
- }
255
- /**
256
- * Add a new symbol to the grid.
257
- * @param symbol The symbol to add.
258
- * @returns The new grid with the new symbol.
259
- */
260
- addSymbol(symbol) {
261
- return this.withSymbols(map => {
262
- if (map.has(symbol.id)) {
263
- return map.set(symbol.id, [...map.get(symbol.id), symbol]);
264
- }
265
- else {
266
- return map.set(symbol.id, [symbol]);
267
- }
268
- });
269
- }
270
- /**
271
- * Remove an instance of the symbol from the grid.
272
- * @param symbol The symbol to remove.
273
- * @returns The new grid with the symbol removed.
274
- */
275
- removeSymbol(symbol) {
276
- return this.withSymbols(map => {
277
- if (map.has(symbol.id)) {
278
- const symbols = map.get(symbol.id).filter(s => s !== symbol);
279
- if (symbols.length === 0) {
280
- map.delete(symbol.id);
281
- }
282
- else {
283
- map.set(symbol.id, symbols);
284
- }
285
- }
286
- return map;
287
- });
288
- }
289
- /**
290
- * Remove all symbols that satisfy the predicate.
291
- * @param predicate The predicate to test each symbol with.
292
- * @returns The new grid with the symbols removed.
293
- */
294
- removeSymbolIf(predicate) {
295
- return this.withSymbols(map => {
296
- for (const [id, symbols] of map) {
297
- const newSymbols = symbols.filter(sym => !predicate(sym));
298
- if (newSymbols.length === 0) {
299
- map.delete(id);
300
- }
301
- else {
302
- map.set(id, newSymbols);
303
- }
304
- }
305
- return map;
306
- });
307
- }
308
- /**
309
- * Find the first symbol that satisfies the predicate.
310
- * @param predicate The predicate to test each symbol with.
311
- * @returns The first symbol that satisfies the predicate, or undefined if no symbol is found.
312
- */
313
- findSymbol(predicate) {
314
- for (const symbols of this.symbols.values()) {
315
- const symbol = symbols.find(predicate);
316
- if (symbol)
317
- return symbol;
318
- }
319
- }
320
- /**
321
- * Replace an existing symbol with a new symbol.
322
- * @param oldSymbol The symbol to replace.
323
- * @param newSymbol The new symbol to replace with.
324
- * @returns The new grid with the symbol replaced.
325
- */
326
- replaceSymbol(oldSymbol, newSymbol) {
327
- return this.withSymbols(map => {
328
- if (map.has(oldSymbol.id)) {
329
- const symbols = map
330
- .get(oldSymbol.id)
331
- .map(s => (s === oldSymbol ? newSymbol : s));
332
- map.set(oldSymbol.id, symbols);
333
- }
334
- return map;
335
- });
336
- }
337
- /**
338
- * Add or modify the rules in the grid.
339
- * @param rules The new rules to add or modify.
340
- * @returns The new grid with the new rules.
341
- */
342
- withRules(rules) {
343
- return this.copyWith({
344
- rules: typeof rules === 'function' ? rules(this.rules) : rules,
345
- });
346
- }
347
- /**
348
- * Add a new rule to the grid.
349
- * @param rule The rule to add.
350
- * @returns The new grid with the new rule.
351
- */
352
- addRule(rule) {
353
- return this.withRules(rules => [...rules, rule]);
354
- }
355
- /**
356
- * Remove an instance of the rule from the grid.
357
- * @param rule The rule to remove.
358
- * @returns The new grid with the rule removed.
359
- */
360
- removeRule(rule) {
361
- return this.withRules(rules => rules.filter(r => r !== rule));
362
- }
363
- /**
364
- * Remove all rules that satisfy the predicate.
365
- * @param predicate The predicate to test each rule with.
366
- * @returns The new grid with the rules removed.
367
- */
368
- removeRuleIf(predicate) {
369
- return this.withRules(rules => rules.filter(r => !predicate(r)));
370
- }
371
- /**
372
- * Find the first rule that satisfies the predicate.
373
- * @param predicate The predicate to test each rule with.
374
- * @returns The first rule that satisfies the predicate, or undefined if no rule is found.
375
- */
376
- findRule(predicate) {
377
- return this.rules.find(predicate);
378
- }
379
- /**
380
- * Replace an existing rule with a new rule.
381
- * @param oldRule The rule to replace.
382
- * @param newRule The new rule to replace with.
383
- * @returns The new grid with the rule replaced.
384
- */
385
- replaceRule(oldRule, newRule) {
386
- return this.withRules(rules => rules.map(r => (r === oldRule ? newRule : r)));
387
- }
388
- /**
389
- * Insert a new column at the given index, shifting all components of the grid accordingly. Newly inserted tiles are gray.
390
- * @param index The index to insert the column at.
391
- * @returns The new grid with the new column inserted.
392
- */
393
- insertColumn(index) {
394
- if (index < 0 || index > this.width)
395
- return this;
396
- const tiles = array(this.width + 1, this.height, (x, y) => {
397
- if (x < index)
398
- return this.getTile(x, y);
399
- if (x === index)
400
- return TileData.empty();
401
- return this.getTile(x - 1, y);
402
- });
403
- const connections = this.connections.insertColumn(index);
404
- const zones = this.zones.insertColumn(index);
405
- const rules = this.rules
406
- .map(rule => {
407
- if (handlesGridResize(rule))
408
- return rule.onGridResize(this, 'insert', 'column', index);
409
- else
410
- return rule;
411
- })
412
- .filter(rule => rule !== null);
413
- const symbols = new Map();
414
- for (const [id, symbolList] of this.symbols) {
415
- const newList = symbolList
416
- .map(symbol => symbol.onGridResize(this, 'insert', 'column', index))
417
- .filter(symbol => symbol !== null);
418
- if (newList.length > 0)
419
- symbols.set(id, newList);
420
- }
421
- return this.copyWith({
422
- width: this.width + 1,
423
- tiles,
424
- connections,
425
- zones,
426
- rules,
427
- symbols,
428
- });
429
- }
430
- /**
431
- * Insert a new row at the given index, shifting all components of the grid accordingly. Newly inserted tiles are gray.
432
- * @param index The index to insert the row at.
433
- * @returns The new grid with the new row inserted.
434
- */
435
- insertRow(index) {
436
- if (index < 0 || index > this.height)
437
- return this;
438
- const tiles = array(this.width, this.height + 1, (x, y) => {
439
- if (y < index)
440
- return this.getTile(x, y);
441
- if (y === index)
442
- return TileData.empty();
443
- return this.getTile(x, y - 1);
444
- });
445
- const connections = this.connections.insertRow(index);
446
- const zones = this.zones.insertRow(index);
447
- const rules = this.rules
448
- .map(rule => {
449
- if (handlesGridResize(rule))
450
- return rule.onGridResize(this, 'insert', 'row', index);
451
- else
452
- return rule;
453
- })
454
- .filter(rule => rule !== null);
455
- const symbols = new Map();
456
- for (const [id, symbolList] of this.symbols) {
457
- const newList = symbolList
458
- .map(symbol => symbol.onGridResize(this, 'insert', 'row', index))
459
- .filter(symbol => symbol !== null);
460
- if (newList.length > 0)
461
- symbols.set(id, newList);
462
- }
463
- return this.copyWith({
464
- height: this.height + 1,
465
- tiles,
466
- connections,
467
- zones,
468
- rules,
469
- symbols,
470
- });
471
- }
472
- /**
473
- * Remove a column at the given index, shifting all components of the grid accordingly.
474
- * @param index The index to remove the column at.
475
- * @returns The new grid with the column removed.
476
- */
477
- removeColumn(index) {
478
- if (index < 0 || index >= this.width)
479
- return this;
480
- const tiles = array(this.width - 1, this.height, (x, y) => x < index ? this.getTile(x, y) : this.getTile(x + 1, y));
481
- const connections = this.connections.removeColumn(index);
482
- const zones = this.zones.removeColumn(index);
483
- const rules = this.rules
484
- .map(rule => {
485
- if (handlesGridResize(rule))
486
- return rule.onGridResize(this, 'remove', 'column', index);
487
- else
488
- return rule;
489
- })
490
- .filter(rule => rule !== null);
491
- const symbols = new Map();
492
- for (const [id, symbolList] of this.symbols) {
493
- const newList = symbolList
494
- .map(symbol => symbol.onGridResize(this, 'remove', 'column', index))
495
- .filter(symbol => symbol !== null);
496
- if (newList.length > 0)
497
- symbols.set(id, newList);
498
- }
499
- return this.copyWith({
500
- width: this.width - 1,
501
- tiles,
502
- connections,
503
- zones,
504
- rules,
505
- symbols,
506
- });
507
- }
508
- /**
509
- * Remove a row at the given index, shifting all components of the grid accordingly.
510
- * @param index The index to remove the row at.
511
- * @returns The new grid with the row removed.
512
- */
513
- removeRow(index) {
514
- if (index < 0 || index >= this.height)
515
- return this;
516
- const tiles = array(this.width, this.height - 1, (x, y) => y < index ? this.getTile(x, y) : this.getTile(x, y + 1));
517
- const connections = this.connections.removeRow(index);
518
- const zones = this.zones.removeRow(index);
519
- const rules = this.rules
520
- .map(rule => {
521
- if (handlesGridResize(rule))
522
- return rule.onGridResize(this, 'remove', 'row', index);
523
- else
524
- return rule;
525
- })
526
- .filter(rule => rule !== null);
527
- const symbols = new Map();
528
- for (const [id, symbolList] of this.symbols) {
529
- const newList = symbolList
530
- .map(symbol => symbol.onGridResize(this, 'remove', 'row', index))
531
- .filter(symbol => symbol !== null);
532
- if (newList.length > 0)
533
- symbols.set(id, newList);
534
- }
535
- return this.copyWith({
536
- height: this.height - 1,
537
- tiles,
538
- connections,
539
- zones,
540
- rules,
541
- symbols,
542
- });
543
- }
544
- /**
545
- * Resize the grid to the new width and height, shifting all components of the grid accordingly. Newly inserted tiles are gray.
546
- * @param width The new width of the grid.
547
- * @param height The new height of the grid.
548
- * @returns The new grid with the new dimensions.
549
- */
550
- resize(width, height) {
551
- if (width < 0 || height < 0)
552
- throw new Error(`Invalid grid size: ${width}x${height}`);
553
- // eslint-disable-next-line @typescript-eslint/no-this-alias
554
- let newGrid = this;
555
- while (newGrid.width < width)
556
- newGrid = newGrid.insertColumn(newGrid.width);
557
- while (newGrid.width > width)
558
- newGrid = newGrid.removeColumn(newGrid.width - 1);
559
- while (newGrid.height < height)
560
- newGrid = newGrid.insertRow(newGrid.height);
561
- while (newGrid.height > height)
562
- newGrid = newGrid.removeRow(newGrid.height - 1);
563
- return newGrid;
564
- }
565
- /**
566
- * Create a new mutable TileData array from a string array.
567
- *
568
- * - Use `b` for dark cells, `w` for light cells, and `n` for gray cells.
569
- * - Capitalize the letter to make the tile fixed.
570
- * - Use `.` to represent empty space.
571
- *
572
- * @param array - The string array to create the tiles from.
573
- * @returns The created tile array.
574
- */
575
- static createTiles(array) {
576
- const width = array.reduce((max, row) => Math.max(max, row.length), 0);
577
- return array.map(row => Array.from({ length: width }, (_, x) => {
578
- return TileData.create(row.charAt(x));
579
- }));
580
- }
581
- /**
582
- * Find a tile in the grid that satisfies the predicate.
583
- *
584
- * @param predicate The predicate to test each tile with.
585
- * @returns The position of the first tile that satisfies the predicate, or undefined if no tile is found.
586
- */
587
- find(predicate) {
588
- for (let y = 0; y < this.height; y++) {
589
- for (let x = 0; x < this.width; x++) {
590
- if (predicate(this.getTile(x, y), x, y)) {
591
- return { x, y };
592
- }
593
- }
594
- }
595
- return undefined;
596
- }
597
- /**
598
- * Iterate over all tiles in the same region as the given position that satisfy the predicate.
599
- * The iteration stops when the callback returns a value that is not undefined.
600
- * Non-existent tiles are not included in the iteration.
601
- *
602
- * @param position The position to start the iteration from. This position is included in the iteration.
603
- * @param predicate The predicate to test each tile with. The callback is only called for tiles that satisfy this predicate.
604
- * @param callback The callback to call for each tile that satisfies the predicate. The iteration stops when this callback returns a value that is not undefined.
605
- * @param visited A 2D array to keep track of visited tiles. This array is modified by the function.
606
- * @returns The value returned by the callback that stopped the iteration, or undefined if the iteration completed.
607
- */
608
- iterateArea(position, predicate, callback, visited = array(this.width, this.height, () => false)) {
609
- const tile = this.getTile(position.x, position.y);
610
- if (!tile.exists || !predicate(tile, position.x, position.y)) {
611
- return;
612
- }
613
- const stack = [position];
614
- while (stack.length > 0) {
615
- const { x, y } = stack.pop();
616
- const { x: arrX, y: arrY } = this.toArrayCoordinates(x, y);
617
- if (visited[arrY][arrX]) {
618
- continue;
619
- }
620
- visited[arrY][arrX] = true;
621
- const ret = callback(this.getTile(x, y), arrX, arrY, x, y);
622
- if (ret !== undefined)
623
- return ret;
624
- for (const offset of NEIGHBOR_OFFSETS) {
625
- const next = { x: x + offset.x, y: y + offset.y };
626
- if (this.isPositionValid(next.x, next.y)) {
627
- const nextTile = this.getTile(next.x, next.y);
628
- if (nextTile.exists && predicate(nextTile, next.x, next.y))
629
- stack.push(next);
630
- }
631
- }
632
- }
633
- }
634
- /**
635
- * Iterate over all tiles in a straight line from the given position in the given direction that satisfy the predicate.
636
- * The iteration stops when the callback returns a value that is not undefined.
637
- * Non-existent tiles break the iteration.
638
- *
639
- * @param position The position to start the iteration from. This position is included in the iteration.
640
- * @param direction The direction to iterate in.
641
- * @param predicate The predicate to test each tile with. The callback is only called for tiles that satisfy this predicate.
642
- * @param callback The callback to call for each tile that satisfies the predicate. The iteration stops when this callback returns a value that is not undefined.
643
- * @param visited A 2D array to keep track of visited tiles. This array is modified by the function.
644
- * @returns The value returned by the callback that stopped the iteration, or undefined if the iteration completed.
645
- */
646
- iterateDirection(position, direction, predicate, callback, visited = array(this.width, this.height, () => false)) {
647
- return this.iterateDirectionAll(position, direction, (tile, logicalX, logicalY) => tile.exists && predicate(tile, logicalX, logicalY), callback, visited);
648
- }
649
- /**
650
- * Iterate over all tiles in a straight line from the given position in the given direction that satisfy the predicate.
651
- * The iteration stops when the callback returns a value that is not undefined.
652
- * Non-existent tiles are included in the iteration.
653
- *
654
- * @param position The position to start the iteration from. This position is included in the iteration.
655
- * @param direction The direction to iterate in.
656
- * @param predicate The predicate to test each tile with. The callback is only called for tiles that satisfy this predicate.
657
- * @param callback The callback to call for each tile that satisfies the predicate. The iteration stops when this callback returns a value that is not undefined.
658
- * @param visited A 2D array to keep track of visited tiles. This array is modified by the function.
659
- * @returns The value returned by the callback that stopped the iteration, or undefined if the iteration completed.
660
- */
661
- iterateDirectionAll(position, direction, predicate, callback, visited = array(this.width, this.height, () => false)) {
662
- let current = position;
663
- while (this.isPositionValid(current.x, current.y)) {
664
- const arrPos = this.toArrayCoordinates(current.x, current.y);
665
- if (visited[arrPos.y][arrPos.x]) {
666
- break;
667
- }
668
- visited[arrPos.y][arrPos.x] = true;
669
- const tile = this.getTile(current.x, current.y);
670
- if (!predicate(tile, arrPos.x, arrPos.y)) {
671
- break;
672
- }
673
- const ret = callback(tile, arrPos.x, arrPos.y, current.x, current.y);
674
- if (ret !== undefined)
675
- return ret;
676
- current = move(current, direction);
677
- }
678
- }
679
- /**
680
- * Reduce the grid by zones defined in the GridZones.
681
- *
682
- * @param reducer The reducer function to apply to each zone.
683
- * @param initializer The initializer function to create the initial value for each zone.
684
- * @param visited A 2D array to keep track of visited tiles. This array is modified by the function.
685
- * @returns An array of reduced values, one for each zone.
686
- */
687
- reduceByZone(reducer, initializer, visited = array(this.width, this.height, () => false)) {
688
- const zones = [];
689
- while (true) {
690
- const seed = this.find((tile, x, y) => tile.exists && !visited[y][x]);
691
- if (!seed)
692
- break;
693
- let zone = initializer();
694
- const stack = [seed];
695
- while (stack.length > 0) {
696
- const { x, y } = stack.pop();
697
- const { x: arrX, y: arrY } = this.toArrayCoordinates(x, y);
698
- if (visited[arrY][arrX])
699
- continue;
700
- visited[arrY][arrX] = true;
701
- zone = reducer(zone, this.getTile(arrX, arrY), arrX, arrY, x, y);
702
- for (const offset of NEIGHBOR_OFFSETS) {
703
- const next = this.toArrayCoordinates(x + offset.x, y + offset.y);
704
- if (!this.zones.edges.some(e => {
705
- const { x: x1, y: y1 } = this.toArrayCoordinates(e.x1, e.y1);
706
- const { x: x2, y: y2 } = this.toArrayCoordinates(e.x2, e.y2);
707
- return ((x1 === arrX &&
708
- y1 === arrY &&
709
- x2 === next.x &&
710
- y2 === next.y) ||
711
- (x2 === arrX && y2 === arrY && x1 === next.x && y1 === next.y));
712
- })) {
713
- const nextTile = this.getTile(next.x, next.y);
714
- if (nextTile.exists) {
715
- stack.push(next);
716
- }
717
- }
718
- }
719
- }
720
- zones.push(zone);
721
- }
722
- return zones;
723
- }
724
- /**
725
- * Check if every tile in the grid is filled with a color other than gray.
726
- *
727
- * @returns True if every tile is filled with a color other than gray, false otherwise.
728
- */
729
- isComplete() {
730
- return this.tiles.every(row => row.every(tile => !tile.exists || tile.color !== Color.Gray));
731
- }
732
- /**
733
- * Iterate over all tiles in the grid.
734
- * The iteration stops when the callback returns a value that is not undefined.
735
- *
736
- * @param callback The callback to call for each tile.
737
- * @returns The value returned by the callback that stopped the iteration, or undefined if the iteration completed.
738
- */
739
- forEach(callback) {
740
- for (let y = 0; y < this.height; y++) {
741
- for (let x = 0; x < this.width; x++) {
742
- const ret = callback(this.getTile(x, y), x, y);
743
- if (ret !== undefined)
744
- return ret;
745
- }
746
- }
747
- }
748
- /**
749
- * Flood fill a continuous region starting from the given position with the given color.
750
- *
751
- * @param position The position to start the flood fill from.
752
- * @param from The color of the tiles to fill.
753
- * @param to The color to fill the tiles with.
754
- * @param allowFixed Whether to fill fixed tiles.
755
- * @returns The new grid with the region filled with the new color.
756
- */
757
- floodFill(position, from, to, allowFixed) {
758
- const tiles = array(this.width, this.height, (x, y) => this.getTile(x, y));
759
- this.iterateArea(position, t => t.color === from && (allowFixed || !t.fixed), (tile, x, y) => {
760
- tiles[y][x] = tile.withColor(to);
761
- });
762
- return this.copyWith({ tiles }, false);
763
- }
764
- /**
765
- * Flood fill all tiles with the given color to a new color, even if they are not connected.
766
- *
767
- * @param from The color of the tiles to fill.
768
- * @param to The color to fill the tiles with.
769
- * @param allowFixed Whether to fill fixed tiles.
770
- * @returns The new grid with all tiles filled with the new color.
771
- */
772
- floodFillAll(from, to, allowFixed) {
773
- return this.copyWith({
774
- tiles: this.tiles.map(row => row.map(tile => tile.color === from && (allowFixed || !tile.fixed)
775
- ? tile.withColor(to)
776
- : tile)),
777
- }, false);
778
- }
779
- /**
780
- * Check if the grid has any instructions that require a custom solution.
781
- * @returns True if the grid has any instructions that require a custom solution, false otherwise.
782
- */
783
- requireSolution() {
784
- if (this.rules.some(rule => rule.validateWithSolution))
785
- return true;
786
- if ([...this.symbols.values()].some(list => list.some(symbol => symbol.validateWithSolution)))
787
- return true;
788
- return false;
789
- }
790
- /**
791
- * Reset all non-fixed tiles to gray.
792
- *
793
- * @returns The new grid with all non-fixed tiles reset to gray.
794
- */
795
- resetTiles() {
796
- let changed = false;
797
- const newTiles = array(this.width, this.height, (x, y) => {
798
- const tile = this.getTile(x, y);
799
- if (tile.exists && !tile.fixed && tile.color !== Color.Gray) {
800
- changed = true;
801
- return tile.withColor(Color.Gray);
802
- }
803
- return tile;
804
- });
805
- if (!changed)
806
- return this;
807
- let newGrid = this.copyWith({ tiles: newTiles }, false);
808
- this.symbols.forEach(list => {
809
- list.forEach(symbol => {
810
- if (handlesSetGrid(symbol)) {
811
- newGrid = symbol.onSetGrid(this, newGrid, null);
812
- }
813
- });
814
- });
815
- this.rules.forEach(rule => {
816
- if (handlesSetGrid(rule)) {
817
- newGrid = rule.onSetGrid(this, newGrid, null);
818
- }
819
- });
820
- return newGrid;
821
- }
822
- /**
823
- * Copy the tiles in the given region to a new grid.
824
- * All connections and symbols within the selected region are copied.
825
- * All rules are included as well.
826
- *
827
- * @param origin The top-left corner of the region to copy.
828
- * @param width The width of the region to copy.
829
- * @param height The height of the region to copy.
830
- * @returns The new grid with the copied tiles.
831
- */
832
- copyTiles(origin, width, height) {
833
- const newTiles = array(width, height, (x, y) => this.getTile(origin.x + x, origin.y + y));
834
- const connections = new GridConnections(this.connections.edges
835
- .filter(edge => edge.x1 >= origin.x &&
836
- edge.y1 >= origin.y &&
837
- edge.x2 >= origin.x &&
838
- edge.y2 >= origin.y &&
839
- edge.x1 < origin.x + width &&
840
- edge.y1 < origin.y + height &&
841
- edge.x2 < origin.x + width &&
842
- edge.y2 < origin.y + height)
843
- .map(edge => ({
844
- x1: edge.x1 - origin.x,
845
- y1: edge.y1 - origin.y,
846
- x2: edge.x2 - origin.x,
847
- y2: edge.y2 - origin.y,
848
- })));
849
- const zones = new GridZones(this.zones.edges
850
- .filter(edge => edge.x1 >= origin.x &&
851
- edge.y1 >= origin.y &&
852
- edge.x2 >= origin.x &&
853
- edge.y2 >= origin.y &&
854
- edge.x1 < origin.x + width &&
855
- edge.y1 < origin.y + height &&
856
- edge.x2 < origin.x + width &&
857
- edge.y2 < origin.y + height)
858
- .map(edge => ({
859
- x1: edge.x1 - origin.x,
860
- y1: edge.y1 - origin.y,
861
- x2: edge.x2 - origin.x,
862
- y2: edge.y2 - origin.y,
863
- })));
864
- const symbols = new Map();
865
- for (const [id, symbolList] of this.symbols) {
866
- const newSymbolList = symbolList
867
- .filter(symbol => symbol.x >= origin.x &&
868
- symbol.y >= origin.y &&
869
- symbol.x < origin.x + width &&
870
- symbol.y < origin.y + height)
871
- .map(symbol => symbol.copyWith({
872
- x: symbol.x - origin.x,
873
- y: symbol.y - origin.y,
874
- }));
875
- if (newSymbolList.length > 0)
876
- symbols.set(id, newSymbolList);
877
- }
878
- return GridData.create(width, height, newTiles, connections, zones, symbols, this.rules);
879
- }
880
- pasteTiles(origin, grid) {
881
- if (!(grid instanceof GridData))
882
- return this.pasteTiles(origin, new GridData(grid[0].length, grid.length, grid));
883
- const newTiles = this.tiles.map(row => [...row]);
884
- grid.forEach((tile, x, y) => {
885
- if (this.isPositionValid(origin.x + x, origin.y + y))
886
- newTiles[origin.y + y][origin.x + x] = tile;
887
- });
888
- const connections = new GridConnections([
889
- ...this.connections.edges.filter(edge => edge.x1 < origin.x ||
890
- edge.y1 < origin.y ||
891
- edge.x2 < origin.x ||
892
- edge.y2 < origin.y ||
893
- edge.x1 >= origin.x + grid.width ||
894
- edge.y1 >= origin.y + grid.height ||
895
- edge.x2 >= origin.x + grid.width ||
896
- edge.y2 >= origin.y + grid.height),
897
- ...grid.connections.edges
898
- .map(edge => ({
899
- x1: edge.x1 + origin.x,
900
- y1: edge.y1 + origin.y,
901
- x2: edge.x2 + origin.x,
902
- y2: edge.y2 + origin.y,
903
- }))
904
- .filter(edge => edge.x1 >= 0 &&
905
- edge.y1 >= 0 &&
906
- edge.x2 >= 0 &&
907
- edge.y2 >= 0 &&
908
- edge.x1 < this.width &&
909
- edge.y1 < this.height &&
910
- edge.x2 < this.width &&
911
- edge.y2 < this.height),
912
- ]);
913
- const zones = new GridZones([
914
- ...this.zones.edges.filter(edge => edge.x1 < origin.x ||
915
- edge.y1 < origin.y ||
916
- edge.x2 < origin.x ||
917
- edge.y2 < origin.y ||
918
- edge.x1 >= origin.x + grid.width ||
919
- edge.y1 >= origin.y + grid.height ||
920
- edge.x2 >= origin.x + grid.width ||
921
- edge.y2 >= origin.y + grid.height),
922
- ...grid.zones.edges
923
- .map(edge => ({
924
- x1: edge.x1 + origin.x,
925
- y1: edge.y1 + origin.y,
926
- x2: edge.x2 + origin.x,
927
- y2: edge.y2 + origin.y,
928
- }))
929
- .filter(edge => edge.x1 >= 0 &&
930
- edge.y1 >= 0 &&
931
- edge.x2 >= 0 &&
932
- edge.y2 >= 0 &&
933
- edge.x1 < this.width &&
934
- edge.y1 < this.height &&
935
- edge.x2 < this.width &&
936
- edge.y2 < this.height),
937
- ]);
938
- const symbols = new Map(this.symbols);
939
- for (const [id, sourceList] of grid.symbols) {
940
- const symbolList = sourceList
941
- .filter(symbol => symbol.x + origin.x >= 0 &&
942
- symbol.y + origin.y >= 0 &&
943
- symbol.x + origin.x < this.width &&
944
- symbol.y + origin.y < this.height)
945
- .map(symbol => symbol.copyWith({ x: symbol.x + origin.x, y: symbol.y + origin.y }));
946
- if (symbols.has(id)) {
947
- const newList = [
948
- ...symbols
949
- .get(id)
950
- .filter(symbol => symbol.x < origin.x ||
951
- symbol.y < origin.y ||
952
- symbol.x >= origin.x + grid.width ||
953
- symbol.y >= origin.y + grid.height),
954
- ...symbolList,
955
- ];
956
- if (newList.length > 0)
957
- symbols.set(id, newList);
958
- else
959
- symbols.delete(id);
960
- }
961
- else if (symbolList.length > 0) {
962
- symbols.set(id, symbolList);
963
- }
964
- }
965
- for (const id of [...symbols.keys()]) {
966
- if (grid.symbols.has(id))
967
- continue;
968
- const newList = symbols
969
- .get(id)
970
- .filter(symbol => symbol.x < origin.x ||
971
- symbol.y < origin.y ||
972
- symbol.x >= origin.x + grid.width ||
973
- symbol.y >= origin.y + grid.height);
974
- if (newList.length > 0)
975
- symbols.set(id, newList);
976
- else
977
- symbols.delete(id);
978
- }
979
- const rules = GridData.deduplicateRules([...this.rules, ...grid.rules]);
980
- return this.copyWith({
981
- tiles: newTiles,
982
- connections,
983
- zones,
984
- symbols,
985
- rules,
986
- });
987
- }
988
- /**
989
- * Check if this grid is equal to another grid in terms of size and tile colors.
990
- * Rules, symbols, and connections are not compared.
991
- *
992
- * @param grid The grid to compare with.
993
- * @returns True if the grids are equal in size and tile colors, false otherwise.
994
- */
995
- colorEquals(grid) {
996
- return (this.width === grid.width &&
997
- this.height === grid.height &&
998
- this.tiles.every((row, y) => row.every((tile, x) => (!tile.exists && !grid.getTile(x, y).exists) ||
999
- tile.color === grid.getTile(x, y).color)));
1000
- }
1001
- /**
1002
- * Check if this grid is equal to another grid in terms of size, tile colors, connections, symbols, and rules.
1003
- *
1004
- * @param other The grid to compare with.
1005
- * @returns True if the grids are equal, false otherwise.
1006
- */
1007
- equals(other) {
1008
- if (this.width !== other.width)
1009
- return false;
1010
- if (this.height !== other.height)
1011
- return false;
1012
- if (this.tiles.some((row, y) => row.some((tile, x) => !tile.equals(other.getTile(x, y)))))
1013
- return false;
1014
- if (!this.connections.equals(other.connections))
1015
- return false;
1016
- if (!this.zones.equals(other.zones))
1017
- return false;
1018
- if (this.symbols.size !== other.symbols.size)
1019
- return false;
1020
- for (const [id, symbols] of this.symbols) {
1021
- const otherSymbols = other.symbols.get(id);
1022
- if (!otherSymbols || symbols.length !== otherSymbols.length)
1023
- return false;
1024
- for (const symbol of symbols) {
1025
- if (!otherSymbols.some(s => symbol.equals(s)))
1026
- return false;
1027
- }
1028
- }
1029
- if (this.rules.length !== other.rules.length)
1030
- return false;
1031
- for (const rule of this.rules) {
1032
- if (!other.rules.some(r => rule.equals(r)))
1033
- return false;
1034
- }
1035
- return true;
1036
- }
1037
- /**
1038
- * Get the count of tiles that satisfy the given conditions.
1039
- * @param exists Whether the tile exists or not.
1040
- * @param fixed Whether the tile is fixed or not. If undefined, the fixed state is ignored.
1041
- * @param color The color of the tile. If undefined, all colors are included.
1042
- * @returns The count of tiles that satisfy the given conditions.
1043
- */
1044
- getTileCount(exists, fixed, color) {
1045
- let count = 0;
1046
- this.forEach(tile => {
1047
- if (tile.exists !== exists)
1048
- return;
1049
- if (fixed !== undefined && tile.fixed !== fixed)
1050
- return;
1051
- if (color !== undefined && tile.color !== color)
1052
- return;
1053
- count++;
1054
- });
1055
- return count;
1056
- }
1057
- /**
1058
- * Get the count of tiles that satisfy the given conditions for each color.
1059
- * @param color The color of the tiles.
1060
- * @returns The count of tiles that satisfy the given conditions for each color.
1061
- */
1062
- getColorCount(color) {
1063
- let min = 0;
1064
- let max = this.width * this.height;
1065
- this.forEach(tile => {
1066
- if (!tile.exists || (tile.fixed && tile.color !== color)) {
1067
- max--;
1068
- }
1069
- if (tile.exists && tile.fixed && tile.color === color) {
1070
- min++;
1071
- }
1072
- });
1073
- return { min, max };
1074
- }
1075
- /**
1076
- * Deduplicate the rules in the given list.
1077
- *
1078
- * @param rules The list of rules to deduplicate.
1079
- * @returns The deduplicated list of rules.
1080
- */
1081
- static deduplicateRules(rules) {
1082
- return rules.filter((rule, index, self) => self.findIndex(r => r.equals(rule)) === index);
1083
- }
1084
- /**
1085
- * Deduplicate the singleton rules in the given list.
1086
- *
1087
- * @param rules The list of rules to deduplicate.
1088
- * @returns The deduplicated list of rules.
1089
- */
1090
- static deduplicateSingletonRules(rules) {
1091
- return rules.filter((rule, index, self) => !rule.isSingleton || self.findIndex(r => r.id === rule.id) === index);
1092
- }
1093
- /**
1094
- * Deduplicate the symbols in the given map.
1095
- *
1096
- * @param symbols The map of symbols to deduplicate.
1097
- * @returns The deduplicated map of symbols.
1098
- */
1099
- static deduplicateSymbols(symbols) {
1100
- const map = new Map();
1101
- for (const [id, symbolList] of symbols) {
1102
- map.set(id, symbolList.filter((symbol, index, self) => self.findIndex(s => symbol.equals(s)) === index));
1103
- }
1104
- return map;
1105
- }
1106
- }