78 lines
2.7 KiB
Python
78 lines
2.7 KiB
Python
"""Pathfinding for isometric game navigation.
|
|
|
|
Implements A* and click-to-move navigation for isometric games
|
|
where the bot needs to move between known locations.
|
|
"""
|
|
|
|
from typing import List, Tuple, Optional, Dict
|
|
from dataclasses import dataclass
|
|
import heapq
|
|
import math
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@dataclass
|
|
class Waypoint:
|
|
"""A named location in the game world."""
|
|
name: str
|
|
screen_x: int
|
|
screen_y: int
|
|
metadata: Dict = None
|
|
|
|
|
|
class WaypointGraph:
|
|
"""Graph of connected waypoints for navigation."""
|
|
|
|
def __init__(self):
|
|
self._waypoints: Dict[str, Waypoint] = {}
|
|
self._edges: Dict[str, List[str]] = {}
|
|
|
|
def add_waypoint(self, waypoint: Waypoint) -> None:
|
|
self._waypoints[waypoint.name] = waypoint
|
|
self._edges.setdefault(waypoint.name, [])
|
|
|
|
def connect(self, name_a: str, name_b: str, bidirectional: bool = True) -> None:
|
|
self._edges.setdefault(name_a, []).append(name_b)
|
|
if bidirectional:
|
|
self._edges.setdefault(name_b, []).append(name_a)
|
|
|
|
def find_path(self, start: str, goal: str) -> Optional[List[Waypoint]]:
|
|
"""A* pathfinding between waypoints."""
|
|
if start not in self._waypoints or goal not in self._waypoints:
|
|
return None
|
|
|
|
goal_wp = self._waypoints[goal]
|
|
|
|
def heuristic(name: str) -> float:
|
|
wp = self._waypoints[name]
|
|
return math.hypot(wp.screen_x - goal_wp.screen_x, wp.screen_y - goal_wp.screen_y)
|
|
|
|
open_set = [(heuristic(start), 0, start)]
|
|
came_from: Dict[str, str] = {}
|
|
g_score: Dict[str, float] = {start: 0}
|
|
|
|
while open_set:
|
|
_, cost, current = heapq.heappop(open_set)
|
|
|
|
if current == goal:
|
|
path = []
|
|
while current in came_from:
|
|
path.append(self._waypoints[current])
|
|
current = came_from[current]
|
|
path.append(self._waypoints[start])
|
|
return list(reversed(path))
|
|
|
|
for neighbor in self._edges.get(current, []):
|
|
n_wp = self._waypoints[neighbor]
|
|
c_wp = self._waypoints[current]
|
|
edge_cost = math.hypot(n_wp.screen_x - c_wp.screen_x, n_wp.screen_y - c_wp.screen_y)
|
|
tentative_g = g_score[current] + edge_cost
|
|
|
|
if tentative_g < g_score.get(neighbor, float('inf')):
|
|
came_from[neighbor] = current
|
|
g_score[neighbor] = tentative_g
|
|
heapq.heappush(open_set, (tentative_g + heuristic(neighbor), tentative_g, neighbor))
|
|
|
|
return None
|