use std::collections::{BinaryHeap, HashMap};
use std::cmp::Ordering;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct GridPos {
pub x: i32,
pub y: i32,
}
impl GridPos {
pub fn new(x: i32, y: i32) -> Self {
Self { x, y }
}
pub fn distance(&self, other: &GridPos) -> f32 {
let dx = (self.x - other.x) as f32;
let dy = (self.y - other.y) as f32;
(dx * dx + dy * dy).sqrt()
}
pub fn manhattan_distance(&self, other: &GridPos) -> i32 {
(self.x - other.x).abs() + (self.y - other.y).abs()
}
}
#[derive(Debug, Clone)]
struct Node {
pos: GridPos,
g_cost: f32,
h_cost: f32,
parent: Option<GridPos>,
}
impl Node {
fn f_cost(&self) -> f32 {
self.g_cost + self.h_cost
}
}
impl PartialEq for Node {
fn eq(&self, other: &Self) -> bool {
self.f_cost() == other.f_cost()
}
}
impl Eq for Node {}
impl PartialOrd for Node {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
other.f_cost().partial_cmp(&self.f_cost())
}
}
impl Ord for Node {
fn cmp(&self, other: &Self) -> Ordering {
self.partial_cmp(other).unwrap_or(Ordering::Equal)
}
}
pub struct Pathfinder {
pub grid_width: i32,
pub grid_height: i32,
pub obstacles: Vec<GridPos>,
pub allow_diagonal: bool,
}
impl Pathfinder {
pub fn new(width: i32, height: i32) -> Self {
Self {
grid_width: width,
grid_height: height,
obstacles: Vec::new(),
allow_diagonal: true,
}
}
pub fn add_obstacle(&mut self, x: i32, y: i32) {
self.obstacles.push(GridPos::new(x, y));
}
pub fn remove_obstacle(&mut self, x: i32, y: i32) {
self.obstacles.retain(|pos| pos.x != x || pos.y != y);
}
pub fn is_walkable(&self, pos: &GridPos) -> bool {
pos.x >= 0
&& pos.x < self.grid_width
&& pos.y >= 0
&& pos.y < self.grid_height
&& !self.obstacles.contains(pos)
}
fn get_neighbors(&self, pos: &GridPos) -> Vec<GridPos> {
let mut neighbors = Vec::new();
let directions = if self.allow_diagonal {
vec![
(-1, 0), (1, 0), (0, -1), (0, 1),
(-1, -1), (-1, 1), (1, -1), (1, 1),
]
} else {
vec![(-1, 0), (1, 0), (0, -1), (0, 1)]
};
for (dx, dy) in directions {
let new_pos = GridPos::new(pos.x + dx, pos.y + dy);
if self.is_walkable(&new_pos) {
neighbors.push(new_pos);
}
}
neighbors
}
pub fn find_path(&self, start: GridPos, goal: GridPos) -> Option<Vec<GridPos>> {
if !self.is_walkable(&start) || !self.is_walkable(&goal) {
return None;
}
let mut open_set = BinaryHeap::new();
let mut came_from: HashMap<GridPos, GridPos> = HashMap::new();
let mut g_score: HashMap<GridPos, f32> = HashMap::new();
g_score.insert(start, 0.0);
open_set.push(Node {
pos: start,
g_cost: 0.0,
h_cost: start.distance(&goal),
parent: None,
});
while let Some(current) = open_set.pop() {
if current.pos == goal {
return Some(self.reconstruct_path(&came_from, current.pos));
}
for neighbor in self.get_neighbors(¤t.pos) {
let tentative_g = g_score.get(¤t.pos).unwrap_or(&f32::MAX)
+ current.pos.distance(&neighbor);
if tentative_g < *g_score.get(&neighbor).unwrap_or(&f32::MAX) {
came_from.insert(neighbor, current.pos);
g_score.insert(neighbor, tentative_g);
open_set.push(Node {
pos: neighbor,
g_cost: tentative_g,
h_cost: neighbor.distance(&goal),
parent: Some(current.pos),
});
}
}
}
None
}
fn reconstruct_path(&self, came_from: &HashMap<GridPos, GridPos>, mut current: GridPos) -> Vec<GridPos> {
let mut path = vec![current];
while let Some(&prev) = came_from.get(¤t) {
path.push(prev);
current = prev;
}
path.reverse();
path
}
}