Skip to content

Commit

Permalink
feat(grid): rectangle() traverser and grid method now accept opposing…
Browse files Browse the repository at this point in the history
… corners as arguments

The existing rectangleFromOpposingCorners() Grid method only worked for top left and bottom right
corners. First I fixed that it could handle any opposing corners (so also top right and bottom left)
in any order and then moved that to the rectangle traverser so that the grid method only needs to
delegate.
  • Loading branch information
flauwekeul committed Apr 22, 2021
1 parent 3e0ca95 commit b8bab92
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 42 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 { createHexPrototype, Grid, Hex } from '../dist'
import { createHexPrototype, Grid, Hex, Orientation } from '../dist'
import { createSuite } from './benchmark'
import { render } from './render'

Expand All @@ -9,15 +9,15 @@ interface CustomHex extends Hex {

const hexPrototype = createHexPrototype<CustomHex>({
dimensions: 30,
// orientation: Orientation.FLAT,
orientation: Orientation.POINTY,
custom: 'custom', // fixme: adding `orientation: 'flat'` makes this an error, adding `orientation: Orientation.FLAT` doesn't
origin: (hexPrototype) => ({ x: hexPrototype.width * -0.5, y: hexPrototype.height * -0.5 }),
})
// const hex = createHex(hexPrototype, { q: 4, r: 3 })

Grid.of(hexPrototype)
.rectangle({ start: { q: 0, r: 3 }, width: 5, height: 5 })
// .rectangleFromOpposingCorners({ q: 0, r: 3 }, { q: 3, r: 6 })
.rectangle({ start: { q: 0, r: 0 }, width: 10, height: 10 })
// .traverse(at({ q: 6, r: 6 }), move(CompassDirection.SE, 8))
.each((hex) => {
hex.svg = render(hex)
// console.log(hex)
Expand All @@ -27,9 +27,9 @@ Grid.of(hexPrototype)
createSuite()
.add('rectangle', function () {
const grid = Grid.of(hexPrototype)
grid.rectangle({ start: { q: 1, r: 2 }, width: 5, height: 5 }).run()
grid.rectangle({ start: { q: 0, r: 0 }, width: 10, height: 10 }).run()
})
.add('rectangleFromOpposingCorners', function () {
const grid = Grid.of(hexPrototype)
grid.rectangleFromOpposingCorners({ q: 1, r: 2 }, { q: 3, r: 6 }).run()
grid.rectangle({ q: 5, r: 9 }, { q: 0, r: 0 }).run()
})
35 changes: 7 additions & 28 deletions src/grid/grid.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CompassDirection } from '../compass'
import { createHex, equals, Hex, HexCoordinates, hexToOffsetFlat, hexToOffsetPointy } from '../hex'
import { createHex, equals, Hex, HexCoordinates } from '../hex'
import { neighborOf } from './functions'
import { rectangle } from './traversers'
import { RectangleOptions, Traverser } from './types'
Expand Down Expand Up @@ -51,34 +51,12 @@ export class Grid<T extends Hex> {
return this
}

// todo: maybe remove this method? What's wrong with just calling grid.traverse(rectangle({ ... }))?
// todo: add in docs: only 90° corners for cardinal directions
rectangle(options: RectangleOptions) {
return this.traverse(rectangle(options))
}

// todo: accept any opposing corner
// todo: have a single rectangle method that either takes {width, height} or {topLeft, bottomRight}?
// todo: maybe add option to determine if row or col is traversed first
rectangleFromOpposingCorners(topLeft: HexCoordinates, bottomRight: HexCoordinates) {
const { isPointy, offset } = this.hexPrototype

if (isPointy) {
const { col: topLeftCol, row: topLeftRow } = hexToOffsetPointy(topLeft.q, topLeft.r, offset)
const { col: bottomRightCol, row: bottomRightRow } = hexToOffsetPointy(bottomRight.q, bottomRight.r, offset)
return this.rectangle({
width: Math.abs(topLeftCol - bottomRightCol) + 1,
height: Math.abs(topLeftRow - bottomRightRow) + 1,
start: topLeft,
})
}

const { col: topLeftCol, row: topLeftRow } = hexToOffsetFlat(topLeft.q, topLeft.r, offset)
const { col: bottomRightCol, row: bottomRightRow } = hexToOffsetFlat(bottomRight.q, bottomRight.r, offset)
return this.rectangle({
width: Math.abs(topLeftCol - bottomRightCol) + 1,
height: Math.abs(topLeftRow - bottomRightRow) + 1,
start: topLeft,
})
rectangle(options: RectangleOptions): Grid<T>
rectangle(cornerA: HexCoordinates, cornerB: HexCoordinates): Grid<T>
rectangle(optionsOrCornerA: RectangleOptions | HexCoordinates, cornerB?: HexCoordinates) {
return this.traverse(rectangle(optionsOrCornerA as HexCoordinates, cornerB as HexCoordinates))
}

traverse(...traversers: Traverser<T>[]) {
Expand Down Expand Up @@ -108,6 +86,7 @@ export class Grid<T extends Hex> {
return this.clone(traverse)
}

// todo: maybe remove this method?
neighborOf(hex: T, direction: CompassDirection) {
return neighborOf(hex, direction)
}
Expand Down
70 changes: 62 additions & 8 deletions src/grid/traversers/rectangle.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,72 @@
import { Compass, CompassDirection } from '../../compass'
import { Hex } from '../../hex'
import { Hex, HexCoordinates, hexToOffset } from '../../hex'
import { RectangleOptions, Traverser } from '../types'
import { at } from './at'
import { branch } from './branch'
import { concat } from './concat'
import { move } from './move'

export const rectangle = <T extends Hex>({
width,
height,
start = { q: 0, r: 0 },
direction = CompassDirection.E,
}: RectangleOptions): Traverser<T> =>
branch(concat(at(start), move(Compass.rotate(direction, 2), height - 1)), move(direction, width - 1))
// todo: add in docs: only 90° corners for cardinal directions
// todo: when passed opposing corners: maybe add option to determine if row or col is traversed first
export function rectangle<T extends Hex>(options: RectangleOptions): Traverser<T>
export function rectangle<T extends Hex>(cornerA: HexCoordinates, cornerB: HexCoordinates): Traverser<T>
export function rectangle<T extends Hex>(
optionsOrCornerA: RectangleOptions | HexCoordinates,
cornerB?: HexCoordinates,
): Traverser<T> {
return (cursor) => {
const { width, height, start = { q: 0, r: 0 }, direction = CompassDirection.E } = cornerB
? optionsFromOpposingCorners(optionsOrCornerA as HexCoordinates, cornerB, cursor.isPointy, cursor.offset)
: (optionsOrCornerA as RectangleOptions)

return branch<T>(
concat(at(start), move(Compass.rotate(direction, 2), height - 1)),
move(direction, width - 1),
)(cursor)
}
}

function optionsFromOpposingCorners(
cornerA: HexCoordinates,
cornerB: HexCoordinates,
isPointy: boolean,
offset: number,
): RectangleOptions {
const { col: cornerACol, row: cornerARow } = hexToOffset({ q: cornerA.q, r: cornerA.r, isPointy, offset })
const { col: cornerBCol, row: cornerBRow } = hexToOffset({ q: cornerB.q, r: cornerB.r, isPointy, offset })
const smallestCol = cornerACol < cornerBCol ? 'A' : 'B'
const smallestRow = cornerARow < cornerBRow ? 'A' : 'B'
const smallestColRow = (smallestCol + smallestRow) as keyof typeof RULES_FOR_SMALLEST_COL_ROW
const { swapWidthHeight, direction } = RULES_FOR_SMALLEST_COL_ROW[smallestColRow]
const width = Math.abs(cornerACol - cornerBCol) + 1
const height = Math.abs(cornerARow - cornerBRow) + 1

return {
width: swapWidthHeight ? height : width,
height: swapWidthHeight ? width : height,
start: cornerA,
direction,
}
}

const RULES_FOR_SMALLEST_COL_ROW = {
AA: {
swapWidthHeight: false,
direction: CompassDirection.E,
},
AB: {
swapWidthHeight: true,
direction: CompassDirection.N,
},
BA: {
swapWidthHeight: true,
direction: CompassDirection.S,
},
BB: {
swapWidthHeight: false,
direction: CompassDirection.W,
},
}

/**
* This is the "old way" of creating rectangles. It's less performant (up until ~40x slower with 200x200 rectangles), but it's able to create
Expand Down

0 comments on commit b8bab92

Please sign in to comment.