Skip to content

Commit e80a39d

Browse files
committed
Merge branch 'develop'
2 parents e4fed37 + 04d494b commit e80a39d

File tree

9 files changed

+458
-30
lines changed

9 files changed

+458
-30
lines changed

docs/schema.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ Now for the game board itself. This is rendered first, followed by the pieces.
7373
hex-of-cir:: A hex-shaped board composed of circles.
7474
snubsquare:: A basic https://en.wikipedia.org/wiki/Snub_square_tiling[snub square grid].
7575
circular-cobweb:: A circular board with offset cells.
76+
circular-wheel:: A more typical wheel & spoke field with access to vertices and spaces.
7677
sowing:: A generic board for games like Mancala with customizable width and height and optional end pits.
7778
conhex-dots:: A standard ConHex board of flexible size (but square) where the `pieces` property represents the dots. Cells are coloured using the `flood` marker. Cells are indexed as a circle, with the first row being the outermost "ring," starting with the top-left corner and counting clockwise. ConHex boards must be square, at least size 5, and always an odd number.
7879
conhex-cells:: A standard ConHex board of flexible size (but square) where there are no dots. Pieces are placed at the centroid of cells. Cells are not labelled, but pieces are assigned in circular order, from outside in, from top left clockwise. ConHex boards must be square, at least size 5, and always an odd number.

src/grids/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ import { hexSlanted } from "./hexSlanted";
55
import { rectOfRects } from "./rectOfRects";
66
import { snubsquare } from "./snubsquare";
77
import { cobweb } from "./cobweb";
8+
import { wheel } from "./wheel";
89
import { cairo } from "./cairo";
910
import { conicalHex, genPolys as genConicalHexPolys } from "./conicalHex";
1011
import { pyramidHex, genPolys as genPyramidHexPolys } from "./pyramidHex";
1112

1213
import { GridPoints, IPoint, Poly, IPolyCircle, IPolyPath, IPolyPolygon, type SnubStart, type PentagonOrientation } from "./_base";
1314
import { deg2rad } from "../common/plotting";
1415

15-
export {GridPoints, IPoint, hexOfCir, hexOfHex, hexOfTri, hexSlanted, rectOfRects, snubsquare, cobweb, cairo, conicalHex, genConicalHexPolys, pyramidHex, genPyramidHexPolys, Poly, IPolyCircle, IPolyPath, IPolyPolygon, SnubStart, PentagonOrientation};
16+
export {GridPoints, IPoint, hexOfCir, hexOfHex, hexOfTri, hexSlanted, rectOfRects, snubsquare, cobweb, wheel, cairo, conicalHex, genConicalHexPolys, pyramidHex, genPyramidHexPolys, Poly, IPolyCircle, IPolyPath, IPolyPolygon, SnubStart, PentagonOrientation};
1617

1718
export const rotateGrid = (grid: GridPoints, deg: number, cx: number, cy: number): GridPoints => {
1819
const rad = deg2rad(deg);

src/grids/wheel.ts

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
import { projectPoint } from "../common/plotting";
2+
import { GridPoints, IGeneratorArgs, IPoint, IPolyPath, IPolyPolygon } from "./_base";
3+
4+
export interface IWheelArgs extends IGeneratorArgs {
5+
straight?: boolean;
6+
start?: number;
7+
}
8+
9+
/**
10+
* Generates a circular web & spoke field. This function returns the centroids of each space.
11+
* Vertices and spaces are interlaced. The first row is the outermost row of vertices.
12+
* The second row is the outermost row of spaces. The next row is vertices, then spaces, etc.
13+
* The last row is always the singular centre point. And yes, the centre point gets repeated
14+
* in each row of vertices.
15+
*
16+
* @param args - Generator options
17+
* @returns Map of x,y coordinates to row/column locations
18+
*/
19+
export const wheel = (args: IWheelArgs): GridPoints => {
20+
const polys = wheelPolys(args);
21+
const grid: GridPoints = [];
22+
for (let row = 0; row < polys.length; row++) {
23+
const slice = polys[row];
24+
const verts: IPoint[] = [];
25+
const spaces: IPoint[] = [];
26+
for (const poly of slice) {
27+
// The vertex is always the "top left" corner of the poly
28+
verts.push(poly.points[1]);
29+
// If it's an outer space, we need to bias the centroid further outward
30+
// Indices 1 and 2 are the "top" left and right points
31+
// Duplicate them to bias the average outwards
32+
const pts: IPoint[] = [...poly.points];
33+
if (row === 0) {
34+
pts.push(poly.points[1]);
35+
pts.push(poly.points[2]);
36+
pts.push(poly.points[1]);
37+
pts.push(poly.points[2]);
38+
pts.push(poly.points[1]);
39+
pts.push(poly.points[2]);
40+
}
41+
const cx = pts.reduce((prev, curr) => prev += curr.x, 0) / pts.length;
42+
const cy = pts.reduce((prev, curr) => prev += curr.y, 0) / pts.length;
43+
spaces.push({x: cx, y: cy});
44+
}
45+
grid.push(verts);
46+
grid.push(spaces);
47+
}
48+
grid.push([{x: 0, y: 0}]);
49+
50+
return grid;
51+
}
52+
53+
export const wheelPolys = (args: IWheelArgs): (IPolyPolygon|IPolyPath)[][] => {
54+
let cellSize = 50;
55+
if (args.cellSize !== undefined) {
56+
cellSize = args.cellSize;
57+
}
58+
59+
let gridHeight = 4;
60+
let gridWidth = 8;
61+
if (args.gridHeight !== undefined) {
62+
gridHeight = args.gridHeight;
63+
}
64+
if (args.gridWidth !== undefined) {
65+
gridWidth = args.gridWidth;
66+
}
67+
let straight = false;
68+
if (args.straight !== undefined) {
69+
straight = args.straight;
70+
}
71+
let start = 0;
72+
if (args.start !== undefined) {
73+
start = args.start;
74+
}
75+
76+
// First generate a list of intersection points for each line
77+
// Each line has the same distribution of points
78+
const pts: IPoint[][] = [];
79+
const phi = 360 / gridWidth;
80+
for (let i = 0; i < gridWidth; i++) {
81+
const line: IPoint[] = [];
82+
const angle = start + (phi * i);
83+
for (let j = 0; j <= gridHeight; j++) {
84+
const [x,y] = projectPoint(0, 0, cellSize * j, angle);
85+
line.push({x,y})
86+
}
87+
pts.push(line);
88+
}
89+
// wrap the lines around
90+
pts.push(pts[0].map(pt => {return {...pt}}));
91+
92+
// construct polys, section by section, from inside to outside
93+
const polys: (IPolyPolygon|IPolyPath)[][] = [];
94+
for (let slice = 0; slice < pts.length - 1; slice++) {
95+
const left = pts[slice];
96+
const right = pts[slice + 1];
97+
const slicePolys: (IPolyPolygon|IPolyPath)[] = [];
98+
for (let cell = 0; cell < gridHeight; cell++) {
99+
const bottom = cell;
100+
const top = bottom + 1;
101+
102+
const bl = left[bottom];
103+
const tl = left[top];
104+
const tr = right[top];
105+
const br = right[bottom];
106+
// round off the tops
107+
if (cell === gridHeight - 1) {
108+
if (straight) {
109+
slicePolys.push({
110+
type: "path",
111+
points: [bl, tl, tr, br],
112+
path: `M${tl.x},${tl.y} A ${cellSize * top} ${cellSize * top} 0 0 1 ${tr.x},${tr.y} L${br.x},${br.y} L${bl.x},${bl.y} Z`
113+
});
114+
} else {
115+
slicePolys.push({
116+
type: "path",
117+
points: [bl, tl, tr, br],
118+
path: `M${tl.x},${tl.y} A ${cellSize * top} ${cellSize * top} 0 0 1 ${tr.x},${tr.y} L${br.x},${br.y} A ${cellSize * bottom} ${cellSize * bottom} 0 0 0 ${bl.x},${bl.y} Z`
119+
});
120+
}
121+
}
122+
// innermost cells only have three points
123+
else if (cell === 0) {
124+
if (straight) {
125+
slicePolys.push({
126+
type: "poly",
127+
points: [bl, tl, tr],
128+
});
129+
} else {
130+
slicePolys.push({
131+
type: "path",
132+
points: [bl, tl, tr],
133+
path: `M${tl.x},${tl.y} A ${cellSize * top} ${cellSize * top} 0 0 1 ${tr.x},${tr.y} L${bl.x},${bl.y} Z`
134+
});
135+
}
136+
}
137+
// round out the others if `straight` is false
138+
else {
139+
if (straight) {
140+
slicePolys.push({type: "poly", points: [bl, tl, tr, br]});
141+
} else {
142+
slicePolys.push({
143+
type: "path",
144+
points: [bl, tl, tr, br],
145+
path: `M${tl.x},${tl.y} A ${cellSize * top} ${cellSize * top} 0 0 1 ${tr.x},${tr.y} L${br.x},${br.y} A ${cellSize * bottom} ${cellSize * bottom} 0 0 0 ${bl.x},${bl.y} Z`
146+
});
147+
}
148+
}
149+
}
150+
polys.push(slicePolys);
151+
}
152+
// currently col/row, but we need row/col
153+
const rearranged: (IPolyPolygon|IPolyPath)[][] = [];
154+
for (let row = 0; row < gridHeight; row++) {
155+
rearranged.push([...polys.map(col => col[gridHeight - 1 - row])]);
156+
}
157+
// finally, add the centre circle
158+
return rearranged;
159+
}
160+
161+
export const wheelLabels = (args: IWheelArgs): IPoint[] => {
162+
let cellSize = 50;
163+
if (args.cellSize !== undefined) {
164+
cellSize = args.cellSize;
165+
}
166+
167+
let gridHeight = 4;
168+
let gridWidth = 8;
169+
if (args.gridHeight !== undefined) {
170+
gridHeight = args.gridHeight;
171+
}
172+
if (args.gridWidth !== undefined) {
173+
gridWidth = args.gridWidth;
174+
}
175+
// let straight = true;
176+
// if (args.straight !== undefined) {
177+
// straight = args.straight;
178+
// }
179+
let start = 0;
180+
if (args.start !== undefined) {
181+
start = args.start;
182+
}
183+
184+
const innerR = 0;
185+
const webR = gridHeight * cellSize;
186+
const outerR = innerR + webR;
187+
const phi = 360 / gridWidth;
188+
const pts: IPoint[] = [];
189+
for (let i = 0; i < gridWidth; i++) {
190+
const angle = start + (phi * (i + 0.5));
191+
const [x,y] = projectPoint(0, 0, outerR + (cellSize / 2), angle);
192+
pts.push({x,y})
193+
}
194+
return pts;
195+
}

0 commit comments

Comments
 (0)