@xtia/grid 0.3.0 → 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.
package/README.md CHANGED
@@ -22,7 +22,7 @@ npm i @xtia/grid
22
22
  ```
23
23
 
24
24
  ```ts
25
- import { Grid } from "@xtia/grid"; // ~4.8kb gzipped (2.2kb + Pipe2D dep)
25
+ import { Grid } from "@xtia/grid"; // ~5.1kb gzipped (2.5kb + Pipe2D dep)
26
26
 
27
27
  // initialise a 30x20 Grid<number> of 0's
28
28
  const numGrid = Grid.solid(30, 20, 0);
@@ -115,11 +115,11 @@ spriteMap.set(1, 0, "forest.png"); // modify the view
115
115
  console.log(world.get(1, 0)); // read the parent: "forest"
116
116
  ```
117
117
 
118
- For one-way read-mapping, use `grid.pipe` - a [Pipe2D](https://github.com/tiadrop/pipe2d) view into the grid's data.
118
+ For one-way read-mapping, use `grid.values` - a [Pipe2D](https://github.com/tiadrop/pipe2d) view into the grid's data.
119
119
 
120
120
  ```ts
121
121
  // create a one-way, player-centred sprite map
122
- const viewport = world.pipe
122
+ const viewport = world.values
123
123
  .oob("mountain")
124
124
  .map(t => t + ".png")
125
125
  .crop(playerX - 5, playerY - 5, 10, 10);
@@ -138,7 +138,7 @@ const colourMap = new Map(Object.entries({
138
138
  forest: C.x080,
139
139
  mountain: C.xa70
140
140
  }));
141
- const minimap = world.pipe
141
+ const minimap = world.values
142
142
  .map(colourMap)
143
143
  .floorCoordinates()
144
144
  .crop(viewX, viewY, vw, vh)
@@ -236,7 +236,7 @@ We can perform batched updates, suppressing the 'change' event until a process c
236
236
  Pipe2D makes it easy to save and restore grid data:
237
237
 
238
238
  ```ts
239
- const snapshot = world.pipe.stash();
239
+ const snapshot = world.values.stash();
240
240
 
241
241
  const restored = Grid.init(snapshot);
242
242
  // or paste it to an existing grid
package/lib/grid.d.ts CHANGED
@@ -82,8 +82,9 @@ export declare class Grid<T> {
82
82
  private parentGet;
83
83
  private parentSet;
84
84
  private batch;
85
- readonly cells: Pipe2D<Cell<T>>;
86
85
  protected constructor(width: number, height: number, parentGet: GetXYFunc<T>, parentSet: (x: number, y: number, value: T) => void, batch: (cb: () => void) => void);
86
+ readonly cells: Pipe2D<Cell<T>>;
87
+ values: Pipe2D<T>;
87
88
  pipe: Pipe2D<T>;
88
89
  batchUpdate(fn: () => void): void;
89
90
  get(x: number, y: number): T;
@@ -91,6 +92,7 @@ export declare class Grid<T> {
91
92
  trySet(x: number, y: number, value: T): boolean;
92
93
  fill(value: T): void;
93
94
  paste(source: Source2D<T>, x?: number, y?: number): void;
95
+ paste(get: GetXYFunc<T>): void;
94
96
  writeMask(mask: Source2D<boolean> | GetXYFunc<boolean>): Grid<T>;
95
97
  [Symbol.iterator](): IterableIterator<T>;
96
98
  forEach(callback: (value: T, x: number, y: number) => void): void;
package/lib/grid.js CHANGED
@@ -6,7 +6,12 @@ export var TraversalType;
6
6
  TraversalType[TraversalType["diagonal"] = 1] = "diagonal";
7
7
  TraversalType[TraversalType["shortcut"] = 2] = "shortcut";
8
8
  })(TraversalType || (TraversalType = {}));
9
+ const cardinalOffsets = [[-1, 0], [1, 0], [0, -1], [0, 1]];
10
+ const allOffsets = [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1]];
9
11
  export class Cell {
12
+ x;
13
+ y;
14
+ gridView;
10
15
  constructor(x, y, gridView) {
11
16
  this.x = x;
12
17
  this.y = y;
@@ -36,12 +41,11 @@ export class Cell {
36
41
  }
37
42
  getNeighbours(includeDiagonals = false) {
38
43
  const offsets = includeDiagonals
39
- ? [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1]]
40
- : [[-1, 0], [1, 0], [0, -1], [0, 1]];
44
+ ? allOffsets
45
+ : cardinalOffsets;
41
46
  return offsets
42
- .map(([ox, oy]) => [ox + this.x, oy + this.y])
43
- .filter(([tx, ty]) => tx >= 0 && tx < this.gridView.width && ty >= 0 && ty < this.gridView.height)
44
- .map(([tx, ty]) => this.gridView.cells.get(tx, ty));
47
+ .map(o => this.look(o[0], o[1]))
48
+ .filter(v => v);
45
49
  }
46
50
  look(xDelta, yDelta) {
47
51
  const [x, y] = [this.x + xDelta, this.y + yDelta];
@@ -50,8 +54,11 @@ export class Cell {
50
54
  return this.gridView.cells.get(xDelta + this.x, yDelta + this.y);
51
55
  }
52
56
  findPath(target, getCost, options = {}) {
53
- const pathMap = this.calculateCosts(getCost, Object.assign(Object.assign({}, options), { stopAt: target }), true);
54
- return pathMap === null || pathMap === void 0 ? void 0 : pathMap.get(...this.getSiblingXY(target));
57
+ const pathMap = this.calculateCosts(getCost, {
58
+ ...options,
59
+ stopAt: target,
60
+ }, true);
61
+ return pathMap?.get(...this.getSiblingXY(target));
55
62
  }
56
63
  getPathMap(getCost, options = {}) {
57
64
  return this.calculateCosts(getCost, options, true);
@@ -60,16 +67,14 @@ export class Cell {
60
67
  return this.calculateCosts(getCost, options);
61
68
  }
62
69
  calculateCosts(getCost, options, asPathMap = false) {
63
- var _a, _b;
64
- var _c;
65
70
  const visited = new Set();
66
71
  const costs = new Map();
67
- const maxCost = (_c = options.maxCost) !== null && _c !== void 0 ? _c : Infinity;
72
+ const maxCost = options.maxCost ?? Infinity;
68
73
  const pathInfo = asPathMap
69
74
  ? new Map()
70
75
  : null;
71
76
  const getCostFn = getCost instanceof Map
72
- ? (cell) => { var _a; return (_a = getCost.get(cell.value)) !== null && _a !== void 0 ? _a : Infinity; }
77
+ ? (cell) => getCost.get(cell.value) ?? Infinity
73
78
  : getCost;
74
79
  costs.set(this, 0);
75
80
  const queue = new OrderedQueue([this, 0]);
@@ -79,7 +84,7 @@ export class Cell {
79
84
  if (visited.has(current))
80
85
  continue;
81
86
  visited.add(current);
82
- (_a = options.onVisit) === null || _a === void 0 ? void 0 : _a.call(options, { cell: current, cost: costs.get(current) });
87
+ options.onVisit?.({ cell: current, cost: costs.get(current) });
83
88
  if (stopAtCell && current === stopAtCell) {
84
89
  break;
85
90
  }
@@ -92,7 +97,7 @@ export class Cell {
92
97
  }));
93
98
  const shortcutCells = typeof options.shortcutMap == "function"
94
99
  ? options.shortcutMap(current.x, current.y)
95
- : (_b = options.shortcutMap) === null || _b === void 0 ? void 0 : _b.get(current.x, current.y);
100
+ : options.shortcutMap?.get(current.x, current.y);
96
101
  if (shortcutCells) {
97
102
  const shortcuts = shortcutCells.map(loc => this.getSiblingCell(loc)).map(cell => ({
98
103
  traversalType: TraversalType.shortcut,
@@ -101,7 +106,6 @@ export class Cell {
101
106
  neighbours.push(...shortcuts);
102
107
  }
103
108
  neighbours.forEach(n => {
104
- var _a;
105
109
  if (visited.has(n.cell))
106
110
  return;
107
111
  const moveCost = getCostFn(n.cell, {
@@ -111,10 +115,10 @@ export class Cell {
111
115
  if (moveCost === Infinity)
112
116
  return;
113
117
  const newCost = currentCost + moveCost;
114
- const oldCost = (_a = costs.get(n.cell)) !== null && _a !== void 0 ? _a : Infinity;
118
+ const oldCost = costs.get(n.cell) ?? Infinity;
115
119
  if (newCost < oldCost && newCost <= maxCost) {
116
120
  costs.set(n.cell, newCost);
117
- pathInfo === null || pathInfo === void 0 ? void 0 : pathInfo.set(n.cell, current);
121
+ pathInfo?.set(n.cell, current);
118
122
  queue.add(n.cell, newCost);
119
123
  }
120
124
  });
@@ -181,9 +185,8 @@ export class Cell {
181
185
  }
182
186
  }
183
187
  createVisibilityMap(isClear, options = {}) {
184
- var _a;
185
188
  const maxDistance = options.maxDistance;
186
- const boundariesVisible = (_a = options.boundariesVisible) !== null && _a !== void 0 ? _a : true;
189
+ const boundariesVisible = options.boundariesVisible ?? true;
187
190
  return new Pipe2D(this.gridView.width, this.gridView.height, (x, y) => {
188
191
  if (!isClear(this)) {
189
192
  return x === this.x && y === this.y ? boundariesVisible : false;
@@ -208,23 +211,30 @@ function getDistance(c1, c2) {
208
211
  return Math.sqrt(Math.pow(c2.x - c1.x, 2) + Math.pow(c2.y - c1.y, 2));
209
212
  }
210
213
  export class Grid {
214
+ width;
215
+ height;
216
+ parentGet;
217
+ parentSet;
218
+ batch;
211
219
  constructor(width, height, parentGet, parentSet, batch) {
212
220
  this.width = width;
213
221
  this.height = height;
214
222
  this.parentGet = parentGet;
215
223
  this.parentSet = parentSet;
216
224
  this.batch = batch;
217
- this.cells = new Pipe2D(this.width, this.height, (x, y) => {
218
- if (x < 0 || x >= this.width || y < 0 || y >= this.height || !Number.isInteger(x) || !Number.isInteger(y)) {
225
+ if (!Number.isInteger(width) || !Number.isInteger(height) || width < 0 || height < 0) {
226
+ throw new Error(`Grid width & height must be non-negative integers; got (${width} x ${height})`);
227
+ }
228
+ this.cells = new Pipe2D(width, height, (x, y) => {
229
+ if (x < 0 || x >= width || y < 0 || y >= height || !Number.isInteger(x) || !Number.isInteger(y)) {
219
230
  throw new RangeError(`Cell coordinates must be bounded integers; got (${x}, ${y})`);
220
231
  }
221
232
  return new Cell(x, y, this);
222
233
  }).withCache();
223
- this.pipe = new Pipe2D(this);
224
- if (!Number.isInteger(width) || !Number.isInteger(height) || width < 0 || height < 0) {
225
- throw new Error(`Grid width & height must be non-negative integers; got (${width} x ${height})`);
226
- }
227
234
  }
235
+ cells;
236
+ values = new Pipe2D(this);
237
+ pipe = this.values;
228
238
  batchUpdate(fn) {
229
239
  this.batch(fn);
230
240
  }
@@ -254,8 +264,11 @@ export class Grid {
254
264
  }
255
265
  });
256
266
  }
257
- paste(source, x = 0, y = 0) {
267
+ paste(_source, x = 0, y = 0) {
258
268
  this.assertValidCoordinates(x, y, true);
269
+ const source = typeof _source == "function"
270
+ ? new Pipe2D(this.width, this.height, _source)
271
+ : _source;
259
272
  const sourcePipe = source instanceof Pipe2D ? source : new Pipe2D(source);
260
273
  const cachedSource = sourcePipe.stash();
261
274
  this.batchUpdate(() => {
@@ -274,10 +287,15 @@ export class Grid {
274
287
  ? mask
275
288
  : (x, y) => mask.get(x, y);
276
289
  return new Grid(this.width, this.height, this.parentGet, (x, y, v) => {
277
- const writable = getMask(x, y);
278
- if (writable) {
279
- this.parentSet(x, y, v);
290
+ try {
291
+ const writable = getMask(x, y);
292
+ if (!writable)
293
+ return;
294
+ }
295
+ catch (e) {
296
+ throw new Error(`Failed to read write-mask at (${x}, ${y})`, { cause: e });
280
297
  }
298
+ this.parentSet(x, y, v);
281
299
  }, cb => this.batch(cb));
282
300
  }
283
301
  *[Symbol.iterator]() {
@@ -379,10 +397,12 @@ export class Grid {
379
397
  }
380
398
  }
381
399
  export class GridBase extends Grid {
400
+ data;
401
+ eventHandlers = {};
382
402
  triggerEvent(name, data) {
383
- var _a;
384
- (_a = this.eventHandlers[name]) === null || _a === void 0 ? void 0 : _a.forEach(obj => obj.fn(data));
403
+ this.eventHandlers[name]?.forEach(obj => obj.fn(data));
385
404
  }
405
+ batchState = null;
386
406
  batchUpdate(fn) {
387
407
  if (this.batchState) {
388
408
  this.batchState.level++;
@@ -404,8 +424,6 @@ export class GridBase extends Grid {
404
424
  }
405
425
  constructor(source) {
406
426
  super(source.width, source.height, (x, y) => this.data[y * this.width + x], (x, y, value) => this._set(x, y, value), fn => this.batchUpdate(fn));
407
- this.eventHandlers = {};
408
- this.batchState = null;
409
427
  const sourcePipe = source instanceof Pipe2D
410
428
  ? source
411
429
  : new Pipe2D(source);
package/lib/utils.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export class OrderedQueue {
2
+ queue = [];
2
3
  constructor(...items) {
3
- this.queue = [];
4
4
  items.forEach(v => this.add(...v));
5
5
  }
6
6
  add(value, priority) {
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.3.0",
2
+ "version": "0.4.0",
3
3
  "description": "2D store and utilities, with zero-copy regions and transform layers, pathfinding, Pipe2D interop",
4
4
  "name": "@xtia/grid",
5
5
  "exports": {