Skip to content

Commit

Permalink
feat(grid): change how a grid's store is set
Browse files Browse the repository at this point in the history
Instead of having to pass a store manually when creating a grid, the grid always makes a store. When
passed one or more traversers, the store is populated with the hexes that the traversers produce.
When passed a store, the grid's store is set to that store and it use those hexes to traverse over.

BREAKING CHANGE: Remove the of() static grid method, because it's a rather redundant as it does the
same as the constructor. Also update the inStore function to be used directly as a grid iterator
method callback (before change: `grid.each(inStore())`, after change: `grid.each(inStore)`). Remove
setStore() because it's ambiguous how the store should be set: hexes could be removed/added/updated
from the store.
  • Loading branch information
flauwekeul committed Apr 22, 2021
1 parent b505b2b commit 2376845
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 53 deletions.
12 changes: 6 additions & 6 deletions playground/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { at, Compass, createHexPrototype, Grid, Hex, inStore, move, Orientation, rectangle, setStore } from '../dist'
import { at, Compass, createHexPrototype, Grid, Hex, inStore, move, Orientation, rectangle } from '../dist'
import { createSuite } from './benchmark'
import { render } from './render'

Expand All @@ -14,16 +14,16 @@ const hexPrototype = createHexPrototype<CustomHex>({
})
// const hex = createHex(hexPrototype, { q: 4, r: 3 })

const store = new Map<string, CustomHex>()
const grid = Grid.of(hexPrototype, rectangle({ start: { q: 0, r: 0 }, width: 10, height: 10 }), store)
.each(setStore())
const grid = new Grid(hexPrototype, rectangle({ start: { q: 0, r: 0 }, width: 10, height: 10 }))
.traverse([at({ q: 9, r: 0 }), move(Compass.SE, 4), move(Compass.SW, 4)])
.filter(inStore())
.filter(inStore)
.each((hex) => {
hex.svg = render(hex)
// console.log(hex)
})
.run()
console.log(grid.store)

createSuite().add('', function () {})
createSuite().add('', function () {
/* */
})
13 changes: 13 additions & 0 deletions src/grid/functions/inStore.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createHex, createHexPrototype } from '../../hex'
import { Grid } from '../grid'
import { at } from '../traversers'
import { inStore } from './inStore'

test('returns whether the passed hex is present in the passed store', () => {
const hexPrototype = createHexPrototype()
const hex = createHex(hexPrototype, { q: 1, r: 2 })

expect(inStore(hex, new Grid(hexPrototype, new Map([[hex.toString(), hex]])))).toBe(true)
expect(inStore(hex, new Grid(hexPrototype, at({ q: 3, r: 4 })))).toBe(false)
expect(inStore(hex, new Grid(hexPrototype))).toBe(false)
})
3 changes: 1 addition & 2 deletions src/grid/functions/inStore.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Hex } from '../../hex'
import { Grid } from '../grid'

export const inStore = <T extends Hex>(store?: Map<string, T>) => (hex: T, grid: Grid<T>): boolean =>
!!(store ?? grid.store)?.get(hex.toString())
export const inStore = <T extends Hex>(hex: T, grid: Grid<T>) => grid.store.has(hex.toString())
1 change: 0 additions & 1 deletion src/grid/functions/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export * from './flatTraverse'
export * from './inStore'
export * from './neighborOf'
export * from './setStore'
5 changes: 0 additions & 5 deletions src/grid/functions/setStore.ts

This file was deleted.

90 changes: 61 additions & 29 deletions src/grid/grid.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,78 @@ import { Traverser } from './types'
const hexPrototype = createHexPrototype()

describe('creation', () => {
test('accepts a single traverser', () => {
const traverser = jest.fn(() => [])
new Grid(hexPrototype, traverser).run()
test(`accepts a single traverser that's called eagerly to set store`, () => {
const hex1 = createHex(hexPrototype, { q: 1, r: 2 })
const hex2 = createHex(hexPrototype, { q: 3, r: 4 })
const traverser = jest.fn(() => [hex1, hex2])
const grid = new Grid(hexPrototype, traverser) /* don't call run() */

expect(traverser).toBeCalledWith(createHex(hexPrototype), grid.getHex)
expect(grid.store).toEqual(
new Map([
['1,2', hex1],
['3,4', hex2],
]),
)

const callback = jest.fn()
grid.run(callback)

expect(traverser).toBeCalledWith(createHex(hexPrototype), expect.any(Function))
expect(callback.mock.calls).toEqual([
[hex1, grid],
[hex2, grid],
])
})

test('accepts multiple traversers', () => {
test('accepts multiple traversers that are called eagerly to set store', () => {
const hex1 = createHex(hexPrototype, { q: 1, r: 2 })
const hex2 = createHex(hexPrototype, { q: 3, r: 4 })
const traverser1 = jest.fn(() => [hex1])
const traverser2 = jest.fn(() => [hex2])
const traversers: Traverser<Hex>[] = [traverser1, traverser2]
const grid = new Grid(hexPrototype, traversers) /* don't call run() yet */

expect(traverser1).toBeCalledWith(createHex(hexPrototype), grid.getHex)
expect(traverser2).toBeCalledWith(hex1, grid.getHex)
expect(grid.store).toEqual(
new Map([
['1,2', hex1],
['3,4', hex2],
]),
)

const callback = jest.fn()
const grid = new Grid(hexPrototype, [at({ q: 1, r: 2 }), at({ q: 3, r: 4 })]).run(callback)
grid.run(callback)

expect(callback.mock.calls).toEqual([
[createHex(hexPrototype, { q: 1, r: 2 }), grid],
[createHex(hexPrototype, { q: 3, r: 4 }), grid],
[hex1, grid],
[hex2, grid],
])
})

test(`accepts a store that's cloned`, () => {
test(`accepts a store that's cloned and its hexes can be traversed`, () => {
const hex = createHex(hexPrototype)
const store = new Map([[hex.toString(), hex]])
const grid = new Grid(hexPrototype, null, store)
const grid = new Grid(hexPrototype, store)

expect(grid.store).toEqual(store)
expect(grid.store).not.toBe(store)

const callback = jest.fn()
grid.run(callback)

expect(callback.mock.calls).toEqual([[hex, grid]])
})

test('creates a stateless grid when called with only a hex prototype', () => {
const grid = new Grid(hexPrototype)

expect(grid.store).toEqual(new Map())

const callback = jest.fn()
grid.run(callback)

expect(callback).not.toBeCalled()
})
})

Expand All @@ -51,7 +99,7 @@ describe('getHex()', () => {
const coordinates = { q: 1, r: 2 }
const hex = createHex(hexPrototype, coordinates)
const store = new Map([[toString(hex), hex]])
const grid = new Grid(hexPrototype, null, store)
const grid = new Grid(hexPrototype, store)

expect(grid.getHex(coordinates)).toBe(hex)
})
Expand All @@ -65,7 +113,7 @@ describe('getHex()', () => {
const coordinates = { q: 1, r: 2 }
const hex = createHex(customPrototype, coordinates)
const store = new Map([['1|2', hex]])
const grid = new Grid(customPrototype, null, store)
const grid = new Grid(customPrototype, store)

expect(grid.getHex(coordinates)).toBe(hex)
})
Expand Down Expand Up @@ -109,22 +157,6 @@ describe('each()', () => {
.run()
expect(callback.mock.calls).toEqual([[createHex(hexPrototype, { q: 5, r: 6 }), grid2]])
})

test(`can add hexes to the grid's store`, () => {
const store = new Map<string, Hex>()
const grid = new Grid(hexPrototype, [at({ q: 1, r: 2 }), at({ q: 3, r: 4 })], store)
.each((hex, grid) => {
grid.store?.set(hex.toString(), hex)
})
.run()

expect(grid.store).toEqual(
new Map([
['1,2', createHex(hexPrototype, { q: 1, r: 2 })],
['3,4', createHex(hexPrototype, { q: 3, r: 4 })],
]),
)
})
})

describe('filter()', () => {
Expand Down Expand Up @@ -204,7 +236,7 @@ describe('traverse()', () => {
const hexInStore = createHex(hexPrototype, { q: 1, r: 2 })
const store = new Map([[hexInStore.toString(), hexInStore]])
const traverser: Traverser<Hex> = (_, getHex) => [getHex({ q: 1, r: 2 })]
const grid = new Grid(hexPrototype, null, store)
const grid = new Grid(hexPrototype, store)
const callback = jest.fn()

grid.traverse(traverser).run(callback)
Expand Down
23 changes: 13 additions & 10 deletions src/grid/grid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,35 @@ import { flatTraverse } from './functions'
import { Callback, Traverser } from './types'

export class Grid<T extends Hex> {
static of<T extends Hex>(hexPrototype: T, traverser?: Traverser<T> | Traverser<T>[] | null, store?: Map<string, T>) {
return new Grid<T>(hexPrototype, traverser, store)
}

get [Symbol.toStringTag]() {
return 'Grid'
}

store?: Map<string, T>
store = new Map<string, T>()
getHex = (coordinates?: HexCoordinates) => {
const hex = createHex(this.hexPrototype).clone(coordinates) // clone to enable users to make custom hexes
return this.store?.get(hex.toString()) ?? hex
return this.store.get(hex.toString()) ?? hex
}

// todo: add getters for hexes and cursor? Then [Symbol.iterator] can be removed. Also, hexes should be stored as a Map?
// then: what's the purpose of passing a store?
private _getPrevHexState: GetHexState<T> = () => ({ hexes: [], cursor: null })

constructor(public hexPrototype: T, traversers?: Traverser<T> | Traverser<T>[] | null, store?: Map<string, T>) {
if (traversers) {
constructor(hexPrototype: T, traversers?: Traverser<T> | Traverser<T>[])
constructor(hexPrototype: T, store?: Map<string, T>)
constructor(public hexPrototype: T, traversersOrStore?: Traverser<T> | Traverser<T>[] | Map<string, T>) {
if (traversersOrStore instanceof Map) {
this._getPrevHexState = () => {
const hexes = flatTraverse(traversers)(this.getHex(), this.getHex)
const hexes = Array.from(traversersOrStore.values())
return { hexes, cursor: hexes[hexes.length - 1] }
}
this.store = new Map(traversersOrStore)
} else if (traversersOrStore) {
const hexes = flatTraverse(traversersOrStore)(this.getHex(), this.getHex)
this._getPrevHexState = () => ({ hexes, cursor: hexes[hexes.length - 1] })
this.store = new Map(hexes.map((hex) => [hex.toString(), hex]))
}
this.store = store && new Map(store)
}

*[Symbol.iterator]() {
Expand Down Expand Up @@ -111,7 +114,7 @@ export class Grid<T extends Hex> {
}

private _clone(getHexState: GetHexState<T>) {
const newGrid = new Grid(this.hexPrototype, null, this.store)
const newGrid = new Grid(this.hexPrototype, this.store)
newGrid._getPrevHexState = getHexState
return newGrid
}
Expand Down

0 comments on commit 2376845

Please sign in to comment.