diff --git a/src/grid/constants.ts b/src/grid/constants.ts new file mode 100644 index 00000000..24788c2a --- /dev/null +++ b/src/grid/constants.ts @@ -0,0 +1,4 @@ +// copied from https://github.com/dphilipson/transducist/blob/master/src/propertyNames.ts +export const INIT = '@@transducer/init' +export const RESULT = '@@transducer/result' +export const STEP = '@@transducer/step' diff --git a/src/grid/grid.ts b/src/grid/grid.ts index 8a16699a..542f1e77 100644 --- a/src/grid/grid.ts +++ b/src/grid/grid.ts @@ -1,21 +1,20 @@ -import { transduce, Transducer } from 'transducist' +import { filter, map, toArray, Transducer, Transformer } from 'transducist' import { createHex, Hex, HexCoordinates, Point, pointToCube } from '../hex' import { isFunction } from '../utils' +import { INIT, RESULT, STEP } from './constants' import { concat, distance } from './functions' +import { transduce } from './transduce' import { Traverser } from './types' export class Grid implements Iterable { - static fromIterable(hexes: Map): Grid - static fromIterable(hexes: Iterable): Grid - static fromIterable(iterable: Map | Iterable): Grid { - const iterator = iterable instanceof Map ? iterable.values() : iterable[Symbol.iterator]() - const firstHex = iterator.next().value + static fromIterable(hexes: Iterable): Grid { + const firstHex: T = hexes[Symbol.iterator]().next().value if (!firstHex) { - throw new Error(`Can't create grid from empty iterable: ${iterable}`) + throw new Error(`Can't create grid from empty iterable: ${hexes}`) } - return new Grid(Object.getPrototypeOf(firstHex), iterable) + return new Grid(Object.getPrototypeOf(firstHex), hexes) } get [Symbol.toStringTag]() { @@ -23,32 +22,42 @@ export class Grid implements Iterable { } [Symbol.iterator]() { - return this.#hexes.values() + return this.hexes.values() } - createHex = (coordinates?: HexCoordinates): T => createHex(this.hexPrototype, coordinates) - - #hexes = new Map() + readonly hexPrototype: T + readonly hexes = new Map() + // arrow function to avoid having to call it like so: this.createHex.bind(this) + readonly createHex = (coordinates?: HexCoordinates): T => createHex(this.hexPrototype, coordinates) + constructor(hexPrototype: T) constructor(hexPrototype: T, traversers: Traverser | Traverser[]) - constructor(hexPrototype: T, hexes: Map) - constructor(hexPrototype: T, hexes: Iterable) - constructor(public hexPrototype: T, input: Traverser | Traverser[] | Map | Iterable) { - if (input instanceof Map) { - this.#hexes = new Map(input) - } else if (this.#isTraverser(input)) { - this.#setHexes(input(this.createHex)) - } else if (Array.isArray(input) && this.#isTraverser(input[0])) { - this.#setHexes(concat(input)(this.createHex)) - } else { - this.#setHexes(input as Iterable) + constructor(hexPrototype: T, hexLikes: Iterable) + constructor(grid: Grid) + constructor( + hexPrototypeOrGrid: T | Grid, + input: Traverser | Traverser[] | Iterable = [], + ) { + if (hexPrototypeOrGrid instanceof Grid) { + this.hexPrototype = hexPrototypeOrGrid.hexPrototype + this.setHexes(hexPrototypeOrGrid) + return } - // todo: throw error when all if's fail? + + this.hexPrototype = hexPrototypeOrGrid + this.setHexes(this.#getHexesFromIterableOrTraversers(input)) } getHex(coordinates: HexCoordinates): T | undefined { const hex = this.createHex(coordinates) - return this.#hexes.get(hex.toString()) + return this.hexes.get(hex.toString()) + } + + setHexes(hexes: Iterable): this { + for (const hex of hexes) { + this.hexes.set(hex.toString(), hex) + } + return this } reduce(reducer: (previousHex: T, currentHex: T) => T): T @@ -82,7 +91,7 @@ export class Grid implements Iterable { // return new Map(this.#hexes) // } toMap(): Map { - return new Map(this.#hexes) + return new Map(this.hexes) } toObject(): Record { @@ -93,40 +102,40 @@ export class Grid implements Iterable { return obj } - // enabling stopWhenOutOfBounds can improve performance significantly - traverse(traversers: Traverser | Traverser[], { stopWhenOutOfBounds = false } = {}): T[] { - const hexes: T[] = [] - - for (const hex of concat(traversers)(this.createHex)) { - const existingHex = this.getHex(hex) - if (existingHex) { - hexes.push(existingHex) - } else if (stopWhenOutOfBounds) { - return hexes - } - } - - return hexes + // todo: should probably be a generator, because it's output will be used for transducing + traverse(traversers: Traverser | Traverser[]): T[] { + return transduce( + concat(traversers)(this.createHex), + // todo: move to grid/transformers + [map((hex) => this.getHex(hex)), filter(Boolean)], + toArray(), + ) } clone(): Grid { - const clonedHexes = new Map() - for (const hex of this) { - clonedHexes.set(hex.toString(), hex.clone()) + return new Grid(this) + } + + update(transformers: Transducer | Transducer[]): this + update(transformers: Transducer | Transducer[], hexes: Iterable): this + update(transformers: Transducer | Transducer[], traversers: Traverser | Traverser[]): this + update( + transformers: Transducer | Transducer[], + hexesOrTraversers: Grid | Iterable | Traverser | Traverser[] = this, + ): this { + if (hexesOrTraversers === this) { + transduce(hexesOrTraversers as Grid, transformers, this.#toGridReducer) + return this } - return new Grid(this.hexPrototype, clonedHexes) - } - - update(transformer: Transducer, iterable: Iterable = this): this { - transduce(iterable, transformer, { - // todo: move strings to constants.ts - ['@@transducer/init']: () => this.#hexes, - ['@@transducer/result']: (grid) => grid, - ['@@transducer/step']: (_, hex) => { - this.#hexes.set(hex.toString(), hex) - return this.#hexes - }, - }) + + transduce( + this.#getHexesFromIterableOrTraversers(hexesOrTraversers), + // automatically limit to hexes in grid (unless hexes is already those in the grid) + // todo: move to grid/transformers + [map((hex) => this.getHex(hex)), filter(Boolean)].concat(transformers), + this.#toGridReducer, + ) + return this } @@ -138,13 +147,30 @@ export class Grid implements Iterable { return distance(this.hexPrototype, from, to) } - #setHexes(iterable: Iterable) { - for (const hex of iterable) { - this.#hexes.set(hex.toString(), hex) + *#getHexesFromIterableOrTraversers( + input: Iterable | Traverser | Traverser[], + ): Generator { + const hexLikes = this.#isTraverser(input) + ? input(this.createHex) + : Array.isArray(input) && this.#isTraverser(input[0]) + ? concat(input)(this.createHex) + : (input as Iterable) + + for (const hexLike of hexLikes) { + yield this.createHex(hexLike) } } #isTraverser(value: unknown): value is Traverser { return isFunction>(value) } + + readonly #toGridReducer: Transformer = { + [INIT]: () => this, + [RESULT]: () => this, + [STEP]: (_, hex) => { + this.hexes.set(hex.toString(), hex) + return this + }, + } }