Browse Source

feat(chase): add hex graph adjacency and path utilities

Made-with: Cursor
master
npmrun 2 weeks ago
parent
commit
8962367652
  1. 71
      src/game/chase/hexGraph.ts
  2. 67
      tests/chase/hexGraph.test.ts

71
src/game/chase/hexGraph.ts

@ -0,0 +1,71 @@
import type { GameGraph, NodeId } from "./types";
export function axialNeighbors(q: number, r: number): Array<[number, number]> {
return [
[q + 1, r],
[q + 1, r - 1],
[q, r - 1],
[q - 1, r],
[q - 1, r + 1],
[q, r + 1],
];
}
export function shortestPathLength(
graph: GameGraph,
from: NodeId,
to: NodeId,
): number {
if (from === to) {
return 0;
}
if (!graph.nodes[from] || !graph.nodes[to]) {
return Infinity;
}
const queue: Array<[NodeId, number]> = [[from, 0]];
const visited = new Set<NodeId>([from]);
while (queue.length > 0) {
const [current, distance] = queue.shift()!;
const node = graph.nodes[current];
for (const neighbor of node.neighbors) {
if (neighbor === to) {
return distance + 1;
}
if (!visited.has(neighbor) && graph.nodes[neighbor]) {
visited.add(neighbor);
queue.push([neighbor, distance + 1]);
}
}
}
return Infinity;
}
export function isConnected(graph: GameGraph): boolean {
const nodeIds = Object.keys(graph.nodes);
if (nodeIds.length <= 1) {
return true;
}
const start = nodeIds[0];
const visited = new Set<NodeId>([start]);
const queue: NodeId[] = [start];
while (queue.length > 0) {
const current = queue.shift()!;
const node = graph.nodes[current];
for (const neighbor of node.neighbors) {
if (!visited.has(neighbor) && graph.nodes[neighbor]) {
visited.add(neighbor);
queue.push(neighbor);
}
}
}
return visited.size === nodeIds.length;
}

67
tests/chase/hexGraph.test.ts

@ -0,0 +1,67 @@
import { describe, expect, it } from "vitest";
import {
axialNeighbors,
isConnected,
shortestPathLength,
} from "../../src/game/chase/hexGraph";
import type { GameGraph } from "../../src/game/chase/types";
function buildGraph(nodes: GameGraph["nodes"]): GameGraph {
return {
nodes,
edgeList: [],
};
}
describe("hex graph utilities", () => {
it("returns 6 axial neighbors", () => {
const neighbors = axialNeighbors(0, 0);
expect(neighbors).toEqual([
[1, 0],
[1, -1],
[0, -1],
[-1, 0],
[-1, 1],
[0, 1],
]);
});
it("finds bfs shortest path length", () => {
const graph = buildGraph({
A: { id: "A", q: 0, r: 0, neighbors: ["B"] },
B: { id: "B", q: 1, r: 0, neighbors: ["A", "C"] },
C: { id: "C", q: 2, r: 0, neighbors: ["B", "D"] },
D: { id: "D", q: 3, r: 0, neighbors: ["C"] },
});
expect(shortestPathLength(graph, "A", "D")).toBe(3);
expect(shortestPathLength(graph, "A", "A")).toBe(0);
});
it("returns Infinity when target is unreachable", () => {
const graph = buildGraph({
A: { id: "A", q: 0, r: 0, neighbors: ["B"] },
B: { id: "B", q: 1, r: 0, neighbors: ["A"] },
C: { id: "C", q: 5, r: 5, neighbors: [] },
});
expect(shortestPathLength(graph, "A", "C")).toBe(Infinity);
});
it("checks graph connectivity", () => {
const connectedGraph = buildGraph({
A: { id: "A", q: 0, r: 0, neighbors: ["B"] },
B: { id: "B", q: 1, r: 0, neighbors: ["A", "C"] },
C: { id: "C", q: 2, r: 0, neighbors: ["B"] },
});
const disconnectedGraph = buildGraph({
A: { id: "A", q: 0, r: 0, neighbors: ["B"] },
B: { id: "B", q: 1, r: 0, neighbors: ["A"] },
C: { id: "C", q: 5, r: 5, neighbors: [] },
});
expect(isConnected(connectedGraph)).toBe(true);
expect(isConnected(disconnectedGraph)).toBe(false);
});
});
Loading…
Cancel
Save