Skip to content

Commit

Permalink
feat(grid): add rays() traverser
Browse files Browse the repository at this point in the history
This first version is very basic and only accepts at/start, a length for the rays, a rotation and an
updateRay callback that's called with each ray and is meant to remove hexes from the ray to create a
field of view.
  • Loading branch information
flauwekeul committed May 2, 2021
1 parent 2fe12ff commit 50e707d
Show file tree
Hide file tree
Showing 3 changed files with 213 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/grid/traversers/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './add'
export * from './branch'
export * from './line'
export * from './rays'
export * from './rectangle'
export * from './ring'
export * from './spiral'
176 changes: 176 additions & 0 deletions src/grid/traversers/rays.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import { createHex, createHexPrototype, Hex } from '../../hex'
import { rays } from './rays'

const hexPrototype = createHexPrototype()
const cursor = createHex(hexPrototype, { q: 1, r: 2 })
const getHex = jest.fn((coordinates) => createHex(hexPrototype, coordinates))

describe('when called with only length', () => {
test('returns a traverser that returns all (unique) hexes around the cursor with radius length', () => {
const result = [...rays({ length: 2 })(cursor, getHex)]
expect(result).toMatchObject([
{ q: 1, r: 1 },
{ q: 1, r: 0 },
{ q: 2, r: 1 },
{ q: 2, r: 0 },
{ q: 3, r: 0 },
{ q: 2, r: 2 },
{ q: 3, r: 1 },
{ q: 3, r: 2 },
{ q: 2, r: 3 },
{ q: 1, r: 3 },
{ q: 1, r: 4 },
{ q: 0, r: 4 },
{ q: 0, r: 3 },
{ q: -1, r: 4 },
{ q: -1, r: 3 },
{ q: 0, r: 2 },
{ q: -1, r: 2 },
{ q: 0, r: 1 },
])
})
})

describe('when called with start and length', () => {
test('returns a traverser that returns all (unique) hexes around and including the start with radius length', () => {
const result = [...rays({ start: [3, 2], length: 2 })(cursor, getHex)]
expect(result).toMatchObject([
{ q: 3, r: 2 },
{ q: 3, r: 1 },
{ q: 3, r: 0 },
{ q: 4, r: 1 },
{ q: 4, r: 0 },
{ q: 5, r: 0 },
{ q: 4, r: 2 },
{ q: 5, r: 1 },
{ q: 5, r: 2 },
{ q: 4, r: 3 },
{ q: 3, r: 3 },
{ q: 3, r: 4 },
{ q: 2, r: 4 },
{ q: 2, r: 3 },
{ q: 1, r: 4 },
{ q: 1, r: 3 },
{ q: 2, r: 2 },
{ q: 1, r: 2 },
{ q: 2, r: 1 },
])
})
})

describe('when called with at and length', () => {
test('returns a traverser that returns all (unique) hexes around the at with radius length', () => {
const result = [...rays({ at: [1, 4], length: 2 })(cursor, getHex)]
expect(result).toMatchObject([
{ q: 1, r: 3 },
{ q: 1, r: 2 },
{ q: 2, r: 3 },
{ q: 2, r: 2 },
{ q: 3, r: 2 },
{ q: 2, r: 4 },
{ q: 3, r: 3 },
{ q: 3, r: 4 },
{ q: 2, r: 5 },
{ q: 1, r: 5 },
{ q: 1, r: 6 },
{ q: 0, r: 6 },
{ q: 0, r: 5 },
{ q: -1, r: 6 },
{ q: -1, r: 5 },
{ q: 0, r: 4 },
{ q: -1, r: 4 },
{ q: 0, r: 3 },
])
})
})

describe('updateRay callback', () => {
test('is called with each ray', () => {
const updateRay = jest.fn<Hex[], [Hex[]]>((ray) => ray.filter((hex) => hex.q < 1))
const result = [...rays({ length: 2, updateRay })(cursor, getHex)]

expect(updateRay.mock.calls).toMatchObject([
[
[
{ q: 1, r: 1 },
{ q: 1, r: 0 },
],
],
[
[
{ q: 2, r: 1 },
{ q: 2, r: 0 },
],
],
[
[
{ q: 2, r: 1 },
{ q: 3, r: 0 },
],
],
[
[
{ q: 2, r: 2 },
{ q: 3, r: 1 },
],
],
[
[
{ q: 2, r: 2 },
{ q: 3, r: 2 },
],
],
[
[
{ q: 2, r: 2 },
{ q: 2, r: 3 },
],
],
[
[
{ q: 1, r: 3 },
{ q: 1, r: 4 },
],
],
[
[
{ q: 1, r: 3 },
{ q: 0, r: 4 },
],
],
[
[
{ q: 0, r: 3 },
{ q: -1, r: 4 },
],
],
[
[
{ q: 0, r: 3 },
{ q: -1, r: 3 },
],
],
[
[
{ q: 0, r: 2 },
{ q: -1, r: 2 },
],
],
[
[
{ q: 0, r: 2 },
{ q: 0, r: 1 },
],
],
])
expect(result).toMatchObject([
{ q: 0, r: 4 },
{ q: 0, r: 3 },
{ q: -1, r: 4 },
{ q: -1, r: 3 },
{ q: 0, r: 2 },
{ q: -1, r: 2 },
{ q: 0, r: 1 },
])
})
})
36 changes: 36 additions & 0 deletions src/grid/traversers/rays.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { assertCubeCoordinates, CubeCoordinates, Hex } from '../../hex'
import { RotationLike, StartOrAt, Traverser } from '../types'
import { line, LineBetweenOptions } from './line'
import { ring } from './ring'

// todo:
// - add option for first ray ()
export const rays = <T extends Hex>({
at,
start,
length,
rotation,
updateRay = (_) => _,
}: RaysOptions<T>): Traverser<T> => {
return (cursor, getHex) => {
const firstCoordinates = at ?? start ?? cursor
const { q, r, s } = assertCubeCoordinates(firstCoordinates, cursor)
// todo: make this configurable: either a direction or end of line?
const ringStart: CubeCoordinates = { q, r: r - length, s: s + length }

return ring<T>({ center: firstCoordinates, start: ringStart, rotation })(cursor, getHex)
.reduce((uniqueHexes, through) => {
const ray = line<T>({ at, start, through } as LineBetweenOptions)(cursor, getHex)
updateRay(ray).forEach((hex) => uniqueHexes.set(hex.toString(), hex))
return uniqueHexes
}, new Map<string, T>())
.values()
}
}

export type RaysOptions<T extends Hex> = StartOrAt & {
length: number
// todo: add arc option
rotation?: RotationLike
updateRay?: (hexesInRay: T[]) => T[]
}

0 comments on commit 50e707d

Please sign in to comment.