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(); 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 }