Skip to content

Commit 2efdbc1

Browse files
Korlanicolodavis
authored andcommitted
utils for working with hexagonal boards (#271)
* getAllNeighbors implemented and tested * hexUtils exported in packages * getDistance implemented and tested * getRange implemented and tested * Storybook example of GetRange * Resolve getRange import correctly * fix some naming conventions
1 parent 137dd7c commit 2efdbc1

4 files changed

Lines changed: 198 additions & 2 deletions

File tree

packages/ui.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { Card } from '../src/ui/card.js';
1010
import { Deck } from '../src/ui/deck.js';
1111
import { Grid } from '../src/ui/grid.js';
1212
import { HexGrid } from '../src/ui/hex.js';
13+
import { HexUtils } from '../src/ui/hex-utils.js';
1314
import Token from '../src/ui/token.js';
1415

15-
export { Card, Deck, Grid, HexGrid, Token };
16+
export { Card, Deck, Grid, HexGrid, Token, HexUtils };

src/ui/hex-utils.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2018 The boardgame.io Authors
3+
*
4+
* Use of this source code is governed by a MIT-style
5+
* license that can be found in the LICENSE file or at
6+
* https://opensource.org/licenses/MIT.
7+
*/
8+
9+
const addPoint = (a, b) => ({ x: a.x + b.x, y: a.y + b.y, z: a.z + b.z });
10+
11+
/**
12+
* Get neighbors
13+
*
14+
* A utility function which returns all neighbors for a point
15+
* expressed in cube coordinates
16+
*
17+
* Arguments:
18+
* point (Cube coorinates)
19+
*
20+
*/
21+
export const getAllNeighbors = point =>
22+
[[1, -1, 0], [1, 0, -1], [0, 1, -1], [0, -1, 1], [-1, 1, 0], [-1, 0, 1]].map(
23+
([dx, dy, dz]) => addPoint(point, { x: dx, y: dy, z: dz })
24+
);
25+
26+
/**
27+
* Get distance
28+
*
29+
* A utility function which calculates the distance between two
30+
* points expressed in cube coordinates
31+
*
32+
* Arguments:
33+
* Two objects with:
34+
* x - X coordinate (cube coordinates)
35+
* y - Y coordinate (cube coordinates)
36+
* z - Z coordinate (cube coordinates)
37+
*
38+
*/
39+
export const getDistance = (a, b) =>
40+
(Math.abs(a.x - b.x) + Math.abs(a.y - b.y) + Math.abs(a.z - b.z)) / 2;
41+
42+
/**
43+
* Get range
44+
*
45+
* A utility function which returns all points within a range
46+
* from the center
47+
*
48+
* Arguments:
49+
* center (Cube coordinates)
50+
* distance number
51+
*
52+
*/
53+
export const getRange = (center, distance) => {
54+
const results = [];
55+
for (let x = -distance; x <= distance; x++) {
56+
const startY = Math.max(-distance, -x - distance);
57+
const stopY = Math.min(distance, -x + distance);
58+
for (let y = startY; y <= stopY; y++) {
59+
const z = -x - y;
60+
results.push(addPoint(center, { x, y, z }));
61+
}
62+
}
63+
return results;
64+
};
65+
66+
export const HexUtils = { getAllNeighbors, getDistance, getRange };

src/ui/hex-utils.test.js

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright 2018 The boardgame.io Authors
3+
*
4+
* Use of this source code is governed by a MIT-style
5+
* license that can be found in the LICENSE file or at
6+
* https://opensource.org/licenses/MIT.
7+
*/
8+
9+
import { getAllNeighbors, getDistance, getRange } from './hex-utils';
10+
11+
const createCoordinate = ([x, y, z]) => ({ x, y, z });
12+
13+
test('neighbors of origo', () => {
14+
const coordinate = createCoordinate([0, 0, 0]);
15+
const result = getAllNeighbors(coordinate);
16+
const expectedNeighbors = [
17+
[1, -1, 0],
18+
[1, 0, -1],
19+
[0, 1, -1],
20+
[0, -1, 1],
21+
[-1, 1, 0],
22+
[-1, 0, 1],
23+
].map(createCoordinate);
24+
expect(result).toEqual(expectedNeighbors);
25+
});
26+
27+
test('neighbors of (1, 0, -1)', () => {
28+
const coordinate = createCoordinate([1, 0, -1]);
29+
const result = getAllNeighbors(coordinate);
30+
const expectedNeighbors = [
31+
[2, -1, -1],
32+
[2, 0, -2],
33+
[1, 1, -2],
34+
[1, -1, 0],
35+
[0, 1, -1],
36+
[0, 0, 0],
37+
].map(createCoordinate);
38+
expect(result).toEqual(expectedNeighbors);
39+
});
40+
41+
test('distance between neighbors', () => {
42+
const origo = createCoordinate([0, 0, 0]);
43+
const neighbor = createCoordinate([1, 0, -1]);
44+
const result = getDistance(origo, neighbor);
45+
expect(result).toEqual(1);
46+
});
47+
48+
test('distance between non-neighbors', () => {
49+
const origo = createCoordinate([0, 0, 0]);
50+
const nonNeighbor = createCoordinate([3, 3, -6]);
51+
const result = getDistance(origo, nonNeighbor);
52+
expect(result).toEqual(6);
53+
});
54+
55+
test('range from origo', () => {
56+
const origo = createCoordinate([0, 0, 0]);
57+
const result = getRange(origo, 1);
58+
expect(result).toEqual(
59+
[
60+
[-1, 0, 1],
61+
[-1, 1, 0],
62+
[0, -1, 1],
63+
[0, 0, 0],
64+
[0, 1, -1],
65+
[1, -1, 0],
66+
[1, 0, -1],
67+
].map(createCoordinate)
68+
);
69+
});
70+
71+
test('range not from origo', () => {
72+
const origo = createCoordinate([2, -3, 1]);
73+
const result = getRange(origo, 2);
74+
expect(result).toEqual(
75+
[
76+
[0, -3, 3],
77+
[0, -2, 2],
78+
[0, -1, 1],
79+
[1, -4, 3],
80+
[1, -3, 2],
81+
[1, -2, 1],
82+
[1, -1, 0],
83+
[2, -5, 3],
84+
[2, -4, 2],
85+
[2, -3, 1],
86+
[2, -2, 0],
87+
[2, -1, -1],
88+
[3, -5, 2],
89+
[3, -4, 1],
90+
[3, -3, 0],
91+
[3, -2, -1],
92+
[4, -5, 1],
93+
[4, -4, 0],
94+
[4, -3, -1],
95+
].map(createCoordinate)
96+
);
97+
});

storybook/hex.js

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import React from 'react';
1010
import { storiesOf } from '@storybook/react';
1111
import { action } from '@storybook/addon-actions';
1212
import { withKnobs, boolean, number } from '@storybook/addon-knobs/react';
13-
import { HexGrid, Token } from 'boardgame.io/ui';
13+
import { HexGrid, Token, HexUtils } from 'boardgame.io/ui';
1414

1515
function Basic() {
1616
const levels = number('levels', 5);
@@ -43,6 +43,37 @@ function Basic() {
4343
);
4444
}
4545

46+
function GetRange() {
47+
const levels = number('levels', 5);
48+
const distance = number('distance', 2);
49+
const outline = boolean('outline', true);
50+
51+
class Runner extends React.Component {
52+
state = { tokens: HexUtils.getRange({ x: 0, y: 0, z: 0 }, distance) };
53+
render = () => (
54+
<HexGrid levels={levels} outline={outline}>
55+
{this.state.tokens.map((t, index) => {
56+
return (
57+
<Token
58+
key={index}
59+
x={t.x}
60+
y={t.y}
61+
z={t.z}
62+
style={{ fill: '#555' }}
63+
/>
64+
);
65+
})}
66+
</HexGrid>
67+
);
68+
}
69+
70+
return (
71+
<div style={{ padding: '50px' }}>
72+
<Runner />
73+
</div>
74+
);
75+
}
76+
4677
function TokenTrail() {
4778
const levels = number('levels', 5);
4879
const outline = boolean('outline', true);
@@ -86,4 +117,5 @@ function TokenTrail() {
86117
storiesOf('HexGrid', module)
87118
.addDecorator(withKnobs)
88119
.add('basic', Basic)
120+
.add('Get range', GetRange)
89121
.add('Tokens placed on hover', TokenTrail);

0 commit comments

Comments
 (0)