Skip to content

Commit ba761fc

Browse files
committed
StarGraph added
1 parent ed22e42 commit ba761fc

11 files changed

Lines changed: 608 additions & 2 deletions

File tree

src/common/graphs/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { SquareDiamondsDirectedGraph } from "./square-diamonds-directed";
2222
import { PentaHexGraph } from "./penta-hex";
2323
export { RectTriGraph } from "./rect-tri";
2424
export { BentTriGraph, type BentTriNodeData } from "./bent-tri";
25+
export { StarGraph, type StarNodeData, starFrequencyFromWidth, STAR_DEFAULT_FREQUENCY } from "./star";
2526

2627
export { IGraph, IGraph3D, Square3DGraph, SquareGraph, SquareDirectedGraph,
2728
SquareOrth3DGraph, SquareOrthGraph, SquareOrthDirectedGraph, SquareDiag3DGraph,

src/common/graphs/star.ts

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { UndirectedGraph } from "graphology";
2+
import { bidirectional } from "graphology-shortest-path/unweighted";
3+
import { starBoard, Graph as StarTopology, starFrequencyFromWidth, STAR_DEFAULT_FREQUENCY } from "../star";
4+
import { IGraph } from "./IGraph";
5+
6+
const columnLabels = "abcdefghijklmnopqrstuvwxyz".split("");
7+
8+
const ringLetter = (ring: number): string => {
9+
const letter = columnLabels[ring];
10+
if (letter === undefined) {
11+
throw new Error(`Ring index out of range: ${ring}`);
12+
}
13+
return letter;
14+
};
15+
16+
const parseAlgebraic = (cell: string): [number, number] => {
17+
const match = cell.match(/^([a-z]+)(\d+)$/);
18+
if (match === null) {
19+
throw new Error(`Invalid algebraic notation: ${cell}`);
20+
}
21+
const ring = columnLabels.indexOf(match[1]);
22+
if (ring < 0) {
23+
throw new Error(`Invalid ring label: ${match[1]}`);
24+
}
25+
const pos = parseInt(match[2], 10) - 1;
26+
if (isNaN(pos) || pos < 0) {
27+
throw new Error(`Invalid position: ${match[2]}`);
28+
}
29+
return [ring, pos];
30+
};
31+
32+
export type StarNodeData = {
33+
id: number;
34+
ring: number;
35+
pos: number;
36+
isOuter: boolean;
37+
isBridge: boolean;
38+
isQuark: boolean;
39+
isPericell: boolean;
40+
};
41+
42+
export { starFrequencyFromWidth, STAR_DEFAULT_FREQUENCY };
43+
44+
export class StarGraph implements IGraph {
45+
public readonly frequency: number;
46+
public readonly topo: StarTopology;
47+
public graph: UndirectedGraph;
48+
private readonly vidToLabel = new Map<number, string>();
49+
50+
constructor(frequency: number) {
51+
this.frequency = frequency;
52+
this.topo = starBoard(frequency);
53+
this.buildLabelMaps();
54+
this.graph = this.buildGraph();
55+
}
56+
57+
/** Rings outside-in (a, b, c, …); position 1-based clockwise from the top quark. */
58+
private buildLabelMaps(): void {
59+
for (let ring = 0; ring < this.topo.gridLayers.length; ring++) {
60+
const layer = this.topo.gridLayers[ring];
61+
const letter = ringLetter(ring);
62+
for (let pos = 0; pos < layer.length; pos++) {
63+
const label = letter + (pos + 1).toString();
64+
const vid = layer[pos].id;
65+
this.vidToLabel.set(vid, label);
66+
}
67+
}
68+
}
69+
70+
private buildGraph(): UndirectedGraph {
71+
const g = new UndirectedGraph();
72+
for (const vertex of this.topo.vertices) {
73+
const nodeId = this.vidToLabel.get(vertex.id);
74+
if (nodeId === undefined) {
75+
throw new Error(`Missing algebraic label for vertex ${vertex.id}`);
76+
}
77+
const [ring, pos] = parseAlgebraic(nodeId);
78+
g.addNode(nodeId, {
79+
id: vertex.id,
80+
ring,
81+
pos,
82+
isOuter: vertex.isOuter,
83+
isBridge: this.topo.bridgeIds.has(vertex.id),
84+
isQuark: this.topo.quarkIds.has(vertex.id),
85+
isPericell: this.topo.pericellIds.has(vertex.id),
86+
} as StarNodeData);
87+
}
88+
for (const edge of this.topo.edges) {
89+
const a = this.vidToLabel.get(edge.vidA);
90+
const b = this.vidToLabel.get(edge.vidB);
91+
if (a === undefined || b === undefined) {
92+
throw new Error(`Could not map edge endpoints ${edge.vidA} or ${edge.vidB} to labels.`);
93+
}
94+
g.addUndirectedEdgeWithKey(`${a}>${b}`, a, b);
95+
}
96+
return g;
97+
}
98+
99+
/** x = ring index (0 = outer), y = clockwise position index within the ring. */
100+
public coords2algebraic(x: number, y: number): string {
101+
return ringLetter(x) + (y + 1).toString();
102+
}
103+
104+
public algebraic2coords(cell: string): [number, number] {
105+
return parseAlgebraic(cell);
106+
}
107+
108+
public listCells(ordered = false): string[] | string[][] {
109+
if (!ordered) {
110+
return this.graph.nodes();
111+
}
112+
return this.topo.gridLayers.map((layer, ring) =>
113+
layer.map((_, pos) => this.coords2algebraic(ring, pos)),
114+
);
115+
}
116+
117+
public neighbours(node: string): string[] {
118+
return this.graph.neighbors(node);
119+
}
120+
121+
public path(from: string, to: string): string[] | null {
122+
return bidirectional(this.graph, from, to);
123+
}
124+
}

src/common/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import { StackSet} from "./stackset";
44
import { reviver, replacer, sortingReplacer } from "./serialization";
55
import { shuffle } from "./shuffle";
66
import { UserFacingError } from "./errors";
7-
import { HexTriGraph, SnubSquareGraph, SquareOrthGraph, SquareDiagGraph, SquareGraph, Square3DGraph, SquareDirectedGraph, SquareFanoronaGraph, BaoGraph, SowingNoEndsGraph, RectTriGraph, BentTriGraph } from "./graphs";
7+
import { HexTriGraph, SnubSquareGraph, SquareOrthGraph, SquareDiagGraph, SquareGraph, Square3DGraph, SquareDirectedGraph, SquareFanoronaGraph, BaoGraph, SowingNoEndsGraph, RectTriGraph, BentTriGraph, StarGraph, starFrequencyFromWidth } from "./graphs";
88
import { wng } from "./namegenerator";
99
import { projectPoint, ptDistance, smallestDegreeDiff, normDeg, deg2rad, rad2deg, toggleFacing, calcBearing, matrixRectRot90, matrixRectRotN90, transposeRect, circle2poly, midpoint, distFromCircle, deg2dir, dir2deg, rotateFacing } from "./plotting";
1010
import { hexhexAi2Ap, hexhexAp2Ai, triAi2Ap, triAp2Ai } from "./aiai";
1111
import stringify from "json-stringify-deterministic";
1212
import fnv from "fnv-plus";
1313

14-
export { RectGrid, StackSet, reviver, replacer, sortingReplacer, shuffle, UserFacingError, HexTriGraph, SnubSquareGraph, SquareOrthGraph, SquareDiagGraph, SquareGraph, Square3DGraph, SquareDirectedGraph, SquareFanoronaGraph, BaoGraph, SowingNoEndsGraph, RectTriGraph, BentTriGraph, wng, projectPoint, ptDistance, smallestDegreeDiff, normDeg, deg2rad, rad2deg, toggleFacing, calcBearing, matrixRectRot90, matrixRectRotN90, transposeRect, hexhexAi2Ap, hexhexAp2Ai, triAi2Ap, triAp2Ai, circle2poly, midpoint, distFromCircle, dir2deg, deg2dir, rotateFacing };
14+
export { RectGrid, StackSet, reviver, replacer, sortingReplacer, shuffle, UserFacingError, HexTriGraph, SnubSquareGraph, SquareOrthGraph, SquareDiagGraph, SquareGraph, Square3DGraph, SquareDirectedGraph, SquareFanoronaGraph, BaoGraph, SowingNoEndsGraph, RectTriGraph, BentTriGraph, StarGraph, starFrequencyFromWidth, wng, projectPoint, ptDistance, smallestDegreeDiff, normDeg, deg2rad, rad2deg, toggleFacing, calcBearing, matrixRectRot90, matrixRectRotN90, transposeRect, hexhexAi2Ap, hexhexAp2Ai, triAi2Ap, triAp2Ai, circle2poly, midpoint, distFromCircle, dir2deg, deg2dir, rotateFacing };
1515

1616
export type DirectionCardinal = "N" | "E" | "S" | "W";
1717
export type DirectionDiagonal = "NE" | "SE" | "SW" | "NW";

src/common/pentagons/Edge.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
export class Edge {
2+
private _id: number;
3+
private _vidA: number; // should be lowest
4+
private _vidB: number; // should be highest
5+
private _isOuter: boolean = false;
6+
7+
constructor(id: number, a: number, b: number) {
8+
this._id = id;
9+
this._vidA = a;
10+
this._vidB = b;
11+
}
12+
13+
public get id(): number {
14+
return this._id;
15+
}
16+
public get vidA(): number {
17+
return this._vidA;
18+
}
19+
public get vidB(): number {
20+
return this._vidB;
21+
}
22+
public get isOuter(): boolean|undefined {
23+
return this._isOuter;
24+
}
25+
public set isOuter(val: boolean) {
26+
this._isOuter = val;
27+
}
28+
29+
public toString = (): string => {
30+
return `E${this.id}=V${this.vidA}-V${this.vidB}, isOuter? ${this.isOuter}`;
31+
}
32+
}

src/common/pentagons/Graph.ts

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { Edge } from "./Edge";
2+
import { Vertex } from "./Vertex";
3+
4+
export class Graph {
5+
public vertices: Vertex[] = [];
6+
public edges: Edge[] = [];
7+
public layers: Vertex[][][] = [];
8+
9+
constructor(size: number) {
10+
this.vertices = [];
11+
this.edges = [];
12+
this.makeLayersAndVertices(size);
13+
this.makeEdges(size);
14+
}
15+
16+
private makeLayersAndVertices(size: number): void {
17+
this.layers = [];
18+
19+
// add central vertex
20+
this.vertices.push(new Vertex(0, false));
21+
22+
// make vertices layer by layer
23+
for (let layer = 0; layer < size + 1; layer++) {
24+
const curveList: Vertex[][] = [];
25+
this.layers.push(curveList);
26+
const vertsPerCurve = layer + 1;
27+
let startVertex: Vertex|undefined;
28+
for (let side = 0; side < 5; side++) {
29+
const curve: Vertex[] = [];
30+
curveList.push(curve);
31+
if (layer === 0) {
32+
curve.push(this.vertices[0]);
33+
} else {
34+
for (let n = 0; n < vertsPerCurve; n++) {
35+
const vertex: Vertex = (layer === 0) ? this.vertices[0] : new Vertex(this.vertices.length, layer === size);
36+
if (startVertex === undefined) {
37+
startVertex = vertex;
38+
}
39+
40+
if (side === 4 && n === vertsPerCurve - 1) {
41+
curve.push(startVertex);
42+
} else {
43+
curve.push(vertex);
44+
}
45+
46+
if (n < vertsPerCurve - 1) {
47+
this.vertices.push(vertex);
48+
}
49+
}
50+
}
51+
}
52+
}
53+
// console.log(`Layers are:`);
54+
// for (let l = 0; l < this.layers.length; l++) {
55+
// console.log(`Layer ${l}:`);
56+
// for (let s = 0; s < this.layers[l].length; s++) {
57+
// console.log(`- Side ${s}:`);
58+
// for (const v of this.layers[l][s]) {
59+
// console.log(` V${v.id}`);
60+
// }
61+
// }
62+
// }
63+
// console.log(`vertices: ${this.vertices.map(v => v.toString()).join("\n")}`);
64+
}
65+
66+
private makeEdges(size: number): void {
67+
for (let layer = 0; layer < size + 1; layer++) {
68+
for (let side = 0; side < 5; side++) {
69+
const curve = this.layers[layer][side];
70+
71+
// join consecutive vertices within layer
72+
for (let n = 0; n < curve.length - 1; n++) {
73+
const vidA = curve[n].id;
74+
const vidB = curve[n+1].id;
75+
this.addEdgeIfUnique(vidA, vidB);
76+
}
77+
78+
if (layer < size) {
79+
// join adjacent vertices between layers
80+
const next = this.layers[layer+1][side];
81+
for (let n = 0; n < curve.length; n++) {
82+
const vidA = curve[n].id;
83+
const vidB1 = next[n].id;
84+
const vidB2 = next[n+1].id;
85+
86+
this.addEdgeIfUnique(vidA, vidB1);
87+
this.addEdgeIfUnique(vidA, vidB2);
88+
}
89+
}
90+
91+
}
92+
}
93+
94+
// set outer edges
95+
for (const edge of this.edges) {
96+
// set outer edges
97+
if (this.vertices[edge.vidA].isOuter && this.vertices[edge.vidB].isOuter) {
98+
edge.isOuter = true;
99+
}
100+
}
101+
102+
// set incident edges
103+
for (const edge of this.edges) {
104+
this.vertices[edge.vidA].addEdge(edge.id);
105+
this.vertices[edge.vidB].addEdge(edge.id);
106+
}
107+
108+
// set vertex nbors
109+
for (const edge of this.edges) {
110+
this.vertices[edge.vidA].addNbor(this.vertices[edge.vidB].id);
111+
this.vertices[edge.vidB].addNbor(this.vertices[edge.vidA].id);
112+
}
113+
}
114+
115+
private addEdgeIfUnique(vidA: number, vidB: number): void {
116+
for (const edge of this.edges) {
117+
if (edge.vidA === vidA && edge.vidB === vidB) {
118+
return;
119+
}
120+
}
121+
this.edges.push(new Edge(this.edges.length, vidA, vidB));
122+
}
123+
124+
public toString = (): string => {
125+
let str = "";
126+
127+
if (this.vertices.length === 0) {
128+
return "Graph has no vertices.";
129+
}
130+
131+
str += `${this.vertices.length} vertices:\n`;
132+
for (const vertex of this.vertices) {
133+
str += `- ${vertex}\n`;
134+
}
135+
136+
if (this.edges.length === 0) {
137+
str += "No edges.\n";
138+
} else {
139+
str += `${this.edges.length} edges:\n`;
140+
for (const edge of this.edges) {
141+
str += `- ${edge}\n`;
142+
}
143+
}
144+
145+
return str;
146+
}
147+
}

src/common/pentagons/Vertex.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
export type Point = { x: number; y: number };
2+
3+
export class Vertex {
4+
private _id: number;
5+
private _isOuter: boolean;
6+
private _pt: Point|undefined;
7+
private _nbors: Set<number>;
8+
private _edges: Set<number>;
9+
10+
constructor(id: number, isOuter: boolean) {
11+
this._id = id;
12+
this._isOuter = isOuter;
13+
this._nbors = new Set<number>();
14+
this._edges = new Set<number>();
15+
}
16+
17+
public get id(): number {
18+
return this._id;
19+
}
20+
public get isOuter(): boolean {
21+
return this._isOuter;
22+
}
23+
public get pt(): Point|undefined {
24+
return this._pt;
25+
}
26+
public get nbors(): number[] {
27+
return [...this._nbors];
28+
}
29+
public get edges(): number[] {
30+
return [...this._edges];
31+
}
32+
33+
public addEdge(edge: number) {
34+
this._edges.add(edge);
35+
}
36+
public addNbor(nbor: number) {
37+
this._nbors.add(nbor);
38+
}
39+
public setPoint(x: number, y: number) {
40+
this._pt = {x, y};
41+
}
42+
43+
public toString = (): string => {
44+
return `V${this.id} at ${this.pt?.x.toFixed(3)},${this.pt?.y.toFixed(3)}, N=${this.nbors.join(",")}, E=${this.edges.join(",")}, isOuter? ${this.isOuter}`;
45+
}
46+
47+
}

src/common/pentagons/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { Graph } from "./Graph";
2+
import { Vertex } from "./Vertex";
3+
import { Edge } from "./Edge";
4+
5+
export { Vertex, Edge, Graph };
6+
7+
export const pentagonalBoard = (size: number): Graph => new Graph(size);

0 commit comments

Comments
 (0)