@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 +5 -5
- package/lib/grid.d.ts +3 -1
- package/lib/grid.js +50 -32
- package/lib/utils.js +1 -1
- package/package.json +1 -1
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"; // ~
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
?
|
|
40
|
-
:
|
|
44
|
+
? allOffsets
|
|
45
|
+
: cardinalOffsets;
|
|
41
46
|
return offsets
|
|
42
|
-
.map(
|
|
43
|
-
.filter(
|
|
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,
|
|
54
|
-
|
|
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 =
|
|
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) =>
|
|
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
|
-
|
|
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
|
-
:
|
|
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 =
|
|
118
|
+
const oldCost = costs.get(n.cell) ?? Infinity;
|
|
115
119
|
if (newCost < oldCost && newCost <= maxCost) {
|
|
116
120
|
costs.set(n.cell, newCost);
|
|
117
|
-
pathInfo
|
|
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 =
|
|
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
|
-
|
|
218
|
-
|
|
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(
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
|
|
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
package/package.json
CHANGED