Skip to content

Commit

Permalink
feat(grid): add hexes() and cursor() methods to Grid
Browse files Browse the repository at this point in the history
Like run(), both hexes() and cursor() "flush" the previous hex state created by iterator methods.
They offer a convenient alternative to run() as they immediately return an array of hexes and cursor
respectively.
  • Loading branch information
flauwekeul committed Apr 22, 2021
1 parent beba860 commit 5738045
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 92 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ let grid = new Grid(hexPrototype, rectangle({ width: 10, height: 10 }))
// 3. Iterate over the grid to log each hex (notice a new grid instance is returned):
grid = grid.each((hex) => console.log(hex))

// 4. The above won't do anything yet, that's because grid methods are executed lazily.
// 4. The above won't do anything yet, that's because the "iterative" grid methods are executed lazily.
// You need to call its run() method in order to execute the each() call (and most other method calls):
grid.run()
```
Expand Down
134 changes: 48 additions & 86 deletions src/grid/grid.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,8 @@ describe('creation', () => {
['3,4', hex2],
]),
)

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

expect(callback.mock.calls).toEqual([
[hex1, grid],
[hex2, grid],
])
expect(grid.hexes()).toEqual([hex1, hex2])
expect(grid.cursor()).toEqual(hex2)
})

test('accepts multiple traversers that are called eagerly to set store', () => {
Expand All @@ -45,14 +39,8 @@ describe('creation', () => {
['3,4', hex2],
]),
)

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

expect(callback.mock.calls).toEqual([
[hex1, grid],
[hex2, grid],
])
expect(grid.hexes()).toEqual([hex1, hex2])
expect(grid.cursor()).toEqual(hex2)
})

test(`accepts a store that's cloned and its hexes can be traversed`, () => {
Expand All @@ -62,22 +50,16 @@ describe('creation', () => {

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]])
expect(grid.hexes()).toEqual([hex])
expect(grid.cursor()).toEqual(hex)
})

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()
expect(grid.hexes()).toEqual([])
expect(grid.cursor()).toBeNull()
})

describe('Grid.from()', () => {
Expand All @@ -89,11 +71,7 @@ describe('creation', () => {

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

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

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

test('accepts an iterable', () => {
Expand All @@ -102,11 +80,7 @@ describe('creation', () => {

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

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

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

test('throws an error when passed an empty store or iterable', () => {
Expand All @@ -120,15 +94,6 @@ test('implements toStringTag', () => {
expect(`${new Grid(hexPrototype)}`).toBe('[object Grid]')
})

test('can be iterated', () => {
const coordinates = { q: 1, r: 2 }
const grid = new Grid(hexPrototype, [at(coordinates)])

for (const hex of grid) {
expect(hex).toMatchObject(coordinates)
}
})

describe('pointToHex()', () => {
test('converts a point to a hex', () => {
const grid = new Grid(hexPrototype)
Expand Down Expand Up @@ -189,6 +154,22 @@ describe('getHex()', () => {
})
})

test('has a hexes() method that returns the hexes from the last iteration', () => {
const grid1 = new Grid(hexPrototype, [at({ q: 1, r: 2 }), at({ q: 3, r: 4 })])
expect(grid1.hexes()).toEqual([createHex(hexPrototype, { q: 1, r: 2 }), createHex(hexPrototype, { q: 3, r: 4 })])

const grid2 = grid1.filter((hex) => hex.q === 1)
expect(grid2.hexes()).toEqual([createHex(hexPrototype, { q: 1, r: 2 })])
})

test('has a cursor() method that returns the last hex from the last iteration', () => {
const grid1 = new Grid(hexPrototype, [at({ q: 1, r: 2 }), at({ q: 3, r: 4 })])
expect(grid1.cursor()).toEqual(createHex(hexPrototype, { q: 3, r: 4 }))

const grid2 = grid1.filter((hex) => hex.q === 1)
expect(grid2.cursor()).toEqual(createHex(hexPrototype, { q: 1, r: 2 }))
})

describe('each()', () => {
test('returns a new grid', () => {
const grid = new Grid(hexPrototype)
Expand Down Expand Up @@ -230,29 +211,26 @@ describe('map()', () => {
test('creates a clone of each hex and passes it to the callback', () => {
const hexPrototype = createHexPrototype<TestHex>()
const mapCallback = jest.fn((hex) => hex.clone({ test: 1 }))
const runCallback = jest.fn()
const hex = createHex(hexPrototype, { q: 1, r: 2 })
const grid = new Grid(hexPrototype, () => [hex]).map(mapCallback)
const runGrid = grid.run(runCallback)
const hexes = grid.hexes()

expect(mapCallback.mock.calls).toEqual([[hex, grid]])
expect(runCallback.mock.calls[0][0]).not.toBe(hex)
expect(runCallback.mock.calls).toEqual([[createHex(hexPrototype, { q: 1, r: 2, test: 1 }), runGrid]])
expect(hexes).toEqual([createHex(hexPrototype, { q: 1, r: 2, test: 1 })])
expect(hexes[0]).not.toBe(hex)
})

test(`the passed callback doesn't have to return a hex`, () => {
const hexPrototype = createHexPrototype<TestHex>()
const mapCallback = jest.fn((hex) => {
hex.test = 2
})
const runCallback = jest.fn()
const hex = createHex(hexPrototype, { q: 1, r: 2 })
const grid = new Grid(hexPrototype, () => [hex]).map(mapCallback)

grid.run(runCallback)
const hexes = grid.hexes()

expect(mapCallback.mock.calls[0][0]).toEqual(createHex(hexPrototype, { q: 1, r: 2, test: 2 })) // hex is mutated
expect(runCallback.mock.calls[0][0]).toEqual(createHex(hexPrototype, { q: 1, r: 2, test: 2 }))
expect(hexes[0]).toEqual(createHex(hexPrototype, { q: 1, r: 2, test: 2 }))
})

test('updates cursor to last hex', () => {
Expand All @@ -261,7 +239,7 @@ describe('map()', () => {
const hex = createHex(hexPrototype, { q: 1, r: 2 })
const grid = new Grid(hexPrototype, () => [hex]).map(callback)

expect(grid['_getPrevHexState'](grid).cursor).toMatchObject({ q: 1, r: 2, test: 3 })
expect(grid.cursor()).toMatchObject({ q: 1, r: 2, test: 3 })
})
})

Expand All @@ -274,23 +252,18 @@ describe('filter()', () => {
})

test('filters hexes', () => {
const callback = jest.fn()
const grid = new Grid(hexPrototype, [at({ q: 1, r: 1 }), at({ q: 2, r: 2 }), at({ q: 3, r: 3 })])
.filter((hex) => hex.q !== 2)
.run(callback)

expect(callback.mock.calls).toEqual([
[createHex(hexPrototype, { q: 1, r: 1 }), grid],
[createHex(hexPrototype, { q: 3, r: 3 }), grid],
])
const grid = new Grid(hexPrototype, [at({ q: 1, r: 1 }), at({ q: 2, r: 2 }), at({ q: 3, r: 3 })]).filter(
(hex) => hex.q !== 2,
)
expect(grid.hexes()).toEqual([createHex(hexPrototype, { q: 1, r: 1 }), createHex(hexPrototype, { q: 3, r: 3 })])
})

test('updates cursor to last hex', () => {
const grid = new Grid(hexPrototype, [at({ q: 1, r: 1 }), at({ q: 2, r: 2 }), at({ q: 3, r: 3 })]).filter(
(hex) => hex.q < 2,
)

expect(grid['_getPrevHexState'](grid).cursor).toMatchObject({ q: 1, r: 1 })
expect(grid.cursor()).toMatchObject({ q: 1, r: 1 })
})
})

Expand All @@ -303,19 +276,17 @@ describe('takeWhile()', () => {
})

test('stops when the passed predicate returns false', () => {
const callback = jest.fn()
const grid = new Grid(hexPrototype, [at({ q: 1, r: 1 }), at({ q: 2, r: 2 }), at({ q: 3, r: 3 })])
.takeWhile((hex) => hex.q !== 2)
.run(callback)

expect(callback.mock.calls).toEqual([[createHex(hexPrototype, { q: 1, r: 1 }), grid]])
const grid = new Grid(hexPrototype, [at({ q: 1, r: 1 }), at({ q: 2, r: 2 }), at({ q: 3, r: 3 })]).takeWhile(
(hex) => hex.q !== 2,
)
expect(grid.hexes()).toEqual([createHex(hexPrototype, { q: 1, r: 1 })])
})

test('updates cursor to last hex', () => {
const grid = new Grid(hexPrototype, [at({ q: 1, r: 1 }), at({ q: 2, r: 2 }), at({ q: 3, r: 3 })]).takeWhile(
(hex) => hex.q !== 2,
)
expect(grid['_getPrevHexState'](grid).cursor).toMatchObject({ q: 1, r: 1 })
expect(grid.cursor()).toMatchObject({ q: 1, r: 1 })
})
})

Expand Down Expand Up @@ -352,15 +323,9 @@ describe('traverse()', () => {
yield createHex(hexPrototype, { q: 1, r: 2 })
yield createHex(hexPrototype, { q: 3, r: 4 })
}
const callback = jest.fn()
const grid = new Grid(hexPrototype)
const grid = new Grid(hexPrototype).traverse(traverser)

grid.traverse(traverser).run(callback)

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

test('continues where a previous traverser stopped', () => {
Expand All @@ -374,16 +339,13 @@ describe('traverse()', () => {
expect(traverser2).toBeCalledWith(createHex(hexPrototype, { q: 3, r: 4 }), grid.getHex)
})

test('gets hexes from a store if the grid has one', () => {
test('passes a getHex() function to the callback', () => {
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, store)
const callback = jest.fn()

grid.traverse(traverser).run(callback)
const grid = new Grid(hexPrototype, store).traverse(traverser)

expect(callback.mock.calls[0][0]).toBe(hexInStore)
expect(grid.hexes()[0]).toBe(hexInStore)
})

test('updates cursor to last hex', () => {
Expand All @@ -392,7 +354,7 @@ describe('traverse()', () => {
yield createHex(hexPrototype, { q: 3, r: 4 })
})

expect(grid['_getPrevHexState'](grid).cursor).toMatchObject({ q: 3, r: 4 })
expect(grid.cursor()).toMatchObject({ q: 3, r: 4 })
})
})

Expand Down
12 changes: 7 additions & 5 deletions src/grid/grid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,12 @@ export class Grid<T extends Hex> {
}
}

*[Symbol.iterator]() {
for (const hex of this._getPrevHexState(this).hexes) {
yield hex
}
hexes() {
return this._getPrevHexState(this).hexes
}

cursor() {
return this._getPrevHexState(this).cursor
}

pointToHex(point: Point): T {
Expand Down Expand Up @@ -128,7 +130,7 @@ export class Grid<T extends Hex> {
}

run(callback?: Callback<T, void>) {
for (const hex of this._getPrevHexState(this).hexes) {
for (const hex of this.hexes()) {
callback && callback(hex, this)
}
return this
Expand Down

0 comments on commit 5738045

Please sign in to comment.