@xtia/grid 0.1.0 → 0.2.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 CHANGED
@@ -22,7 +22,7 @@ npm i @xtia/grid
22
22
  ```
23
23
 
24
24
  ```ts
25
- import { Grid } from "@xtia/grid"; // ~4.8kb gzipped
25
+ import { Grid } from "@xtia/grid"; // ~4.8kb gzipped (2.2kb + Pipe2D dep)
26
26
 
27
27
  // initialise a 30x20 Grid<number> of 0's
28
28
  const numGrid = Grid.solid(30, 20, 0);
@@ -32,7 +32,7 @@ numGrid.set(3, 3, 50);
32
32
 
33
33
  // initialise a chessboard
34
34
  const chessGrid = Grid.init(8, 8, (x, y) =>
35
- (x + y) % 2 === 0 ? 'black' : 'white'
35
+ (x + y) % 2 === 0 ? 'black' : 'white'
36
36
  );
37
37
 
38
38
  // read a value
@@ -43,7 +43,7 @@ const source = imagePipe
43
43
  .crop(10, 10, 64, 64)
44
44
  .scale(.5)
45
45
  .rotateLeft();
46
- const grid = Grid.from(source);
46
+ const grid = Grid.init(source);
47
47
  ```
48
48
 
49
49
  ### Wrapping existing structures
@@ -97,7 +97,7 @@ world.writeMask(mask).fill("mountain");
97
97
  const lakeRegion = world.region(25, 25, 30, 30);
98
98
 
99
99
  // do we want a distinct, self-contained copy of the region?
100
- const lakeGrid = Grid.from(lakeRegion);
100
+ const lakeGrid = Grid.init(lakeRegion);
101
101
  ```
102
102
 
103
103
  ## Transformation Layers
@@ -106,12 +106,11 @@ const lakeGrid = Grid.from(lakeRegion);
106
106
 
107
107
  ```ts
108
108
  const spriteMap = world.map(
109
- type => type + ".png",
110
- sprite => sprite.replace(/\..*/, '')
109
+ type => type + ".png", // add .png on parent-read
110
+ sprite => sprite.replace(/\..*/, '') // remove it on parent-write
111
111
  );
112
112
 
113
- world.set(0, 0, "mountain"); // modify the parent
114
- console.log(spriteMap.get(0, 0)); // read the transformed view: "mountain.png"
113
+ console.log(spriteMap.get(0, 0)); // read the transformed view: "grass.png"
115
114
  spriteMap.set(1, 0, "forest.png"); // modify the view
116
115
  console.log(world.get(1, 0)); // read the parent: "forest"
117
116
  ```
@@ -144,7 +143,6 @@ const minimap = world.pipe
144
143
  .floorCoordinates()
145
144
  .crop(viewX, viewY, vw, vh)
146
145
  .stretch(canvas.width, canvas.height);
147
-
148
146
  ```
149
147
 
150
148
  ## Cell interface
@@ -227,9 +225,9 @@ Unlike visibility maps, which are lazily evaluated according to the supplied `is
227
225
 
228
226
  ## The storage layer
229
227
 
230
- `Grid` itself is an interface for reading and writing 2D data. `GridBase` - a subclass of `Grid` - maintains the actual storage of such data.
228
+ `Grid` itself is an interface that sits on top of a synchronous 2D read/write mechanism. `GridBase` is a subclass of `Grid` with built-in storage.
231
229
 
232
- 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)`.
230
+ Although the factory methods `Grid.solid<T>()` and `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)`.
233
231
 
234
232
  We can perform batched updates, suppressing the 'change' event until a process concludes, with `grid.batchUpdate(callback)`.
235
233
 
@@ -240,7 +238,7 @@ Pipe2D makes it easy to save and restore grid data:
240
238
  ```ts
241
239
  const snapshot = world.pipe.stash();
242
240
 
243
- const restored = Grid.from(snapshot);
241
+ const restored = Grid.init(snapshot);
244
242
  // or paste it to an existing grid
245
243
  world.paste(snapshot);
246
244
  ```
@@ -248,7 +246,7 @@ world.paste(snapshot);
248
246
  For persistent storage or transmission, Pipe2D can export as array:
249
247
 
250
248
  ```ts
251
- const saved = snapshot.toFlatArrayXY(); // ["mountain", "forest", "grass", ...]
249
+ const saved = snapshot.toFlatArrayXY(); // ["grass", "forest", "grass", ...]
252
250
 
253
251
  const restoredSnapshot = Pipe2D.fromFlatArrayXY(saved);
254
252
  ```
package/lib/grid.d.ts CHANGED
@@ -67,18 +67,13 @@ export declare class Grid<T> {
67
67
  fill(value: T): void;
68
68
  paste(source: Source2D<T>, x?: number, y?: number): void;
69
69
  writeMask(mask: Source2D<boolean> | GetXYFunc<boolean>): Grid<T>;
70
- [Symbol.iterator](): IterableIterator<{
71
- x: number;
72
- y: number;
73
- value: T;
74
- }>;
70
+ [Symbol.iterator](): IterableIterator<T>;
75
71
  forEach(callback: (value: T, x: number, y: number) => void): void;
76
- combine<U, R>(aux: Source2D<U>, cb: (source: T, aux: U) => R): GridBase<R>;
77
72
  map<U>(read: (initial: T, x: number, y: number) => U, write: (local: U, x: number, y: number) => T): Grid<U>;
78
73
  region(x: number, y: number, width: number, height: number): Grid<T>;
79
74
  scroll(xDelta: number, yDelta: number, fill: T): this;
80
75
  static init<T>(width: number, height: number, initCell: (x: number, y: number) => T): GridBase<T>;
81
- static from<T>(source: Source2D<T>): GridBase<T>;
76
+ static init<T>(source: Source2D<T>): GridBase<T>;
82
77
  static solid<T>(width: number, height: number, fillValue: T): GridBase<T>;
83
78
  static wrap<T>(width: number, height: number, get: (x: number, y: number) => T, set: (x: number, y: number, value: T) => void, batch?: (callback: () => void) => void): Grid<T>;
84
79
  }
@@ -90,7 +85,6 @@ export declare class GridBase<T> extends Grid<T> {
90
85
  batchUpdate(fn: () => void): void;
91
86
  constructor(source: Source2D<T>);
92
87
  on<K extends keyof EventMap<T>>(eventName: K, handler: (data: EventMap<T>[K]) => void): () => void;
93
- private xyToIndex;
94
88
  private _set;
95
89
  private triggerChange;
96
90
  }
package/lib/grid.js CHANGED
@@ -215,6 +215,9 @@ export class Grid {
215
215
  this.parentSet = parentSet;
216
216
  this.batch = batch;
217
217
  this.cells = new Pipe2D(this.width, this.height, (x, y) => {
218
+ if (x < 0 || x >= this.width || y < 0 || y >= this.height || !Number.isInteger(x) || !Number.isInteger(y)) {
219
+ throw new RangeError(`Cell coordinates must be bounded integers; got (${x}, ${y})`);
220
+ }
218
221
  return new Cell(x, y, this);
219
222
  }).strict().withCache();
220
223
  this.pipe = new Pipe2D(this);
@@ -293,7 +296,7 @@ export class Grid {
293
296
  *[Symbol.iterator]() {
294
297
  for (let y = 0; y < this.height; y++) {
295
298
  for (let x = 0; x < this.width; x++) {
296
- yield { x, y, value: this.get(x, y) };
299
+ yield this.get(x, y);
297
300
  }
298
301
  }
299
302
  }
@@ -304,22 +307,11 @@ export class Grid {
304
307
  }
305
308
  }
306
309
  }
307
- combine(aux, cb) {
308
- const width = Math.min(this.width, aux.width);
309
- const height = Math.min(this.height, aux.height);
310
- return new GridBase({
311
- width,
312
- height,
313
- get: (x, y) => {
314
- return cb(this.get(x, y), aux.get(x, y));
315
- }
316
- });
317
- }
318
310
  map(read, write) {
319
311
  return new Grid(this.width, this.height, (x, y) => read(this.parentGet(x, y), x, y), (x, y, value) => this.parentSet(x, y, write(value, x, y)), fn => this.batch(fn));
320
312
  }
321
313
  region(x, y, width, height) {
322
- if (!Number.isInteger(x) || !Number.isInteger(y) || !Number.isInteger(width) || !Number.isInteger(height)) {
314
+ if (!Number.isInteger(x) || !Number.isInteger(y)) {
323
315
  throw new Error(`Region boundaries must be integer; got (${x}, ${y}), (${width} x ${height})`);
324
316
  }
325
317
  if (x < 0 || y < 0 || x + width > this.width || y + height > this.height) {
@@ -350,16 +342,15 @@ export class Grid {
350
342
  }
351
343
  return this;
352
344
  }
353
- static init(width, height, initCell) {
345
+ static init(widthOrSource, height, initCell) {
346
+ if (typeof widthOrSource == "object")
347
+ return new GridBase(widthOrSource);
354
348
  return new GridBase({
355
- width,
356
- height,
349
+ width: widthOrSource,
350
+ height: height,
357
351
  get: initCell
358
352
  });
359
353
  }
360
- static from(source) {
361
- return new GridBase(source);
362
- }
363
354
  static solid(width, height, fillValue) {
364
355
  return new GridBase(Pipe2D.solid(fillValue, width, height));
365
356
  }
@@ -387,11 +378,12 @@ export class GridBase extends Grid {
387
378
  if (this.batchState.level == 0) {
388
379
  const changed = this.batchState.changedCells;
389
380
  this.batchState = null;
390
- this.triggerChange(changed);
381
+ if (changed.size > 0)
382
+ this.triggerChange(changed);
391
383
  }
392
384
  }
393
385
  constructor(source) {
394
- 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));
386
+ 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));
395
387
  this.eventHandlers = {};
396
388
  this.batchState = null;
397
389
  const sourcePipe = source instanceof Pipe2D
@@ -411,20 +403,11 @@ export class GridBase extends Grid {
411
403
  this.eventHandlers[eventName].splice(idx, 1);
412
404
  };
413
405
  }
414
- xyToIndex(x, y) {
415
- if (x < 0 || x >= this.width || y < 0 || y >= this.height) {
416
- throw new Error(`Coordinates out of bounds: (${x}, ${y})`);
417
- }
418
- if (!Number.isInteger(x) || !Number.isInteger(y)) {
419
- throw new Error(`Coordinates must be integer; got (${x}, ${y})`);
420
- }
421
- return y * this.width + x;
422
- }
423
406
  _set(x, y, value) {
424
- const idx = this.xyToIndex(x, y);
407
+ const idx = y * this.width + x;
425
408
  if (this.data[idx] === value)
426
409
  return;
427
- this.data[this.xyToIndex(x, y)] = value;
410
+ this.data[idx] = value;
428
411
  if (this.batchState) {
429
412
  this.batchState.changedCells.add(this.cells.get(x, y));
430
413
  return;
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.1.0",
2
+ "version": "0.2.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": {