Skip to content

Commit

Permalink
feat(grid): add spiral() traverser
Browse files Browse the repository at this point in the history
Like most traversers it accepts an `at` and `start` option and a `radius` and `rotation` options. It
always starts at the cursor (or `at`/`start`) and spirals outward until the required radius is
reached (in Northern direction of the first hex).
  • Loading branch information
flauwekeul committed Apr 22, 2021
1 parent f5a2d6f commit d433af0
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 6 deletions.
1 change: 1 addition & 0 deletions src/grid/traversers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './branch'
export * from './line'
export * from './rectangle'
export * from './ring'
export * from './spiral'
7 changes: 1 addition & 6 deletions src/grid/traversers/ring.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Hex, HexCoordinates } from '../../hex'
import { assertCubeCoordinates } from '../../utils'
import { distance } from '../functions'
import { Traverser } from '../types'
import { Rotation, Traverser } from '../types'

export const ring = <T extends Hex>({ start, at, center, rotation }: RingOptions): Traverser<T, T[]> => (
cursor,
Expand Down Expand Up @@ -38,11 +38,6 @@ export const ring = <T extends Hex>({ start, at, center, rotation }: RingOptions
return hexes.slice(startIndex + (start ? 0 : 1)).concat(hexes.slice(0, startIndex))
}

export enum Rotation {
CLOCKWISE = 'CLOCKWISE',
COUNTERCLOCKWISE = 'COUNTERCLOCKWISE',
}

export interface RingOptions {
center: HexCoordinates
start?: HexCoordinates
Expand Down
107 changes: 107 additions & 0 deletions src/grid/traversers/spiral.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { createHex, createHexPrototype } from '../../hex'
import { spiral } from './spiral'

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

describe('when called with a radius', () => {
test('returns a traverser that returns hexes in a spiral around but excluding the cursor', () => {
expect(spiral({ radius: 2 })(cursor, getHex)).toEqual([
cursor.clone({ q: 2, r: 1 }),
cursor.clone({ q: 2, r: 2 }),
cursor.clone({ q: 1, r: 3 }),
cursor.clone({ q: 0, r: 3 }),
cursor.clone({ q: 0, r: 2 }),
cursor.clone({ q: 1, r: 1 }),
cursor.clone({ q: 2, r: 0 }),
cursor.clone({ q: 3, r: 0 }),
cursor.clone({ q: 3, r: 1 }),
cursor.clone({ q: 3, r: 2 }),
cursor.clone({ q: 2, r: 3 }),
cursor.clone({ q: 1, r: 4 }),
cursor.clone({ q: 0, r: 4 }),
cursor.clone({ q: -1, r: 4 }),
cursor.clone({ q: -1, r: 3 }),
cursor.clone({ q: -1, r: 2 }),
cursor.clone({ q: 0, r: 1 }),
cursor.clone({ q: 1, r: 0 }),
])
})
})

describe('when called with at coordinates', () => {
test('returns a traverser that returns hexes in a spiral around but excluding the "at" coordinates', () => {
expect(spiral({ at: { q: 1, r: 3 }, radius: 2 })(cursor, getHex)).toEqual([
cursor.clone({ q: 1, r: 2 }),
cursor.clone({ q: 2, r: 2 }),
cursor.clone({ q: 2, r: 3 }),
cursor.clone({ q: 1, r: 4 }),
cursor.clone({ q: 0, r: 4 }),
cursor.clone({ q: 0, r: 3 }),
cursor.clone({ q: 2, r: 1 }),
cursor.clone({ q: 3, r: 1 }),
cursor.clone({ q: 3, r: 2 }),
cursor.clone({ q: 3, r: 3 }),
cursor.clone({ q: 2, r: 4 }),
cursor.clone({ q: 1, r: 5 }),
cursor.clone({ q: 0, r: 5 }),
cursor.clone({ q: -1, r: 5 }),
cursor.clone({ q: -1, r: 4 }),
cursor.clone({ q: -1, r: 3 }),
cursor.clone({ q: 0, r: 2 }),
cursor.clone({ q: 1, r: 1 }),
])
})
})

describe('when called with start coordinates', () => {
test('returns a traverser that returns hexes in a spiral around and including the "start" coordinates', () => {
expect(spiral({ start: { q: 2, r: 1 }, radius: 2 })(cursor, getHex)).toEqual([
cursor.clone({ q: 2, r: 1 }),
cursor.clone({ q: 2, r: 0 }),
cursor.clone({ q: 3, r: 0 }),
cursor.clone({ q: 3, r: 1 }),
cursor.clone({ q: 2, r: 2 }),
cursor.clone({ q: 1, r: 2 }),
cursor.clone({ q: 1, r: 1 }),
cursor.clone({ q: 3, r: -1 }),
cursor.clone({ q: 4, r: -1 }),
cursor.clone({ q: 4, r: 0 }),
cursor.clone({ q: 4, r: 1 }),
cursor.clone({ q: 3, r: 2 }),
cursor.clone({ q: 2, r: 3 }),
cursor.clone({ q: 1, r: 3 }),
cursor.clone({ q: 0, r: 3 }),
cursor.clone({ q: 0, r: 2 }),
cursor.clone({ q: 0, r: 1 }),
cursor.clone({ q: 1, r: 0 }),
cursor.clone({ q: 2, r: -1 }),
])
})
})

describe('when called with a counterclockwise rotation', () => {
test('returns a traverser that returns hexes in a spiral around the cursor in a counterclockwise rotation', () => {
expect(spiral({ radius: 2, rotation: 'counterclockwise' })(cursor, getHex)).toEqual([
cursor.clone({ q: 2, r: 1 }),
cursor.clone({ q: 1, r: 1 }),
cursor.clone({ q: 0, r: 2 }),
cursor.clone({ q: 0, r: 3 }),
cursor.clone({ q: 1, r: 3 }),
cursor.clone({ q: 2, r: 2 }),
cursor.clone({ q: 2, r: 0 }),
cursor.clone({ q: 1, r: 0 }),
cursor.clone({ q: 0, r: 1 }),
cursor.clone({ q: -1, r: 2 }),
cursor.clone({ q: -1, r: 3 }),
cursor.clone({ q: -1, r: 4 }),
cursor.clone({ q: 0, r: 4 }),
cursor.clone({ q: 1, r: 4 }),
cursor.clone({ q: 2, r: 3 }),
cursor.clone({ q: 3, r: 2 }),
cursor.clone({ q: 3, r: 1 }),
cursor.clone({ q: 3, r: 0 }),
])
})
})
24 changes: 24 additions & 0 deletions src/grid/traversers/spiral.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { CompassDirection } from '../../compass'
import { Hex, HexCoordinates } from '../../hex'
import { Rotation, Traverser } from '../types'
import { branch } from './branch'
import { line } from './line'
import { ring } from './ring'

export const spiral = <T extends Hex>({ radius, start, at, rotation }: SpiralOptions): Traverser<T, T[]> => (
cursor,
getHex,
) => {
const center = start ? getHex(start) : at ? getHex(at) : cursor
return branch<T>(line({ start, at, direction: CompassDirection.N, length: radius }), ring({ center, rotation }))(
getHex(center),
getHex,
)
}

export interface SpiralOptions {
radius: number
start?: HexCoordinates
at?: HexCoordinates
rotation?: Rotation | 'CLOCKWISE' | 'clockwise' | 'COUNTERCLOCKWISE' | 'counterclockwise'
}
5 changes: 5 additions & 0 deletions src/grid/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@ export interface Traverser<T extends Hex, R extends Iterable<T> = Iterable<T>> {
export interface Callback<T extends Hex, R> {
(hex: T, grid: Grid<T>): R
}

export enum Rotation {
CLOCKWISE = 'CLOCKWISE',
COUNTERCLOCKWISE = 'COUNTERCLOCKWISE',
}

0 comments on commit d433af0

Please sign in to comment.