2 changed files with 138 additions and 0 deletions
@ -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; |
||||
|
} |
||||
@ -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…
Reference in new issue