@xtia/grid 0.0.6 → 0.0.8

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
@@ -1,6 +1,6 @@
1
1
  # `Grid`
2
2
 
3
- **Alpha**: Some API details may change
3
+ **Alpha**: Pre-0.1.0 API details may change
4
4
 
5
5
  ## Summary
6
6
 
@@ -11,8 +11,8 @@ A mutable 2D grid with efficient region views, spatial operations, and seamless
11
11
  - **Mutable 2D storage** with type safety
12
12
  - **Zero-copy region views** - edit or provide subgrids and transformation layers without copying
13
13
  - **Spatial operations** - fill, paste, flood fill, pathfinding
14
- - **Pipe2D integration** - materialise pipes, get pipe views
15
- - **Consistent API** - for grids and regions
14
+ - **First-class Pipe2D interoperability** use grids as sources for pipes, or pipes as sources for grids
15
+ - **Consistent API** - for grids, regions, and your existing 2D structures
16
16
  - **Mask support** - apply operations to arbitrary shapes
17
17
 
18
18
  ## Example
@@ -48,7 +48,7 @@ const grid = Grid.from(source);
48
48
 
49
49
  ### Wrapping existing structures
50
50
 
51
- Use Grid's interface over any read/write 2D structure:
51
+ Use Grid's interface and features over any read/write 2D structure:
52
52
 
53
53
  ```ts
54
54
  const gameMap = [
@@ -134,7 +134,7 @@ const mountainCells = world.cells.toFlatArrayXY()
134
134
 
135
135
  ## Cell interface
136
136
 
137
- `grid.cells` provides a Pipe2D, for convenient transformation, of live-view interfaces relating to positions in the grid.
137
+ `grid.cells` provides a Pipe2D, for convenient transformation, of Cell objects, each representing a live view into a grid location.
138
138
 
139
139
  ```ts
140
140
  const topLeft = world.cells.get(0, 0);
@@ -210,9 +210,9 @@ Unlike visibility maps, which are lazily evaluated according to the supplied `is
210
210
 
211
211
  `Grid` itself is an interface for reading and writing 2D data. `GridBase` - a subclass of `Grid` - maintains the actual storage of such data.
212
212
 
213
- Although the Grid factory methods (`Grid.solid()`, `Grid.from()`, `Grid.init()`) return a `GridBase<T>`, the mental model is that we're simply working with `Grid`s, therefore those factory methods live on `Grid`. The only API distinction is that `GridBase` provides a 'change' event, via `grid.on("change", handler)`.
213
+ Although the Grid factory methods (`Grid.solid<T>()`, `Grid.from<T>()`, `Grid.init<T>()`) belong to `Grid`, they return a `GridBase<T>`. `Grid.wrap<T>()` is an exception, returning `Grid<T>`, as it uses a storage layer provided by the user. The only API distinction is that `GridBase` provides a 'change' event, via `grid.on("change", handler)`.
214
214
 
215
- You can perform batched updates, holding off the 'change' event until the batch process concludes, with `grid.batchUpdate(callback)`.
215
+ We can perform batched updates, suppressing the 'change' event until a process concludes, with `grid.batchUpdate(callback)`.
216
216
 
217
217
  ## Save and load
218
218
 
package/lib/grid.d.ts CHANGED
@@ -13,15 +13,16 @@ type PathFindOptions<T> = {
13
13
  cell: Cell<T>;
14
14
  cost: number;
15
15
  }) => void;
16
- shortcutMap?: Source2D<Cell<T>[] | undefined>;
16
+ shortcutMap?: Source2D<Cell<T>[] | undefined> | GetXYFunc<Cell<T>[]>;
17
17
  };
18
18
  type CostMapOptions<T> = PathFindOptions<T> & {
19
- stopAt?: [number, number] | Cell<T>;
19
+ stopAt?: LocationSpec<T>;
20
20
  };
21
21
  type VisibilityOptions = {
22
22
  maxDistance?: number;
23
23
  boundariesVisible?: boolean;
24
24
  };
25
+ type LocationSpec<T> = Cell<T> | [number, number];
25
26
  export declare enum TraversalType {
26
27
  cardinal = 0,
27
28
  diagonal = 1,
@@ -38,16 +39,16 @@ export declare class Cell<T> {
38
39
  constructor(x: number, y: number, gridView: Grid<T>);
39
40
  get value(): T;
40
41
  set value(v: T);
42
+ private getSiblingCell;
43
+ private getSiblingXY;
41
44
  getNeighbours(includeDiagonals?: boolean): Cell<T>[];
42
45
  look(xDelta: number, yDelta: number): Cell<T> | undefined;
43
- findPath(target: Cell<T> | [number, number], getCost: CostFunc<T>, options?: PathFindOptions<T>): Cell<T>[] | null;
44
- getPathMap(getCost: CostFunc<T>, options?: PathFindOptions<T>): Pipe2D<Cell<T>[] | null>;
45
- getCostMap(getCost: CostFunc<T>, options?: CostMapOptions<T>): Pipe2D<number>;
46
+ findPath(target: LocationSpec<T>, getCost: CostFunc<T> | Map<T, number>, options?: PathFindOptions<T>): Cell<T>[] | null;
47
+ getPathMap(getCost: CostFunc<T> | Map<T, number>, options?: PathFindOptions<T>): Pipe2D<Cell<T>[] | null>;
48
+ getCostMap(getCost: CostFunc<T> | Map<T, number>, options?: CostMapOptions<T>): Pipe2D<number>;
46
49
  private calculateCosts;
47
- getLineTo(target: {
48
- x: number;
49
- y: number;
50
- } | [number, number]): IterableIterator<Cell<T>>;
50
+ getLineTo(target: LocationSpec<T>): IterableIterator<Cell<T>>;
51
+ getLineTo(x: number, y: number): IterableIterator<Cell<T>>;
51
52
  createVisibilityMap(isClear: (cell: Cell<T>) => boolean, options?: VisibilityOptions): Pipe2D<boolean>;
52
53
  }
53
54
  export declare class Grid<T> {
package/lib/grid.js CHANGED
@@ -18,6 +18,22 @@ export class Cell {
18
18
  set value(v) {
19
19
  this.gridView.set(this.x, this.y, v);
20
20
  }
21
+ getSiblingCell(location) {
22
+ if (Array.isArray(location))
23
+ return this.gridView.cells.get(...location);
24
+ if (location.gridView !== this.gridView) {
25
+ throw new Error("Target cell belongs to a different Grid");
26
+ }
27
+ return location;
28
+ }
29
+ getSiblingXY(location) {
30
+ if (Array.isArray(location))
31
+ return location;
32
+ if (location.gridView !== this.gridView) {
33
+ throw new Error("Target cell belongs to a different Grid");
34
+ }
35
+ return [location.x, location.y];
36
+ }
21
37
  getNeighbours(includeDiagonals = false) {
22
38
  const offsets = includeDiagonals
23
39
  ? [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1]]
@@ -34,15 +50,12 @@ export class Cell {
34
50
  return this.gridView.cells.get(xDelta + this.x, yDelta + this.y);
35
51
  }
36
52
  findPath(target, getCost, options = {}) {
37
- const targetXY = Array.isArray(target)
38
- ? target
39
- : [target.x, target.y];
40
53
  const pathMap = this.calculateCosts(getCost, {
41
- stopAt: targetXY,
54
+ stopAt: target,
42
55
  allowDiagonal: options.allowDiagonal,
43
56
  maxCost: options.maxCost,
44
57
  }, true);
45
- return pathMap === null || pathMap === void 0 ? void 0 : pathMap.get(...targetXY);
58
+ return pathMap === null || pathMap === void 0 ? void 0 : pathMap.get(...this.getSiblingXY(target));
46
59
  }
47
60
  getPathMap(getCost, options = {}) {
48
61
  return this.calculateCosts(getCost, options, true);
@@ -59,41 +72,43 @@ export class Cell {
59
72
  const pathInfo = asPathMap
60
73
  ? new Map()
61
74
  : null;
75
+ const getCostFn = getCost instanceof Map
76
+ ? (cell) => { var _a; return (_a = getCost.get(cell.value)) !== null && _a !== void 0 ? _a : Infinity; }
77
+ : getCost;
62
78
  costs.set(this, 0);
63
79
  const queue = new OrderedQueue(cell => { var _a; return (_a = costs.get(cell)) !== null && _a !== void 0 ? _a : Infinity; }, this);
64
- const targetCell = Array.isArray(options.stopAt)
65
- ? this.gridView.cells.get(options.stopAt[0], options.stopAt[1])
66
- : options.stopAt;
67
- if (targetCell && targetCell.gridView !== this.gridView) {
68
- throw new Error("Target cell belongs to a different Grid");
69
- }
80
+ const stopAtCell = options.stopAt && this.getSiblingCell(options.stopAt);
70
81
  while (queue.length > 0) {
71
82
  const current = queue.take();
72
- (_a = options.onVisit) === null || _a === void 0 ? void 0 : _a.call(options, { cell: current, cost: costs.get(current) });
73
83
  if (visited.has(current))
74
84
  continue;
75
85
  visited.add(current);
76
- if (targetCell && current === targetCell) {
86
+ (_a = options.onVisit) === null || _a === void 0 ? void 0 : _a.call(options, { cell: current, cost: costs.get(current) });
87
+ if (stopAtCell && current === stopAtCell) {
77
88
  break;
78
89
  }
79
90
  const currentCost = costs.get(current);
80
91
  const neighbours = current.getNeighbours(options.allowDiagonal).map(cell => ({
81
- traversalType: cell.x == current.x && cell.y == current.y ? TraversalType.cardinal : TraversalType.diagonal,
92
+ traversalType: cell.x == current.x && cell.y == current.y
93
+ ? TraversalType.cardinal
94
+ : TraversalType.diagonal,
82
95
  cell
83
96
  }));
84
- if (options.shortcutMap) {
85
- const shortcuts = (_b = options.shortcutMap.get(current.x, current.y)) === null || _b === void 0 ? void 0 : _b.map(cell => ({
97
+ const shortcutCells = typeof options.shortcutMap == "function"
98
+ ? options.shortcutMap(current.x, current.y)
99
+ : (_b = options.shortcutMap) === null || _b === void 0 ? void 0 : _b.get(current.x, current.y);
100
+ if (shortcutCells) {
101
+ const shortcuts = shortcutCells.map(cell => ({
86
102
  traversalType: TraversalType.shortcut,
87
103
  cell
88
104
  }));
89
- if (shortcuts)
90
- neighbours.push(...shortcuts);
105
+ neighbours.push(...shortcuts);
91
106
  }
92
107
  neighbours.forEach(n => {
93
108
  var _a;
94
109
  if (visited.has(n.cell))
95
110
  return;
96
- const moveCost = getCost(n.cell, {
111
+ const moveCost = getCostFn(n.cell, {
97
112
  traversalType: n.traversalType,
98
113
  from: current,
99
114
  });
@@ -135,10 +150,10 @@ export class Cell {
135
150
  return this.gridView.cells.map(costs, () => Infinity)
136
151
  .strict();
137
152
  }
138
- *getLineTo(target) {
139
- const [targetX, targetY] = Array.isArray(target)
140
- ? target
141
- : [target.x, target.y];
153
+ *getLineTo(targetOrX, _targetY) {
154
+ const [targetX, targetY] = typeof targetOrX == "number"
155
+ ? [targetOrX, _targetY]
156
+ : this.getSiblingXY(targetOrX);
142
157
  if (this.x === targetX && this.y === targetY) {
143
158
  yield this;
144
159
  return;
@@ -182,7 +197,7 @@ export class Cell {
182
197
  return false;
183
198
  }
184
199
  }
185
- for (const cell of this.getLineTo({ x, y })) {
200
+ for (const cell of this.getLineTo([x, y])) {
186
201
  if (isClear(cell))
187
202
  continue;
188
203
  return cell.x === x && cell.y === y
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.0.6",
2
+ "version": "0.0.8",
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": {