@xtia/grid 0.0.9 → 0.0.11

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
@@ -91,7 +91,7 @@ const mask = (x: number, y: number) => {
91
91
  const dist = Math.hypot(x - centre.x, y - centre.y);
92
92
  return dist > 12 && dist < 18; // mountain ring
93
93
  };
94
- world.fill("mountain", mask);
94
+ world.writeMask(mask).fill("mountain");
95
95
 
96
96
  // get a view of just the interesting area
97
97
  const lakeRegion = world.region(25, 25, 30, 30);
@@ -130,6 +130,21 @@ const mountainCells = world.cells.toFlatArrayXY()
130
130
  .filter(cell => cell.value === "mountain")
131
131
  .map(cell => [cell.x, cell.y]);
132
132
 
133
+ // or get a live, renderable minimap
134
+ import { C } from "@xtia/rgba";
135
+
136
+ const colourMap = new Map(Object.entries({
137
+ grass: C.x0f0,
138
+ water: C.x00f,
139
+ forest: C.x080,
140
+ mountain: C.xa70
141
+ }));
142
+ const minimap = world.pipe
143
+ .map(colourMap)
144
+ .floorCoordinates()
145
+ .crop(viewX, viewY, vw, vh)
146
+ .stretch(canvas.width, canvas.height);
147
+
133
148
  ```
134
149
 
135
150
  ## Cell interface
@@ -188,9 +203,8 @@ const visibility = start.createVisibilityMap(
188
203
  cell => cell.value === "grass" // anything except grass blocks vision
189
204
  );
190
205
 
191
- // any 2d source, including visibility maps, can be used as a mask for paste/fill operations
192
- // paste(source: Source2D<T>, mask?: Source2D<boolean>)
193
- screenGrid.paste(spriteMap, visibility);
206
+ // writeMask() creates a live view that only allows writes at positions allowed by the mask Source2D/function
207
+ screenGrid.writeMask(visibility).paste(spriteMap, 0, 0);
194
208
  ```
195
209
 
196
210
  ### Reusing path data
@@ -219,8 +233,17 @@ We can perform batched updates, suppressing the 'change' event until a process c
219
233
  Pipe2D makes it easy to save and restore grid data:
220
234
 
221
235
  ```ts
222
- const saved = world.pipe.toFlatArrayXY(); // ["mountain", "forest", "grass", ...]
236
+ const snapshot = world.pipe.stash();
237
+
238
+ const restored = Grid.from(snapshot);
239
+ // or paste it to an existing grid
240
+ world.paste(snapshot);
241
+ ```
242
+
243
+ For persistent storage or transmission, Pipe2D can export as array:
223
244
 
224
- const restoredPipe = Pipe2D.fromFlatArrayXY(saved);
225
- const restored = Grid.from(restoredPipe);
226
- ```
245
+ ```ts
246
+ const saved = snapshot.toFlatArrayXY(); // ["mountain", "forest", "grass", ...]
247
+
248
+ const restoredSnapshot = Pipe2D.fromFlatArrayXY(saved);
249
+ ```
package/lib/grid.d.ts CHANGED
@@ -13,7 +13,7 @@ type PathFindOptions<T> = {
13
13
  cell: Cell<T>;
14
14
  cost: number;
15
15
  }) => void;
16
- shortcutMap?: Source2D<Cell<T>[] | undefined> | GetXYFunc<Cell<T>[]>;
16
+ shortcutMap?: Source2D<LocationSpec<T>[] | undefined> | GetXYFunc<LocationSpec<T>[] | undefined>;
17
17
  };
18
18
  type CostMapOptions<T> = PathFindOptions<T> & {
19
19
  stopAt?: LocationSpec<T>;
@@ -64,8 +64,9 @@ export declare class Grid<T> {
64
64
  get(x: number, y: number): T;
65
65
  set(x: number, y: number, value: T): void;
66
66
  trySet(x: number, y: number, value: T): boolean;
67
- fill(value: T, mask?: Source2D<boolean> | GetXYFunc<boolean>): void;
68
- paste(x: number, y: number, source: Source2D<T>, mask?: Source2D<boolean> | GetXYFunc<boolean>): void;
67
+ fill(value: T): void;
68
+ paste(source: Source2D<T>, x?: number, y?: number): void;
69
+ writeMask(mask: Source2D<boolean> | GetXYFunc<boolean>): Grid<T>;
69
70
  [Symbol.iterator](): IterableIterator<{
70
71
  x: number;
71
72
  y: number;
package/lib/grid.js CHANGED
@@ -50,11 +50,7 @@ export class Cell {
50
50
  return this.gridView.cells.get(xDelta + this.x, yDelta + this.y);
51
51
  }
52
52
  findPath(target, getCost, options = {}) {
53
- const pathMap = this.calculateCosts(getCost, {
54
- stopAt: target,
55
- allowDiagonal: options.allowDiagonal,
56
- maxCost: options.maxCost,
57
- }, true);
53
+ const pathMap = this.calculateCosts(getCost, Object.assign(Object.assign({}, options), { stopAt: target }), true);
58
54
  return pathMap === null || pathMap === void 0 ? void 0 : pathMap.get(...this.getSiblingXY(target));
59
55
  }
60
56
  getPathMap(getCost, options = {}) {
@@ -98,7 +94,7 @@ export class Cell {
98
94
  ? options.shortcutMap(current.x, current.y)
99
95
  : (_b = options.shortcutMap) === null || _b === void 0 ? void 0 : _b.get(current.x, current.y);
100
96
  if (shortcutCells) {
101
- const shortcuts = shortcutCells.map(cell => ({
97
+ const shortcuts = shortcutCells.map(loc => this.getSiblingCell(loc)).map(cell => ({
102
98
  traversalType: TraversalType.shortcut,
103
99
  cell
104
100
  }));
@@ -185,14 +181,14 @@ export class Cell {
185
181
  }
186
182
  }
187
183
  createVisibilityMap(isClear, options = {}) {
188
- var _a, _b;
189
- const maxDistance = (_a = options.maxDistance) !== null && _a !== void 0 ? _a : Infinity;
190
- const boundariesVisible = (_b = options.boundariesVisible) !== null && _b !== void 0 ? _b : true;
184
+ var _a;
185
+ const maxDistance = options.maxDistance;
186
+ const boundariesVisible = (_a = options.boundariesVisible) !== null && _a !== void 0 ? _a : true;
191
187
  return new Pipe2D(this.gridView.width, this.gridView.height, (x, y) => {
192
188
  if (!isClear(this)) {
193
189
  return x === this.x && y === this.y ? boundariesVisible : false;
194
190
  }
195
- if (maxDistance < Infinity) {
191
+ if (maxDistance !== undefined) {
196
192
  if (getDistance(this, { x, y }) > maxDistance) {
197
193
  return false;
198
194
  }
@@ -239,46 +235,46 @@ export class Grid {
239
235
  this.parentSet(x, y, value);
240
236
  }
241
237
  trySet(x, y, value) {
242
- if (x < 0 || x >= this.width || y < 0 || y >= this.height)
238
+ if (x < 0
239
+ || x >= this.width
240
+ || y < 0
241
+ || y >= this.height
242
+ || !Number.isInteger(x)
243
+ || !Number.isInteger(y))
243
244
  return false;
244
245
  this.set(x, y, value);
245
246
  return true;
246
247
  }
247
- fill(value, mask) {
248
- const maskSource = typeof mask == "function" ? {
249
- width: this.width,
250
- height: this.height,
251
- get: mask
252
- } : mask;
248
+ fill(value) {
253
249
  this.batchUpdate(() => {
254
250
  for (let y = 0; y < this.height; y++) {
255
251
  for (let x = 0; x < this.width; x++) {
256
- if (maskSource && !maskSource.get(x, y))
257
- continue;
258
252
  this.trySet(x, y, value);
259
253
  }
260
254
  }
261
255
  });
262
256
  }
263
- paste(x, y, source, mask) {
264
- const maskSource = typeof mask == "function" ? {
265
- width: this.width,
266
- height: this.height,
267
- get: mask
268
- } : mask;
257
+ paste(source, x = 0, y = 0) {
269
258
  const cachedSource = new GridBase(source);
270
259
  this.batchUpdate(() => {
271
260
  for (let oy = 0; oy < source.height; oy++) {
272
261
  for (let ox = 0; ox < source.width; ox++) {
273
- const tx = ox + x;
274
- const ty = oy + y;
275
- if (maskSource && !maskSource.get(tx, ty))
276
- continue;
277
- this.trySet(tx, ty, cachedSource.get(ox, oy));
262
+ this.trySet(ox + x, oy + y, cachedSource.get(ox, oy));
278
263
  }
279
264
  }
280
265
  });
281
266
  }
267
+ writeMask(mask) {
268
+ const getMask = typeof mask == "function"
269
+ ? mask
270
+ : (x, y) => mask.get(x, y);
271
+ return new Grid(this.width, this.height, this.parentGet, (x, y, v) => {
272
+ const writable = getMask(x, y);
273
+ if (writable) {
274
+ this.set(x, y, v);
275
+ }
276
+ }, cb => this.batch(cb));
277
+ }
282
278
  *[Symbol.iterator]() {
283
279
  for (let y = 0; y < this.height; y++) {
284
280
  for (let x = 0; x < this.width; x++) {
@@ -311,8 +307,6 @@ export class Grid {
311
307
  return new Grid(width, height, (tx, ty) => {
312
308
  return this.parentGet(tx + x, ty + y);
313
309
  }, (tx, ty, value) => {
314
- if (tx < 0 || ty < 0 || tx >= width || ty >= height)
315
- throw new Error("Out of bounds");
316
310
  this.parentSet(tx + x, ty + y, value);
317
311
  }, fn => this.batch(fn));
318
312
  }
@@ -381,17 +375,20 @@ export class GridBase extends Grid {
381
375
  if (x < 0 || x >= this.width || y < 0 || y >= this.height) {
382
376
  throw new Error(`Coordinates out of bounds: (${x}, ${y})`);
383
377
  }
378
+ if (!Number.isInteger(x) || !Number.isInteger(y)) {
379
+ throw new Error(`Coordinates must be integer; got (${x}, ${y})`);
380
+ }
384
381
  return y * this.width + x;
385
382
  }
386
383
  _set(x, y, value) {
387
384
  const idx = this.xyToIndex(x, y);
388
385
  if (this.data[idx] === value)
389
386
  return;
387
+ this.data[this.xyToIndex(x, y)] = value;
390
388
  if (this.batchState) {
391
389
  this.batchState.changedCells.add(this.cells.get(x, y));
392
390
  return;
393
391
  }
394
- this.data[this.xyToIndex(x, y)] = value;
395
392
  if (this.eventHandlers.change) {
396
393
  this.triggerChange(new Set([this.cells.get(x, y)]));
397
394
  }
package/lib/utils.d.ts CHANGED
@@ -4,14 +4,6 @@ export type Source2D<T> = {
4
4
  height: number;
5
5
  get: GetXYFunc<T>;
6
6
  };
7
- export type Angle = {
8
- asDegrees: number;
9
- } | {
10
- asRadians: number;
11
- } | {
12
- asTurns: number;
13
- };
14
- export declare function angleToRadians(angle: Angle): number;
15
7
  export declare class OrderedQueue<T> {
16
8
  private getCost;
17
9
  private queue;
package/lib/utils.js CHANGED
@@ -1,12 +1,3 @@
1
- export function angleToRadians(angle) {
2
- if ("asRadians" in angle)
3
- return angle.asRadians;
4
- if ("asDegrees" in angle)
5
- return angle.asDegrees * (180 / Math.PI);
6
- if ("asTurns" in angle)
7
- return angle.asTurns * Math.PI * 2;
8
- throw new Error("Invalid angle");
9
- }
10
1
  export class OrderedQueue {
11
2
  constructor(getCost, ...values) {
12
3
  this.getCost = getCost;
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.0.9",
2
+ "version": "0.0.11",
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": {
@@ -9,7 +9,12 @@
9
9
  },
10
10
  "./lib/*": null
11
11
  },
12
- "keywords": ["grid", "game-dev", "pathfinding", "cellular-automata"],
12
+ "keywords": [
13
+ "grid",
14
+ "game-dev",
15
+ "pathfinding",
16
+ "cellular-automata"
17
+ ],
13
18
  "scripts": {
14
19
  "build": "npx tsc",
15
20
  "test": "npx vitest run"
@@ -18,10 +23,11 @@
18
23
  "lib/"
19
24
  ],
20
25
  "devDependencies": {
26
+ "@vitest/coverage-v8": "^4.1.10",
21
27
  "typescript": "^7.0.2",
22
28
  "vitest": "^4.1.10"
23
29
  },
24
30
  "dependencies": {
25
- "@xtia/pipe2d": "^2.1.0"
31
+ "@xtia/pipe2d": "^2.2.1"
26
32
  }
27
33
  }