@xtia/grid 0.0.1 → 0.0.2
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/lib/grid.d.ts +6 -20
- package/lib/grid.js +30 -121
- package/package.json +4 -6
package/lib/grid.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Pipe2D } from "@xtia/pipe2d";
|
|
2
|
-
import {
|
|
2
|
+
import { GetXYFunc, Source2D } from "./utils.js";
|
|
3
3
|
type ChangeEvent<T> = {
|
|
4
4
|
changedCells: Set<Cell<T>>;
|
|
5
5
|
};
|
|
@@ -54,41 +54,28 @@ export declare class Cell<T> {
|
|
|
54
54
|
export declare class Grid<T> {
|
|
55
55
|
readonly width: number;
|
|
56
56
|
readonly height: number;
|
|
57
|
-
private batch;
|
|
58
57
|
private parentGet;
|
|
59
58
|
private parentSet;
|
|
59
|
+
private batch;
|
|
60
60
|
readonly cells: Pipe2D<Cell<T>>;
|
|
61
|
-
protected constructor(width: number, height: number,
|
|
62
|
-
private writeMasks;
|
|
61
|
+
protected constructor(width: number, height: number, parentGet: GetXYFunc<T>, parentSet: (x: number, y: number, value: T) => void, batch: (cb: () => void) => void);
|
|
63
62
|
pipe: Pipe2D<T>;
|
|
64
|
-
private isWritable;
|
|
65
63
|
batchUpdate(fn: () => void): void;
|
|
66
64
|
get(x: number, y: number): T;
|
|
67
65
|
set(x: number, y: number, value: T): void;
|
|
68
66
|
trySet(x: number, y: number, value: T): boolean;
|
|
69
|
-
fill(value: T): void;
|
|
70
|
-
paste(x: number, y: number, source: Source2D<T>): void;
|
|
71
|
-
applyWriteMask(mask: Source2D<boolean>): () => void;
|
|
67
|
+
fill(value: T, mask?: Source2D<boolean>): void;
|
|
68
|
+
paste(x: number, y: number, source: Source2D<T>, mask?: Source2D<boolean>): void;
|
|
72
69
|
[Symbol.iterator](): IterableIterator<{
|
|
73
70
|
x: number;
|
|
74
71
|
y: number;
|
|
75
72
|
value: T;
|
|
76
73
|
}>;
|
|
77
74
|
forEach(callback: (value: T, x: number, y: number) => void): void;
|
|
78
|
-
getVisibilityMap(originX: number, originY: number, maxDistance: number, isWall: (value: T, x: number, y: number) => boolean, options?: {
|
|
79
|
-
startAngle?: Angle;
|
|
80
|
-
endAngle?: Angle;
|
|
81
|
-
includeWalls?: boolean;
|
|
82
|
-
angleStep?: Angle;
|
|
83
|
-
}): Pipe2D<boolean>;
|
|
84
|
-
getFloodMap(startX: number, startY: number, predicate: (value: T, x: number, y: number, initialValue: T) => boolean, options?: {
|
|
85
|
-
includeDiagonals?: boolean;
|
|
86
|
-
maxDistance?: number;
|
|
87
|
-
}): Pipe2D<boolean>;
|
|
88
75
|
combine<U, R>(aux: Source2D<U>, cb: (source: T, aux: U) => R): GridBase<R>;
|
|
89
76
|
map<U>(read: (initial: T, x: number, y: number) => U, write: (local: U, x: number, y: number) => T): Grid<U>;
|
|
90
77
|
region(x: number, y: number, width: number, height: number): Grid<T>;
|
|
91
|
-
static init<T>(width: number, height: number, initCell: () => T): GridBase<T>;
|
|
78
|
+
static init<T>(width: number, height: number, initCell: (x: number, y: number) => T): GridBase<T>;
|
|
92
79
|
static from<T>(source: Source2D<T>): GridBase<T>;
|
|
93
80
|
static solid<T>(width: number, height: number, fillValue: T): GridBase<T>;
|
|
94
81
|
}
|
|
@@ -99,7 +86,6 @@ export declare class GridBase<T> extends Grid<T> {
|
|
|
99
86
|
private batchState;
|
|
100
87
|
batchUpdate(fn: () => void): void;
|
|
101
88
|
constructor(source: Source2D<T>);
|
|
102
|
-
constructor(width: number, height: number, cellInit: GetXYFunc<T>);
|
|
103
89
|
on<K extends keyof EventMap<T>>(eventName: K, handler: (data: EventMap<T>[K]) => void): () => void;
|
|
104
90
|
private xyToIndex;
|
|
105
91
|
private _set;
|
package/lib/grid.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Pipe2D } from "@xtia/pipe2d";
|
|
2
|
-
import {
|
|
2
|
+
import { OrderedQueue } from "./utils.js";
|
|
3
3
|
export var TraversalType;
|
|
4
4
|
(function (TraversalType) {
|
|
5
5
|
TraversalType[TraversalType["cardinal"] = 0] = "cardinal";
|
|
@@ -177,6 +177,8 @@ export class Cell {
|
|
|
177
177
|
}
|
|
178
178
|
createVisibilityMap(isWall, maxDistance = Infinity) {
|
|
179
179
|
return new Pipe2D(this.gridView.width, this.gridView.height, (x, y) => {
|
|
180
|
+
if (isWall(this))
|
|
181
|
+
return Visibility.hidden;
|
|
180
182
|
const distance = getDistance(this, { x, y });
|
|
181
183
|
if (distance > maxDistance) {
|
|
182
184
|
return Visibility.hidden;
|
|
@@ -200,20 +202,16 @@ function getDistance(c1, c2) {
|
|
|
200
202
|
return Math.sqrt(Math.pow(c2.x - c1.x, 2) + Math.pow(c2.y - c1.y, 2));
|
|
201
203
|
}
|
|
202
204
|
export class Grid {
|
|
203
|
-
constructor(width, height,
|
|
205
|
+
constructor(width, height, parentGet, parentSet, batch) {
|
|
204
206
|
this.width = width;
|
|
205
207
|
this.height = height;
|
|
208
|
+
this.parentGet = parentGet;
|
|
209
|
+
this.parentSet = parentSet;
|
|
206
210
|
this.batch = batch;
|
|
207
211
|
this.cells = new Pipe2D(this.width, this.height, (x, y) => {
|
|
208
212
|
return new Cell(x, y, this);
|
|
209
213
|
}).strict().withCache();
|
|
210
|
-
this.writeMasks = [];
|
|
211
214
|
this.pipe = new Pipe2D(this);
|
|
212
|
-
this.parentGet = get;
|
|
213
|
-
this.parentSet = set;
|
|
214
|
-
}
|
|
215
|
-
isWritable(x, y) {
|
|
216
|
-
return !this.writeMasks.some(mask => !mask.mask.get(x, y));
|
|
217
215
|
}
|
|
218
216
|
batchUpdate(fn) {
|
|
219
217
|
this.batch(fn);
|
|
@@ -228,8 +226,7 @@ export class Grid {
|
|
|
228
226
|
if (x < 0 || x >= this.width || y < 0 || y >= this.height) {
|
|
229
227
|
throw new RangeError("Coordinates out of bounds");
|
|
230
228
|
}
|
|
231
|
-
|
|
232
|
-
this.parentSet(x, y, value);
|
|
229
|
+
this.parentSet(x, y, value);
|
|
233
230
|
}
|
|
234
231
|
trySet(x, y, value) {
|
|
235
232
|
if (x < 0 || x >= this.width || y < 0 || y >= this.height)
|
|
@@ -237,39 +234,31 @@ export class Grid {
|
|
|
237
234
|
this.set(x, y, value);
|
|
238
235
|
return true;
|
|
239
236
|
}
|
|
240
|
-
fill(value) {
|
|
237
|
+
fill(value, mask) {
|
|
241
238
|
this.batchUpdate(() => {
|
|
242
239
|
for (let y = 0; y < this.height; y++) {
|
|
243
240
|
for (let x = 0; x < this.width; x++) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
this.trySet(
|
|
241
|
+
if (mask && !mask.get(x, y))
|
|
242
|
+
continue;
|
|
243
|
+
this.trySet(x, y, value);
|
|
247
244
|
}
|
|
248
245
|
}
|
|
249
246
|
});
|
|
250
247
|
}
|
|
251
|
-
paste(x, y, source) {
|
|
248
|
+
paste(x, y, source, mask) {
|
|
252
249
|
const cachedSource = new GridBase(source);
|
|
253
250
|
this.batchUpdate(() => {
|
|
254
251
|
for (let oy = 0; oy < source.height; oy++) {
|
|
255
252
|
for (let ox = 0; ox < source.width; ox++) {
|
|
256
253
|
const tx = ox + x;
|
|
257
254
|
const ty = oy + y;
|
|
255
|
+
if (mask && !mask.get(tx, ty))
|
|
256
|
+
continue;
|
|
258
257
|
this.trySet(tx, ty, cachedSource.get(ox, oy));
|
|
259
258
|
}
|
|
260
259
|
}
|
|
261
260
|
});
|
|
262
261
|
}
|
|
263
|
-
applyWriteMask(mask) {
|
|
264
|
-
const unique = { mask };
|
|
265
|
-
this.writeMasks.push(unique);
|
|
266
|
-
return () => {
|
|
267
|
-
const idx = this.writeMasks.indexOf(unique);
|
|
268
|
-
if (idx == -1)
|
|
269
|
-
throw new Error("Mask was not present");
|
|
270
|
-
this.writeMasks.splice(idx, 1);
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
262
|
*[Symbol.iterator]() {
|
|
274
263
|
for (let y = 0; y < this.height; y++) {
|
|
275
264
|
for (let x = 0; x < this.width; x++) {
|
|
@@ -284,91 +273,15 @@ export class Grid {
|
|
|
284
273
|
}
|
|
285
274
|
}
|
|
286
275
|
}
|
|
287
|
-
getVisibilityMap(originX, originY, maxDistance, isWall, options) {
|
|
288
|
-
const { startAngle = { asTurns: 0 }, endAngle = { asTurns: 1 }, includeWalls = false, angleStep = { asDegrees: 1 } } = options || {};
|
|
289
|
-
const visibilityGrid = new GridBase(this.width, this.height, () => false);
|
|
290
|
-
const startRad = angleToRadians(startAngle);
|
|
291
|
-
const endRad = angleToRadians(endAngle);
|
|
292
|
-
const stepRad = angleToRadians(angleStep);
|
|
293
|
-
const totalAngle = endRad > startRad
|
|
294
|
-
? endRad - startRad
|
|
295
|
-
: endRad + (2 * Math.PI) - startRad;
|
|
296
|
-
const effectiveStepRad = Math.max(0.001, Math.min(stepRad, totalAngle));
|
|
297
|
-
const steps = Math.ceil(totalAngle / effectiveStepRad);
|
|
298
|
-
for (let i = 0; i <= steps; i++) {
|
|
299
|
-
const angle = startRad + i * effectiveStepRad;
|
|
300
|
-
if (angle > endRad && i > 0)
|
|
301
|
-
break;
|
|
302
|
-
const endX = Math.round(originX + maxDistance * Math.cos(angle));
|
|
303
|
-
const endY = Math.round(originY + maxDistance * Math.sin(angle));
|
|
304
|
-
let x0 = originX;
|
|
305
|
-
let y0 = originY;
|
|
306
|
-
let x1 = endX;
|
|
307
|
-
let y1 = endY;
|
|
308
|
-
const dx = Math.abs(x1 - x0);
|
|
309
|
-
const dy = Math.abs(y1 - y0);
|
|
310
|
-
const sx = x0 < x1 ? 1 : -1;
|
|
311
|
-
const sy = y0 < y1 ? 1 : -1;
|
|
312
|
-
let err = dx - dy;
|
|
313
|
-
while (true) {
|
|
314
|
-
if (x0 < 0 || x0 >= this.width || y0 < 0 || y0 >= this.height) {
|
|
315
|
-
break;
|
|
316
|
-
}
|
|
317
|
-
const isWallCell = isWall(this.get(x0, y0), x0, y0);
|
|
318
|
-
if (isWallCell) {
|
|
319
|
-
if (includeWalls) {
|
|
320
|
-
visibilityGrid.set(x0, y0, true);
|
|
321
|
-
}
|
|
322
|
-
break;
|
|
323
|
-
}
|
|
324
|
-
else {
|
|
325
|
-
visibilityGrid.set(x0, y0, true);
|
|
326
|
-
}
|
|
327
|
-
if (x0 === x1 && y0 === y1)
|
|
328
|
-
break;
|
|
329
|
-
const e2 = 2 * err;
|
|
330
|
-
if (e2 > -dy) {
|
|
331
|
-
err -= dy;
|
|
332
|
-
x0 += sx;
|
|
333
|
-
}
|
|
334
|
-
if (e2 < dx) {
|
|
335
|
-
err += dx;
|
|
336
|
-
y0 += sy;
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
return visibilityGrid.pipe;
|
|
341
|
-
}
|
|
342
|
-
getFloodMap(startX, startY, predicate, options) {
|
|
343
|
-
const { includeDiagonals = false, maxDistance = Infinity } = options || {};
|
|
344
|
-
const result = new GridBase(this.width, this.height, () => false);
|
|
345
|
-
const startValue = this.get(startX, startY);
|
|
346
|
-
if (!predicate(startValue, startX, startY, startValue)) {
|
|
347
|
-
return result.pipe;
|
|
348
|
-
}
|
|
349
|
-
const queue = [[startX, startY, 0]];
|
|
350
|
-
result.set(startX, startY, true);
|
|
351
|
-
while (queue.length > 0) {
|
|
352
|
-
const [x, y, distance] = queue.shift();
|
|
353
|
-
if (distance >= maxDistance)
|
|
354
|
-
continue;
|
|
355
|
-
this.cells.get(x, y).getNeighbours(includeDiagonals)
|
|
356
|
-
.forEach(neighbour => {
|
|
357
|
-
if (result.get(neighbour.x, neighbour.y))
|
|
358
|
-
return;
|
|
359
|
-
if (predicate(neighbour.value, neighbour.x, neighbour.y, startValue)) {
|
|
360
|
-
result.set(neighbour.x, neighbour.y, true);
|
|
361
|
-
queue.push([neighbour.x, neighbour.y, distance + 1]);
|
|
362
|
-
}
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
return result.pipe;
|
|
366
|
-
}
|
|
367
276
|
combine(aux, cb) {
|
|
368
277
|
const width = Math.min(this.width, aux.width);
|
|
369
278
|
const height = Math.min(this.height, aux.height);
|
|
370
|
-
return new GridBase(
|
|
371
|
-
|
|
279
|
+
return new GridBase({
|
|
280
|
+
width,
|
|
281
|
+
height,
|
|
282
|
+
get: (x, y) => {
|
|
283
|
+
return cb(this.get(x, y), aux.get(x, y));
|
|
284
|
+
}
|
|
372
285
|
});
|
|
373
286
|
}
|
|
374
287
|
map(read, write) {
|
|
@@ -384,7 +297,11 @@ export class Grid {
|
|
|
384
297
|
}, fn => this.batch(fn));
|
|
385
298
|
}
|
|
386
299
|
static init(width, height, initCell) {
|
|
387
|
-
return new GridBase(
|
|
300
|
+
return new GridBase({
|
|
301
|
+
width,
|
|
302
|
+
height,
|
|
303
|
+
get: initCell
|
|
304
|
+
});
|
|
388
305
|
}
|
|
389
306
|
static from(source) {
|
|
390
307
|
return new GridBase(source);
|
|
@@ -416,22 +333,14 @@ export class GridBase extends Grid {
|
|
|
416
333
|
this.triggerChange(changed);
|
|
417
334
|
}
|
|
418
335
|
}
|
|
419
|
-
constructor(
|
|
420
|
-
const source = typeof widthOrSource == "object"
|
|
421
|
-
? widthOrSource
|
|
422
|
-
: {
|
|
423
|
-
width: widthOrSource,
|
|
424
|
-
height: height,
|
|
425
|
-
get: cellInit
|
|
426
|
-
};
|
|
336
|
+
constructor(source) {
|
|
427
337
|
super(source.width, source.height, (x, y) => this.data[this.xyToIndex(x, y)], (x, y, value) => this._set(x, y, value), fn => this.batchUpdate(fn));
|
|
428
338
|
this.eventHandlers = {};
|
|
429
339
|
this.batchState = null;
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
});
|
|
340
|
+
const sourcePipe = source instanceof Pipe2D
|
|
341
|
+
? source
|
|
342
|
+
: new Pipe2D(source);
|
|
343
|
+
this.data = sourcePipe.toFlatArrayXY();
|
|
435
344
|
}
|
|
436
345
|
on(eventName, handler) {
|
|
437
346
|
const unique = { fn: handler };
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.0.
|
|
2
|
+
"version": "0.0.2",
|
|
3
3
|
"description": "2D store and utilities",
|
|
4
4
|
"name": "@xtia/grid",
|
|
5
5
|
"exports": {
|
|
@@ -11,16 +11,14 @@
|
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
13
|
"build": "npx tsc",
|
|
14
|
-
"test": "npx
|
|
14
|
+
"test": "npx vitest run"
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"lib/"
|
|
18
18
|
],
|
|
19
19
|
"devDependencies": {
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"jest": "^30.4.2",
|
|
23
|
-
"typescript": "^7.0.2"
|
|
20
|
+
"typescript": "^7.0.2",
|
|
21
|
+
"vitest": "^4.1.10"
|
|
24
22
|
},
|
|
25
23
|
"dependencies": {
|
|
26
24
|
"@xtia/pipe2d": "^2.1.0"
|