@xtia/grid 0.0.10 → 0.0.12

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
@@ -91,7 +91,7 @@ const mask = (x: number, y: number) => {
91
91
  const dist = Math.hypot(x - centre.x, y - centre.y);
92
92
  return dist > 12 && dist < 18; // mountain ring
93
93
  };
94
- world.fill("mountain", mask);
94
+ world.writeMask(mask).fill("mountain");
95
95
 
96
96
  // get a view of just the interesting area
97
97
  const lakeRegion = world.region(25, 25, 30, 30);
@@ -203,9 +203,8 @@ const visibility = start.createVisibilityMap(
203
203
  cell => cell.value === "grass" // anything except grass blocks vision
204
204
  );
205
205
 
206
- // any 2d source, including visibility maps, can be used as a mask for paste/fill operations
207
- // paste(source: Source2D<T>, mask?: Source2D<boolean>)
208
- screenGrid.paste(spriteMap, 0, 0, visibility);
206
+ // writeMask() creates a live view that only allows writes at positions allowed by the mask Source2D/function
207
+ screenGrid.writeMask(visibility).paste(spriteMap, 0, 0);
209
208
  ```
210
209
 
211
210
  ### Reusing path data
@@ -234,8 +233,17 @@ We can perform batched updates, suppressing the 'change' event until a process c
234
233
  Pipe2D makes it easy to save and restore grid data:
235
234
 
236
235
  ```ts
237
- const saved = world.pipe.toFlatArrayXY(); // ["mountain", "forest", "grass", ...]
236
+ const snapshot = world.pipe.stash();
238
237
 
239
- const restoredPipe = Pipe2D.fromFlatArrayXY(saved);
240
- const restored = Grid.from(restoredPipe);
241
- ```
238
+ const restored = Grid.from(snapshot);
239
+ // or paste it to an existing grid
240
+ world.paste(snapshot);
241
+ ```
242
+
243
+ For persistent storage or transmission, Pipe2D can export as array:
244
+
245
+ ```ts
246
+ const saved = snapshot.toFlatArrayXY(); // ["mountain", "forest", "grass", ...]
247
+
248
+ const restoredSnapshot = Pipe2D.fromFlatArrayXY(saved);
249
+ ```
package/lib/grid.d.ts CHANGED
@@ -64,8 +64,9 @@ 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> | GetXYFunc<boolean>): void;
68
- paste(source: Source2D<T>, x?: number, y?: number, mask?: Source2D<boolean> | GetXYFunc<boolean>): void;
67
+ fill(value: T): void;
68
+ paste(source: Source2D<T>, x?: number, y?: number): void;
69
+ writeMask(mask: Source2D<boolean> | GetXYFunc<boolean>): Grid<T>;
69
70
  [Symbol.iterator](): IterableIterator<{
70
71
  x: number;
71
72
  y: number;
@@ -75,6 +76,7 @@ export declare class Grid<T> {
75
76
  combine<U, R>(aux: Source2D<U>, cb: (source: T, aux: U) => R): GridBase<R>;
76
77
  map<U>(read: (initial: T, x: number, y: number) => U, write: (local: U, x: number, y: number) => T): Grid<U>;
77
78
  region(x: number, y: number, width: number, height: number): Grid<T>;
79
+ scroll(xDelta: number, yDelta: number, fill: T): this;
78
80
  static init<T>(width: number, height: number, initCell: (x: number, y: number) => T): GridBase<T>;
79
81
  static from<T>(source: Source2D<T>): GridBase<T>;
80
82
  static solid<T>(width: number, height: number, fillValue: T): GridBase<T>;
package/lib/grid.js CHANGED
@@ -218,17 +218,29 @@ export class Grid {
218
218
  return new Cell(x, y, this);
219
219
  }).strict().withCache();
220
220
  this.pipe = new Pipe2D(this);
221
+ if (!Number.isInteger(width) || !Number.isInteger(height)) {
222
+ throw new Error(`Grid width & height must be integer; got (${width} x ${height})`);
223
+ }
224
+ if (width < 0 || height < 0) {
225
+ throw new Error(`Grid width/height may not be less than 0; got (${width} x ${height})`);
226
+ }
221
227
  }
222
228
  batchUpdate(fn) {
223
229
  this.batch(fn);
224
230
  }
225
231
  get(x, y) {
232
+ if (!Number.isInteger(x) || !Number.isInteger(y)) {
233
+ throw new Error(`Coordinates must be integer; got (${x}, ${y})`);
234
+ }
226
235
  if (x < 0 || x >= this.width || y < 0 || y >= this.height) {
227
236
  throw new RangeError("Coordinates out of bounds");
228
237
  }
229
238
  return this.parentGet(x, y);
230
239
  }
231
240
  set(x, y, value) {
241
+ if (!Number.isInteger(x) || !Number.isInteger(y)) {
242
+ throw new Error(`Coordinates must be integer; got (${x}, ${y})`);
243
+ }
232
244
  if (x < 0 || x >= this.width || y < 0 || y >= this.height) {
233
245
  throw new RangeError("Coordinates out of bounds");
234
246
  }
@@ -245,41 +257,36 @@ export class Grid {
245
257
  this.set(x, y, value);
246
258
  return true;
247
259
  }
248
- fill(value, mask) {
249
- const maskSource = typeof mask == "function" ? {
250
- width: this.width,
251
- height: this.height,
252
- get: mask
253
- } : mask;
260
+ fill(value) {
254
261
  this.batchUpdate(() => {
255
262
  for (let y = 0; y < this.height; y++) {
256
263
  for (let x = 0; x < this.width; x++) {
257
- if (maskSource && !maskSource.get(x, y))
258
- continue;
259
264
  this.trySet(x, y, value);
260
265
  }
261
266
  }
262
267
  });
263
268
  }
264
- paste(source, x = 0, y = 0, mask) {
265
- const maskSource = typeof mask == "function" ? {
266
- width: this.width,
267
- height: this.height,
268
- get: mask
269
- } : mask;
270
- const cachedSource = new GridBase(source);
269
+ paste(source, x = 0, y = 0) {
270
+ const cachedSource = new Pipe2D(source).stash();
271
271
  this.batchUpdate(() => {
272
272
  for (let oy = 0; oy < source.height; oy++) {
273
273
  for (let ox = 0; ox < source.width; ox++) {
274
- const tx = ox + x;
275
- const ty = oy + y;
276
- if (maskSource && !maskSource.get(tx, ty))
277
- continue;
278
- this.trySet(tx, ty, cachedSource.get(ox, oy));
274
+ this.trySet(ox + x, oy + y, cachedSource.get(ox, oy));
279
275
  }
280
276
  }
281
277
  });
282
278
  }
279
+ writeMask(mask) {
280
+ const getMask = typeof mask == "function"
281
+ ? mask
282
+ : (x, y) => mask.get(x, y);
283
+ return new Grid(this.width, this.height, this.parentGet, (x, y, v) => {
284
+ const writable = getMask(x, y);
285
+ if (writable) {
286
+ this.set(x, y, v);
287
+ }
288
+ }, cb => this.batch(cb));
289
+ }
283
290
  *[Symbol.iterator]() {
284
291
  for (let y = 0; y < this.height; y++) {
285
292
  for (let x = 0; x < this.width; x++) {
@@ -309,12 +316,37 @@ export class Grid {
309
316
  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));
310
317
  }
311
318
  region(x, y, width, height) {
319
+ if (!Number.isInteger(x) || !Number.isInteger(y) || !Number.isInteger(width) || !Number.isInteger(height)) {
320
+ throw new Error(`Region boundaries must be integer; got (${x}, ${y}), (${width} x ${height})`);
321
+ }
322
+ if (x < 0 || y < 0 || x + width > this.width || y + height > this.height) {
323
+ throw new Error("Requested region exceeds the parent's bounds");
324
+ }
312
325
  return new Grid(width, height, (tx, ty) => {
313
326
  return this.parentGet(tx + x, ty + y);
314
327
  }, (tx, ty, value) => {
315
328
  this.parentSet(tx + x, ty + y, value);
316
329
  }, fn => this.batch(fn));
317
330
  }
331
+ scroll(xDelta, yDelta, fill) {
332
+ if (!Number.isInteger(xDelta) || !Number.isInteger(yDelta)) {
333
+ throw new Error(`Scroll offsets must be integer; got (${xDelta}, ${yDelta})`);
334
+ }
335
+ this.paste(this, xDelta, yDelta);
336
+ if (xDelta > 0) {
337
+ this.region(0, 0, xDelta, this.height).fill(fill);
338
+ }
339
+ else if (xDelta < 0) {
340
+ this.region(this.width + xDelta, 0, -xDelta, this.height).fill(fill);
341
+ }
342
+ if (yDelta > 0) {
343
+ this.region(0, 0, this.width, yDelta).fill(fill);
344
+ }
345
+ else if (yDelta < 0) {
346
+ this.region(0, this.height + yDelta, this.width, -yDelta).fill(fill);
347
+ }
348
+ return this;
349
+ }
318
350
  static init(width, height, initCell) {
319
351
  return new GridBase({
320
352
  width,
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.0.10",
2
+ "version": "0.0.12",
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": {