@xtia/grid 0.0.2 → 0.0.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.
package/README.md ADDED
@@ -0,0 +1,156 @@
1
+ # `Grid`
2
+
3
+ ## Summary
4
+
5
+ A mutable 2D grid with efficient region views, spatial operations, and seamless interoperability with [Pipe2D](https://github.com/tiadrop/pipe2d). Store, edit, and transform 2D data with an ergonomic API designed for practical use.
6
+
7
+ ## Features
8
+
9
+ - **Mutable 2D storage** with type safety
10
+ - **Zero-copy region views** - edit or provide subgrids and transformation layers without copying
11
+ - **Spatial operations** - fill, paste, flood fill, pathfinding
12
+ - **Pipe2D integration** - materialise pipes, get pipe views
13
+ - **Consistent API** - for grids and regions
14
+ - **Mask support** - apply operations to arbitrary shapes
15
+
16
+ ## Example
17
+
18
+ ```
19
+ npm i @xtia/grid
20
+ ```
21
+
22
+ ```ts
23
+ import { Grid } from "@xtia/grid";
24
+
25
+ // initialise a 30x20 Grid<number> of 0's
26
+ const numGrid = Grid.solid(30, 20, 0);
27
+
28
+ // change a value
29
+ numGrid.set(3, 3, 50);
30
+
31
+ // initialise a chessboard
32
+ const chessGrid = Grid.init(8, 8, (x, y) =>
33
+ (x + y) % 2 === 0 ? 'black' : 'white'
34
+ );
35
+
36
+ // read a value
37
+ const colour = chessGrid.get(4, 5);
38
+
39
+ // initialise a grid from a Pipe2D
40
+ const source = imagePipe
41
+ .crop(10, 10, 64, 64)
42
+ .scale(.5)
43
+ .rotateLeft();
44
+ const grid = Grid.from(source);
45
+ ```
46
+
47
+ ## Regions
48
+
49
+ Use `grid.region(x, y, w, h)` to define a subgrid. The subgrid is a **view** into the parent; changes to the subgrid affect the parent and vice-versa.
50
+
51
+ ```ts
52
+ // game map with terrain
53
+ const world = Grid.solid(100, 100, 0); // 0 = grass
54
+
55
+ // add a lake
56
+ world.region(30, 30, 20, 20).fill(1); // 1 = water
57
+
58
+ // add mountains around the lake with a circular mask
59
+ const centre = world.cells.get(40, 40);
60
+ const mask = (x: number, y: number) => {
61
+ const dist = Math.hypot(x - centre.x, y - centre.y);
62
+ return dist > 12 && dist < 18; // mountain ring
63
+ };
64
+ world.fill(2, mask); // 2 = mountain
65
+
66
+ // get a view of just the interesting area
67
+ const lakeRegion = world.region(25, 25, 30, 30);
68
+
69
+ // do we want a distinct, self-contained copy of the region?
70
+ const lakeGrid = Grid.from(lakeRegion);
71
+ ```
72
+
73
+ ## Transformation Layers
74
+
75
+ `grid.map(read, write)` creates a **view** that reads and writes the parent through the provided transformation functions.
76
+
77
+ ```ts
78
+ const sprites = ["grass.png", "water.png", "mountain.png", "forest.png"];
79
+ const spriteMap = world.map(
80
+ n => sprites[n],
81
+ s => sprites.indexOf(s)
82
+ );
83
+
84
+ world.set(0, 0, 2); // modify the parent
85
+ console.log(spriteMap.get(0, 0)); // read the transformed view: "mountain.png"
86
+ spriteMap.set(1, 0, "forest.png"); // modify the view
87
+ console.log(world.get(1, 0)); // read the parent: 3
88
+ ```
89
+
90
+ For one-way read-mapping, use `grid.pipe` - a [Pipe2D](https://github.com/tiadrop/pipe2d) view into the grid's data.
91
+
92
+ ## Cell interface
93
+
94
+ `grid.cells` provides a Pipe2D, for convenient transformation, of live-view interfaces relating to positions in the grid.
95
+
96
+ ```ts
97
+ const topLeft = world.cells.get(0, 0);
98
+ console.log(topLeft.value); // 2
99
+
100
+ topLeft.value = 3; // modifies the underlying data
101
+ console.log(spriteMap.get(0, 0)); // now "forest.png";
102
+
103
+ // navigate by offset
104
+ const adjacentCell = topLeft.look(1, 0);
105
+ console.log(adjacentCell.x, adjacentCell.y); // 1, 0
106
+ ```
107
+
108
+ Cells provide methods for locational utilities such as pathfinding and visibility mapping.
109
+
110
+ Cells are unique to, owned by, and coordinated relative to the view that provided them. Their pathfinding and visibility mapping features are unaware of space outside of their view's bounds.
111
+
112
+ ### Path finding
113
+ ```
114
+ const costs = [1, Infinity, Infinity, 2]; // grass=1, water/mountain=impassable, forest=2
115
+
116
+ const start = world.cells.get(5, 5);
117
+ const destination = world.cells.get(90, 90);
118
+ const path = start.findPath(
119
+ destination,
120
+ (cell) => costs[cell.value]
121
+ ); // Cell<number>[]
122
+ ```
123
+
124
+ ### Visibility mapping
125
+
126
+ ```ts
127
+ const visibility = start.createVisibilityMap(
128
+ cell => cell.value == 0 // anything except grass blocks vision
129
+ );
130
+
131
+ // any 2d source, including visibility maps, can be used as a mask
132
+ // paste(source: Source2D<T>, mask?: Source2D<boolean>)
133
+ screenGrid.paste(spriteMap, visibility.map(v => v != Visibility.hidden));
134
+
135
+ // using pipe2d's convenience
136
+ const mountainCells = world.cells.toFlatArrayXY().filter(c => c.value === 2);
137
+ ```
138
+
139
+ ### Reusing path data
140
+
141
+ Use `cell.getPathMap(costFunc)` to create a Pipe2D of optimal paths from the parent cell. The grid space is explored once to produce an internal traversal map, and paths to individual cells are constructed form that data (and cached) when that pipe is queried.
142
+
143
+ ```ts
144
+ const pathMap = start.getPathMap(c => costs[c]);
145
+
146
+ const pathToCentre = pathMap.get(50, 50);
147
+ const pathToCorner = pathMap.get(99, 99);
148
+ ```
149
+
150
+ ## The storage layer
151
+
152
+ `Grid` itself is an interface for reading and writing 2D data. `GridBase` - a subclass of `Grid` - maintains the actual storage of such data.
153
+
154
+ 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)`.
155
+
156
+ You can perform batched updates, holding off the 'change' event until the batch process concludes, with `grid.batchUpdate(callback)`.
package/lib/grid.d.ts CHANGED
@@ -64,8 +64,8 @@ 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>): void;
68
- paste(x: number, y: number, source: Source2D<T>, mask?: Source2D<boolean>): void;
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;
69
69
  [Symbol.iterator](): IterableIterator<{
70
70
  x: number;
71
71
  y: number;
package/lib/grid.js CHANGED
@@ -235,10 +235,15 @@ export class Grid {
235
235
  return true;
236
236
  }
237
237
  fill(value, mask) {
238
+ const maskSource = typeof mask == "function" ? {
239
+ width: this.width,
240
+ height: this.height,
241
+ get: mask
242
+ } : mask;
238
243
  this.batchUpdate(() => {
239
244
  for (let y = 0; y < this.height; y++) {
240
245
  for (let x = 0; x < this.width; x++) {
241
- if (mask && !mask.get(x, y))
246
+ if (maskSource && !maskSource.get(x, y))
242
247
  continue;
243
248
  this.trySet(x, y, value);
244
249
  }
@@ -246,13 +251,18 @@ export class Grid {
246
251
  });
247
252
  }
248
253
  paste(x, y, source, mask) {
254
+ const maskSource = typeof mask == "function" ? {
255
+ width: this.width,
256
+ height: this.height,
257
+ get: mask
258
+ } : mask;
249
259
  const cachedSource = new GridBase(source);
250
260
  this.batchUpdate(() => {
251
261
  for (let oy = 0; oy < source.height; oy++) {
252
262
  for (let ox = 0; ox < source.width; ox++) {
253
263
  const tx = ox + x;
254
264
  const ty = oy + y;
255
- if (mask && !mask.get(tx, ty))
265
+ if (maskSource && !maskSource.get(tx, ty))
256
266
  continue;
257
267
  this.trySet(tx, ty, cachedSource.get(ox, oy));
258
268
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "0.0.2",
3
- "description": "2D store and utilities",
2
+ "version": "0.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": {
6
6
  ".": {