feat: add GameMap and A* pathfinding with tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import type { Position } from '@dflike/shared';
|
||||
import type { GameMap } from './GameMap.js';
|
||||
|
||||
interface Node {
|
||||
x: number;
|
||||
y: number;
|
||||
g: number;
|
||||
h: number;
|
||||
f: number;
|
||||
parent: Node | null;
|
||||
}
|
||||
|
||||
function heuristic(a: Position, b: Position): number {
|
||||
return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
|
||||
}
|
||||
|
||||
const NEIGHBORS = [
|
||||
{ dx: 0, dy: -1 },
|
||||
{ dx: 0, dy: 1 },
|
||||
{ dx: -1, dy: 0 },
|
||||
{ dx: 1, dy: 0 },
|
||||
];
|
||||
|
||||
export function findPath(map: GameMap, start: Position, goal: Position): Position[] | null {
|
||||
if (start.x === goal.x && start.y === goal.y) return [];
|
||||
|
||||
const open: Node[] = [];
|
||||
const closed = new Set<string>();
|
||||
const key = (x: number, y: number) => `${x},${y}`;
|
||||
|
||||
const startNode: Node = {
|
||||
x: start.x, y: start.y,
|
||||
g: 0, h: heuristic(start, goal), f: heuristic(start, goal),
|
||||
parent: null,
|
||||
};
|
||||
open.push(startNode);
|
||||
|
||||
while (open.length > 0) {
|
||||
// Find lowest f
|
||||
let bestIdx = 0;
|
||||
for (let i = 1; i < open.length; i++) {
|
||||
if (open[i].f < open[bestIdx].f) bestIdx = i;
|
||||
}
|
||||
const current = open.splice(bestIdx, 1)[0];
|
||||
|
||||
if (current.x === goal.x && current.y === goal.y) {
|
||||
// Reconstruct path (excluding start)
|
||||
const path: Position[] = [];
|
||||
let node: Node | null = current;
|
||||
while (node && !(node.x === start.x && node.y === start.y)) {
|
||||
path.push({ x: node.x, y: node.y });
|
||||
node = node.parent;
|
||||
}
|
||||
return path.reverse();
|
||||
}
|
||||
|
||||
closed.add(key(current.x, current.y));
|
||||
|
||||
for (const { dx, dy } of NEIGHBORS) {
|
||||
const nx = current.x + dx;
|
||||
const ny = current.y + dy;
|
||||
if (!map.isWalkable(nx, ny) || closed.has(key(nx, ny))) continue;
|
||||
|
||||
const g = current.g + 1;
|
||||
const h = heuristic({ x: nx, y: ny }, goal);
|
||||
const existing = open.find(n => n.x === nx && n.y === ny);
|
||||
|
||||
if (existing) {
|
||||
if (g < existing.g) {
|
||||
existing.g = g;
|
||||
existing.f = g + h;
|
||||
existing.parent = current;
|
||||
}
|
||||
} else {
|
||||
open.push({ x: nx, y: ny, g, h, f: g + h, parent: current });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null; // No path found
|
||||
}
|
||||
Reference in New Issue
Block a user