Skip to content

Commit

Permalink
✨ rename functions and add util
Browse files Browse the repository at this point in the history
Rename assertCubeCoordinates() to toCube() and completeCubeCoordinates() to completeCube() and add tests. Also add isNumber() util.
  • Loading branch information
flauwekeul committed Aug 9, 2022
1 parent b1f8cae commit 6fc63cb
Show file tree
Hide file tree
Showing 21 changed files with 109 additions and 66 deletions.
8 changes: 4 additions & 4 deletions docs/guide/coordinate-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@ hex.col = 2 // ❌ TypeError

Most functions/methods that require coordinates accept `HexCoordinates`, which is a [union type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types) of the four coordinate types.

Because `HexCoordinates` can be any of the four types, you may use `assertCubeCoordinates()` to convert `HexCoordinates` to `CubeCoordinates`:
Because `HexCoordinates` can be any of the four types, you may use `toCube()` to convert `HexCoordinates` to `CubeCoordinates`:

```typescript
const hexPrototype = createHexPrototype()

assertCubeCoordinates(hexPrototype, [1, 2]) // { q: 1, r: 2, s: -3 }
assertCubeCoordinates(hexPrototype, { col: 1, row: 2 }) // { q: 0, r: 2, s: -2 }
assertCubeCoordinates(hexPrototype, { q: 3, r: 4 }) // { q: 3, r: 4, s: -7 }
toCube(hexPrototype, [1, 2]) // { q: 1, r: 2, s: -3 }
toCube(hexPrototype, { col: 1, row: 2 }) // { q: 0, r: 2, s: -2 }
toCube(hexPrototype, { q: 3, r: 4 }) // { q: 3, r: 4, s: -7 }
```

## Converting
Expand Down
6 changes: 3 additions & 3 deletions src/grid/functions/distance.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { assertCubeCoordinates, Hex, HexCoordinates } from '../../hex'
import { Hex, HexCoordinates, toCube } from '../../hex'

export function distance(hex: Pick<Hex, 'offset' | 'isPointy'>, from: HexCoordinates, to: HexCoordinates) {
const { q: fromQ, r: fromR, s: fromS } = assertCubeCoordinates(hex, from)
const { q: toQ, r: toR, s: toS } = assertCubeCoordinates(hex, to)
const { q: fromQ, r: fromR, s: fromS } = toCube(hex, from)
const { q: toQ, r: toR, s: toS } = toCube(hex, to)
return Math.max(Math.abs(fromQ - toQ), Math.abs(fromR - toR), Math.abs(fromS - toS))
}
4 changes: 2 additions & 2 deletions src/grid/traversers/line.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CompassDirection } from '../../compass'
import { assertCubeCoordinates, AxialCoordinates, CubeCoordinates, Hex, HexCoordinates, round } from '../../hex'
import { AxialCoordinates, CubeCoordinates, Hex, HexCoordinates, round, toCube } from '../../hex'
import { distance, neighborOf } from '../functions'
import { Traverser } from '../types'

Expand Down Expand Up @@ -61,7 +61,7 @@ function lineFromBetweenOptions<T extends Hex>({ start, stop }: LineBetweenOptio
const hexes: T[] = []
const firstHex = createHex(start ?? cursor)
const nudgedStart = nudge(firstHex)
const nudgedStop = nudge(assertCubeCoordinates(firstHex, stop))
const nudgedStop = nudge(toCube(firstHex, stop))
const interpolate = lerp(nudgedStart, nudgedStop)
const length = distance(firstHex, firstHex, stop)
const step = 1.0 / Math.max(length, 1)
Expand Down
4 changes: 2 additions & 2 deletions src/grid/traversers/rays.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { assertCubeCoordinates, Hex, HexCoordinates } from '../../hex'
import { Hex, HexCoordinates, toCube } from '../../hex'
import { RotationLike, Traverser } from '../types'
import { line } from './line'
import { ring } from './ring'
Expand All @@ -22,7 +22,7 @@ export function rays<T extends Hex>({

return function raysTraverser(createHex, cursor) {
const firstHex = createHex(start ?? cursor)
const { q, r, s } = assertCubeCoordinates(firstHex, firstHex)
const { q, r, s } = toCube(firstHex, firstHex)
const firstStop = (options as RaysToHexOptions).firstStop ?? { q, r: r - length, s: s + length }

return ring({ center: firstHex, start: firstStop, rotation })(createHex, cursor).flatMap((stop) =>
Expand Down
4 changes: 2 additions & 2 deletions src/grid/traversers/rectangle.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Compass, CompassDirection } from '../../compass'
import { completeCubeCoordinates, Hex, HexCoordinates, HexOffset, hexToOffset, OffsetCoordinates } from '../../hex'
import { completeCube, Hex, HexCoordinates, HexOffset, hexToOffset, OffsetCoordinates } from '../../hex'
import { isOffset, isTuple, tupleToCube } from '../../utils'
import { Traverser } from '../types'
import { line } from './line'
Expand Down Expand Up @@ -74,7 +74,7 @@ function optionsFromOpposingCorners(
function assertOffsetCoordinates(coordinates: HexCoordinates, isPointy: boolean, offset: HexOffset): OffsetCoordinates {
if (isOffset(coordinates)) return coordinates

const { q, r } = isTuple(coordinates) ? tupleToCube(coordinates) : completeCubeCoordinates(coordinates)
const { q, r } = isTuple(coordinates) ? tupleToCube(coordinates) : completeCube(coordinates)
return hexToOffset({ q, r, isPointy, offset })
}

Expand Down
7 changes: 4 additions & 3 deletions src/grid/traversers/ring.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { assertCubeCoordinates, Hex, HexCoordinates } from '../../hex'
import { Hex, HexCoordinates, toCube } from '../../hex'
import { isNumber } from '../../utils'
import { distance } from '../functions'
import { Rotation, RotationLike, Traverser } from '../types'

Expand All @@ -16,15 +17,15 @@ export function ring<T extends Hex>(options: RingOptions | RingFromRadiusOptions
let { radius } = options as RingFromRadiusOptions
let firstHex: T

if (Number.isFinite(radius)) {
if (isNumber(radius)) {
firstHex = createHex(center).translate({ q: radius, s: -radius })
} else {
firstHex = createHex((options as RingOptions).start ?? cursor)
radius = distance(firstHex, center, firstHex)
}

// always start at coordinates radius away from the center, reorder the hexes later
const { q, r, s } = assertCubeCoordinates(firstHex, center)
const { q, r, s } = toCube(firstHex, center)
let _cursor = createHex({ q, r: r - radius, s: s + radius })

if (_rotation === Rotation.CLOCKWISE) {
Expand Down
21 changes: 21 additions & 0 deletions src/hex/functions/completeCube.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { expect, test } from 'vitest'
import { completeCube } from './completeCube'

test('returns complete cube coordinates', () => {
expect(completeCube({ q: 1, r: 2, s: -3 })).toEqual({ q: 1, r: 2, s: -3 })
})

test('converts partial cube coordinates to complete cube coordinates', () => {
expect(completeCube({ q: 1, r: 2 })).toEqual({ q: 1, r: 2, s: -3 })
expect(completeCube({ q: 1, s: 2 })).toEqual({ q: 1, r: -3, s: 2 })
expect(completeCube({ r: 1, s: 2 })).toEqual({ q: -3, r: 1, s: 2 })
})

test('throws when passed less than 2 coordinates', () => {
// @ts-expect-error
expect(() => completeCube({ q: 1 })).toThrowError(
TypeError(
`Can't determine three cube coordinates from less than two coordinates. Received: { q: 1, r: undefined, s: undefined }.`,
),
)
})
23 changes: 23 additions & 0 deletions src/hex/functions/completeCube.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { isNumber } from '../../utils'
import { CubeCoordinates, PartialCubeCoordinates } from '../types'

/**
* @category Coordinates
*/
export function completeCube({ q, r, s }: PartialCubeCoordinates): CubeCoordinates {
const validQ = isNumber(q)
const validR = isNumber(r)
const validS = isNumber(s)

if (validQ && validR && validS) return { q, r, s }

if (validQ && validR) return { q, r, s: -q - r }

if (validQ && validS) return { q, r: -q - s, s }

if (validR && validS) return { q: -r - s, r, s }

throw new TypeError(
`Can't determine three cube coordinates from less than two coordinates. Received: { q: ${q}, r: ${r}, s: ${s} }.`,
)
}
26 changes: 0 additions & 26 deletions src/hex/functions/completeCubeCoordinates.ts

This file was deleted.

4 changes: 2 additions & 2 deletions src/hex/functions/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export * from './assertCubeCoordinates'
export * from './completeCubeCoordinates'
export * from './completeCube'
export * from './createHexDimensions'
export * from './createHexOrigin'
export * from './defineHex'
Expand All @@ -10,4 +9,5 @@ export * from './isHexInstance'
export * from './offsetToCube'
export * from './pointToCube'
export * from './round'
export * from './toCube'
export * from './translate'
4 changes: 2 additions & 2 deletions src/hex/functions/round.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { CubeCoordinates, PartialCubeCoordinates } from '../types'
import { completeCubeCoordinates } from './completeCubeCoordinates'
import { completeCube } from './completeCube'

/**
* @category Hex
*/
export const round = (coordinates: PartialCubeCoordinates): CubeCoordinates => {
const { q, r, s } = completeCubeCoordinates(coordinates)
const { q, r, s } = completeCube(coordinates)
let roundedQ = Math.round(q)
let roundedR = Math.round(r)
let roundedS = Math.round(s)
Expand Down
21 changes: 21 additions & 0 deletions src/hex/functions/toCube.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { expect, test } from 'vitest'
import { defineHex } from './defineHex'
import { toCube } from './toCube'

const Hex = defineHex()
const hex = new Hex()

test('converts tuple coordinates to cube coordinates', () => {
expect(toCube(hex, [1, 2])).toEqual({ q: 1, r: 2, s: -3 })
expect(toCube(hex, [0, 2, -2])).toEqual({ q: 0, r: 2, s: -2 })
})

test('converts offset coordinates to cube coordinates', () => {
expect(toCube(hex, { col: 1, row: 2 })).toEqual({ q: 0, r: 2, s: -2 })
})

test('converts partial cube coordinates to cube coordinates', () => {
expect(toCube(hex, { q: 1, r: 2 })).toEqual({ q: 1, r: 2, s: -3 })
expect(toCube(hex, { q: 1, s: 2 })).toEqual({ q: 1, r: -3, s: 2 })
expect(toCube(hex, { r: 1, s: 2 })).toEqual({ q: -3, r: 1, s: 2 })
})
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
import { isOffset, isTuple, tupleToCube } from '../../utils'
import { Hex } from '../hex'
import { CubeCoordinates, HexCoordinates } from '../types'
import { completeCubeCoordinates } from './completeCubeCoordinates'
import { completeCube } from './completeCube'
import { offsetToCube } from './offsetToCube'

/**
* Util for converting offset/axial/cube/tuple coordinates to cube coordinates.
* @category Coordinates
* @privateRemarks It's not placed in /src/utils because that causes circular dependencies.
*/
export function assertCubeCoordinates(
hex: Pick<Hex, 'offset' | 'isPointy'>,
coordinates: HexCoordinates,
): CubeCoordinates {
return isOffset(coordinates)
? offsetToCube(hex, coordinates)
: isTuple(coordinates)
export function toCube(hex: Pick<Hex, 'offset' | 'isPointy'>, coordinates: HexCoordinates): CubeCoordinates {
return isTuple(coordinates)
? tupleToCube(coordinates)
: completeCubeCoordinates(coordinates)
: isOffset(coordinates)
? offsetToCube(hex, coordinates)
: completeCube(coordinates)
}
10 changes: 5 additions & 5 deletions src/hex/functions/translate.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Hex } from '../hex'
import { CubeCoordinates, PartialCubeCoordinates } from '../types'
import { assertCubeCoordinates } from './assertCubeCoordinates'
import { completeCubeCoordinates } from './completeCubeCoordinates'
import { completeCube } from './completeCube'
import { toCube } from './toCube'

/**
* @category Hex
Expand All @@ -12,13 +12,13 @@ export function translate<T extends Hex>(
input: T | PartialCubeCoordinates,
delta: PartialCubeCoordinates,
): T | CubeCoordinates {
const { q: deltaQ, r: deltaR, s: deltaS } = completeCubeCoordinates(delta)
const { q: deltaQ, r: deltaR, s: deltaS } = completeCube(delta)

if (input instanceof Hex) {
const { q, r, s } = assertCubeCoordinates(input, input)
const { q, r, s } = toCube(input, input)
return input.clone({ q: q + deltaQ, r: r + deltaR, s: s + deltaS })
}

const { q, r, s } = completeCubeCoordinates(input)
const { q, r, s } = completeCube(input)
return { q: q + deltaQ, r: r + deltaR, s: s + deltaS }
}
4 changes: 2 additions & 2 deletions src/hex/hex.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint @typescript-eslint/class-literal-property-style: ["error", "getters"] */

import { isOffset } from '../utils'
import { assertCubeCoordinates, equals, hexToOffset, hexToPoint, offsetToCube, translate } from './functions'
import { equals, hexToOffset, hexToPoint, offsetToCube, toCube, translate } from './functions'
import {
BoundingBox,
CubeCoordinates,
Expand Down Expand Up @@ -96,7 +96,7 @@ export class Hex
readonly s: number

constructor(coordinates: HexCoordinates = [0, 0]) {
const { q, r, s } = assertCubeCoordinates(this, coordinates)
const { q, r, s } = toCube(this, coordinates)
this.q = q
this.r = r
this.s = s
Expand Down
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './isAxial'
export * from './isFunction'
export * from './isNumber'
export * from './isObject'
export * from './isOffset'
export * from './isPoint'
Expand Down
3 changes: 2 additions & 1 deletion src/utils/isAxial.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { AxialCoordinates } from '../hex'
import { isNumber } from './isNumber'
import { isObject } from './isObject'

/**
* @category Coordinates
*/
export const isAxial = (value: unknown): value is AxialCoordinates =>
isObject<AxialCoordinates>(value) && Number.isFinite(value.q) && Number.isFinite(value.r)
isObject<AxialCoordinates>(value) && isNumber(value.q) && isNumber(value.r)
1 change: 1 addition & 0 deletions src/utils/isNumber.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const isNumber = (value: unknown): value is number => Number.isFinite(value) && !Number.isNaN(value)
3 changes: 2 additions & 1 deletion src/utils/isOffset.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { OffsetCoordinates } from '../hex'
import { isNumber } from './isNumber'
import { isObject } from './isObject'

/**
* @category Coordinates
*/
export const isOffset = (value: unknown): value is OffsetCoordinates =>
isObject<OffsetCoordinates>(value) && Number.isFinite(value.col) && Number.isFinite(value.row)
isObject<OffsetCoordinates>(value) && isNumber(value.col) && isNumber(value.row)
3 changes: 2 additions & 1 deletion src/utils/isPoint.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Point } from '../hex'
import { isNumber } from './isNumber'
import { isObject } from './isObject'

export const isPoint = (value: unknown): value is Point =>
isObject<Point>(value) && Number.isFinite(value.x) && Number.isFinite(value.y)
isObject<Point>(value) && isNumber(value.x) && isNumber(value.y)
3 changes: 2 additions & 1 deletion src/utils/isTuple.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { TupleCoordinates } from '../hex'
import { isNumber } from './isNumber'

/**
* @category Coordinates
*/
export const isTuple = (value: unknown): value is TupleCoordinates =>
Array.isArray(value) && Number.isFinite(value[0]) && Number.isFinite(value[1])
Array.isArray(value) && isNumber(value[0]) && isNumber(value[1])

0 comments on commit 6fc63cb

Please sign in to comment.