Skip to content

Commit 4fc929b

Browse files
committed
Update all the graphs to use infinite labels
1 parent 05bb8e0 commit 4fc929b

14 files changed

Lines changed: 196 additions & 215 deletions

File tree

src/common/columnLabels.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
export const columnLabels = "abcdefghijklmnopqrstuvwxyz".split("");
2+
3+
/**
4+
* An infinite generator for creating column labels from an initial string of characters.
5+
* With the English alphabet, you would get a-z, then aa-az-ba-zz, then aaa etc.
6+
*/
7+
export function* generateColumnLabel(labels: string): IterableIterator<string> {
8+
let n = 0;
9+
let len = 1;
10+
const chars = labels.split("");
11+
while (true) {
12+
let label = "";
13+
let mask = n.toString(chars.length);
14+
while (mask.length < len) {
15+
mask = "0" + mask;
16+
}
17+
for (const char of mask) {
18+
const val = parseInt(char, chars.length);
19+
label += chars[val];
20+
}
21+
yield label;
22+
n++;
23+
const threshold = Math.pow(chars.length, len);
24+
if (n === threshold) {
25+
n = 0;
26+
len++;
27+
}
28+
}
29+
}
30+
31+
/** Zero-based index to multi-letter label (0 → a, 25 → z, 26 → aa, …). */
32+
export const indexToColumnLabel = (
33+
index: number,
34+
labels: readonly string[] = columnLabels,
35+
): string => {
36+
const base = labels.length;
37+
let length = 1;
38+
if (index >= base) {
39+
length = Math.floor(Math.log(index) / Math.log(base)) + 1;
40+
}
41+
let label = "";
42+
let counter = index;
43+
for (let i = length; i > 0; i--) {
44+
const radix = base ** (i - 1);
45+
let idx = Math.floor(counter / radix);
46+
if (i > 1) {
47+
idx--;
48+
}
49+
const char = labels[idx];
50+
if (char === undefined) {
51+
throw new Error(`Could not find a character at index ${idx}`);
52+
}
53+
label += char;
54+
counter = counter % radix;
55+
}
56+
return label;
57+
};
58+
59+
/** Multi-letter label to zero-based index. */
60+
export const columnLabelToIndex = (
61+
label: string,
62+
labels: readonly string[] = columnLabels,
63+
): number => {
64+
const reversed = [...label.split("").reverse()];
65+
let index = 0;
66+
for (let exp = 0; exp < reversed.length; exp++) {
67+
const idx = labels.indexOf(reversed[exp]);
68+
if (idx < 0) {
69+
throw new Error(`The column label is invalid: ${reversed[exp]}`);
70+
}
71+
if (exp > 0) {
72+
index += (idx + 1) * (labels.length ** exp);
73+
} else {
74+
index += idx * (labels.length ** exp);
75+
}
76+
}
77+
return index;
78+
};

src/common/graphs/bao.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { DirectedGraph } from "graphology";
2+
import { indexToColumnLabel, columnLabelToIndex } from "../columnLabels";
23
import { IGraph } from "./IGraph";
34

4-
const columnLabels = "abcdefghijklmnopqrstuvwxyz".split("");
5-
65
export type PitType = "nyumba"|"kichwa1L"|"kichwa1R"|"kimbi1L"|"kimbi1R"|"kichwa2L"|"kichwa2R"|"kimbi2L"|"kimbi2R"|"pit";
76
type NodeData = {
87
type: PitType;
@@ -25,19 +24,18 @@ export class BaoGraph implements IGraph {
2524
}
2625

2726
public coords2algebraic(x: number, y: number): string {
28-
return columnLabels[x] + (this.height - y).toString();
27+
return indexToColumnLabel(x) + (this.height - y).toString();
2928
}
3029

3130
public algebraic2coords(cell: string): [number, number] {
32-
const pair: string[] = cell.split("");
33-
const num = (pair.slice(1)).join("");
34-
const x = columnLabels.indexOf(pair[0]);
35-
if ( (x === undefined) || (x < 0) ) {
36-
throw new Error(`The column label is invalid: ${pair[0]}`);
31+
const match = cell.match(/^([a-z]+)(\d+)$/);
32+
if (match === null) {
33+
throw new Error(`The algebraic notation is invalid: ${cell}`);
3734
}
38-
const y = parseInt(num, 10);
39-
if ( (y === undefined) || (isNaN(y)) ) {
40-
throw new Error(`The row label is invalid: ${pair[1]}`);
35+
const x = columnLabelToIndex(match[1]);
36+
const y = parseInt(match[2], 10);
37+
if (isNaN(y)) {
38+
throw new Error(`The row label is invalid: ${match[2]}`);
4139
}
4240
return [x, this.height - y];
4341
}

src/common/graphs/bent-tri.ts

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,15 @@
11
import { UndirectedGraph } from "graphology";
22
import { bidirectional } from "graphology-shortest-path/unweighted";
33
import { bentTriBoard, Graph as BentTriTopology, type BentTriOptions } from "../bentTri";
4+
import { indexToColumnLabel, columnLabelToIndex } from "../columnLabels";
45
import { IGraph } from "./IGraph";
56

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-
167
const parseAlgebraic = (cell: string): [number, number] => {
178
const match = cell.match(/^([a-z]+)(\d+)$/);
189
if (match === null) {
1910
throw new Error(`Invalid algebraic notation: ${cell}`);
2011
}
21-
const ring = columnLabels.indexOf(match[1]);
22-
if (ring < 0) {
23-
throw new Error(`Invalid ring label: ${match[1]}`);
24-
}
12+
const ring = columnLabelToIndex(match[1]);
2513
const pos = parseInt(match[2], 10) - 1;
2614
if (isNaN(pos) || pos < 0) {
2715
throw new Error(`Invalid position: ${match[2]}`);
@@ -54,7 +42,7 @@ export class BentTriGraph implements IGraph {
5442
private buildLabelMaps(): void {
5543
for (let ring = 0; ring < this.topo.gridLayers.length; ring++) {
5644
const layer = this.topo.gridLayers[ring];
57-
const letter = ringLetter(ring);
45+
const letter = indexToColumnLabel(ring);
5846
for (let pos = 0; pos < layer.length; pos++) {
5947
const label = letter + (pos + 1).toString();
6048
const vid = layer[pos].id;
@@ -92,7 +80,7 @@ export class BentTriGraph implements IGraph {
9280

9381
/** x = ring index (0 = outer), y = clockwise position index within the ring. */
9482
public coords2algebraic(x: number, y: number): string {
95-
return ringLetter(x) + (y + 1).toString();
83+
return indexToColumnLabel(x) + (y + 1).toString();
9684
}
9785

9886
public algebraic2coords(cell: string): [number, number] {

src/common/graphs/hex-cone.ts

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { UndirectedGraph } from "graphology";
22
import { bidirectional } from 'graphology-shortest-path/unweighted';
3+
import { indexToColumnLabel, columnLabelToIndex } from "../columnLabels";
34
import { IGraph } from "./IGraph";
4-
5-
const columnLabels = "abcdefghijklmnopqrstuvwxyz".split("");
65
type directions = "NE"|"E"|"SE"|"SW"|"W"|"NW";
76

87
export class HexConeGraph implements IGraph {
@@ -19,20 +18,16 @@ export class HexConeGraph implements IGraph {
1918
}
2019

2120
public coords2algebraic(x: number, y: number): string {
22-
return columnLabels[x] + (y + 1).toString();
21+
return indexToColumnLabel(x) + (y + 1).toString();
2322
}
2423

2524
public algebraic2coords(cell: string): [number, number] {
26-
const pair: string[] = cell.split("");
27-
const num = pair.slice(1).join("");
28-
const x = columnLabels.indexOf(pair[0]);
29-
if (x === undefined || x < 0) {
30-
throw new Error(`The column label is invalid: ${pair[0]}`);
31-
}
32-
const y = Number(num);
33-
if (y === undefined || isNaN(y) || num === "") {
34-
throw new Error(`The row label is invalid: ${pair[1]}`);
25+
const match = cell.match(/^([a-z]+)(\d+)$/);
26+
if (match === null) {
27+
throw new Error(`The algebraic notation is invalid: ${cell}`);
3528
}
29+
const x = columnLabelToIndex(match[1]);
30+
const y = Number(match[2]);
3631
return [x, y - 1];
3732
}
3833

src/common/graphs/hex-slanted.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { UndirectedGraph } from "graphology";
22
import { bidirectional } from 'graphology-shortest-path/unweighted';
3+
import { indexToColumnLabel, columnLabelToIndex } from "../columnLabels";
34
import { IGraph } from "./IGraph";
4-
5-
const columnLabels = "abcdefghijklmnopqrstuvwxyz".split("");
65
type directions = "NE"|"E"|"SE"|"SW"|"W"|"NW";
76
type Edge = "N"|"E"|"S"|"W";
87

@@ -20,19 +19,18 @@ export class HexSlantedGraph implements IGraph {
2019
}
2120

2221
public coords2algebraic(x: number, y: number): string {
23-
return columnLabels[x] + (y + 1).toString();
22+
return indexToColumnLabel(x) + (y + 1).toString();
2423
}
2524

2625
public algebraic2coords(cell: string): [number, number] {
27-
const pair: string[] = cell.split("");
28-
const num = pair.slice(1).join("");
29-
const x = columnLabels.indexOf(pair[0]);
30-
if (x === undefined || x < 0) {
31-
throw new Error(`The column label is invalid: ${pair[0]}`);
26+
const match = cell.match(/^([a-z]+)(\d+)$/);
27+
if (match === null) {
28+
throw new Error(`The algebraic notation is invalid: ${cell}`);
3229
}
33-
const y = Number(num);
34-
if (y === undefined || isNaN(y) || num === "") {
35-
throw new Error(`The row label is invalid: ${pair[1]}`);
30+
const x = columnLabelToIndex(match[1]);
31+
const y = Number(match[2]);
32+
if (isNaN(y)) {
33+
throw new Error(`The row label is invalid: ${match[2]}`);
3634
}
3735
return [x, y - 1];
3836
}

src/common/graphs/hexMoon.ts

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { UndirectedGraph } from "graphology";
22
import { bidirectional } from 'graphology-shortest-path/unweighted';
3+
import { indexToColumnLabel, columnLabelToIndex } from "../columnLabels";
34
import { IGraph } from "./IGraph";
45

5-
const columnLabels = "abcdefghijklmnopqrstuvwxyz".split("");
6-
76
type Edge = "N"|"NE"|"SE"|"S"|"SW"|"NW";
87

98
export class HexMoonGraph implements IGraph {
@@ -35,7 +34,7 @@ export class HexMoonGraph implements IGraph {
3534
return `e${x + 1}`;
3635
}
3736
} else {
38-
return columnLabels[this.height - y - 1] + (x + 1).toString();
37+
return indexToColumnLabel(this.height - y - 1) + (x + 1).toString();
3938
}
4039
}
4140

@@ -53,28 +52,28 @@ export class HexMoonGraph implements IGraph {
5352
throw new Error("Unrecognized cell suffix.");
5453
}
5554
} else {
56-
const num = (pair.slice(1)).join("");
57-
const x = Number(num);
58-
if ( (x === undefined) || (isNaN(x)) || num === "" ) {
59-
throw new Error(`The column label is invalid: ${num}`);
55+
const match = cell.match(/^([a-z]+)(\d+)$/);
56+
if (match === null) {
57+
throw new Error(`The algebraic notation is invalid: ${cell}`);
6058
}
61-
const y = columnLabels.indexOf(pair[0]);
62-
if ( (y === undefined) || (y < 0) ) {
63-
throw new Error(`The row label is invalid: ${pair[0]}`);
59+
const x = Number(match[2]);
60+
if (isNaN(x)) {
61+
throw new Error(`The column label is invalid: ${match[2]}`);
6462
}
63+
const rowIdx = columnLabelToIndex(match[1]);
6564

6665
const midrow = Math.floor(this.height / 2);
6766
const delta = this.maxwidth - this.minwidth;
68-
const rowWidth = this.minwidth + (midrow - Math.abs(delta - y));
69-
if ( (x < 0) || (x > rowWidth) ) {
70-
throw new Error(`The column label is invalid: ${num}`);
67+
const rowWidth = this.minwidth + (midrow - Math.abs(delta - rowIdx));
68+
if ( (x < 1) || (x > rowWidth) ) {
69+
throw new Error(`The column label is invalid: ${match[2]}`);
7170
}
7271

73-
if (pair[0] === "e" && x > 5) {
74-
return [x + 1, this.height - y - 1];
75-
} else {
76-
return [x - 1, this.height - y - 1];
72+
const rowLetter = indexToColumnLabel(rowIdx);
73+
if (rowLetter === "e" && x > 5) {
74+
return [x, this.height - rowIdx - 1];
7775
}
76+
return [x - 1, this.height - rowIdx - 1];
7877
}
7978
}
8079

src/common/graphs/hextri.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { UndirectedGraph } from "graphology";
22
import { bidirectional } from 'graphology-shortest-path/unweighted';
3+
import { indexToColumnLabel, columnLabelToIndex } from "../columnLabels";
34
import { IGraph } from "./IGraph";
4-
5-
const columnLabels = "abcdefghijklmnopqrstuvwxyz".split("");
65
export type Edge = "N"|"NE"|"SE"|"S"|"SW"|"NW";
76
export type HexDir = "NE"|"E"|"SE"|"SW"|"W"|"NW";
87

@@ -54,20 +53,20 @@ export class HexTriGraph implements IGraph {
5453
if (this.reverseLetters) {
5554
idx = (this.height - 1) - idx;
5655
}
57-
return columnLabels[idx] + (x + 1).toString();
56+
return indexToColumnLabel(idx) + (x + 1).toString();
5857
}
5958

6059
public algebraic2coords(cell: string): [number, number] {
61-
const pair: string[] = cell.split("");
62-
const num = (pair.slice(1)).join("");
60+
const match = cell.match(/^([a-z]+)(\d+)$/);
61+
if (match === null) {
62+
throw new Error(`The algebraic notation is invalid: ${cell}`);
63+
}
64+
const num = match[2];
6365
const x = Number(num);
6466
if ( (x === undefined) || (isNaN(x)) || num === "" ) {
6567
throw new Error(`The column label is invalid: ${num}`);
6668
}
67-
const y = columnLabels.indexOf(pair[0]);
68-
if ( (y === undefined) || (y < 0) ) {
69-
throw new Error(`The row label is invalid: ${pair[0]}`);
70-
}
69+
const y = columnLabelToIndex(match[1]);
7170

7271
let realY = this.height - y - 1;
7372
if (this.reverseLetters) {

0 commit comments

Comments
 (0)