@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.
Files changed (3) hide show
  1. package/lib/grid.d.ts +6 -20
  2. package/lib/grid.js +30 -121
  3. package/package.json +4 -6
package/lib/grid.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Pipe2D } from "@xtia/pipe2d";
2
- import { Angle, GetXYFunc, Source2D } from "./utils.js";
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, get: GetXYFunc<T>, set: (x: number, y: number, value: T) => void, batch: (cb: () => void) => void);
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 { angleToRadians, OrderedQueue } from "./utils.js";
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, get, set, batch) {
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
- if (this.isWritable(x, y))
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
- const tx = x;
245
- const ty = y;
246
- this.trySet(tx, ty, value);
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(width, height, (x, y) => {
371
- return cb(this.get(x, y), aux.get(x, y));
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(width, height, initCell);
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(widthOrSource, height, cellInit) {
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
- this.data = Array.from({ length: source.width * source.height }, (_, i) => {
431
- const x = i % source.width;
432
- const y = Math.floor(i / source.width);
433
- return source.get(x, y);
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.1",
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 jest"
14
+ "test": "npx vitest run"
15
15
  },
16
16
  "files": [
17
17
  "lib/"
18
18
  ],
19
19
  "devDependencies": {
20
- "@swc/jest": "^0.2.39",
21
- "@types/jest": "^30.0.0",
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"