"""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