From 4eab4d11984c1c789f5e66ffc159068ee341154f Mon Sep 17 00:00:00 2001 From: Kristofers Solo Date: Tue, 14 Jan 2025 11:58:59 +0200 Subject: [PATCH] feat(pathfinding): use pathfinding crate --- Cargo.lock | 54 ++++++++++++++-- Cargo.toml | 3 +- src/pathfinding.rs | 32 ++++++++++ src/pathfinding/maze.rs | 137 ---------------------------------------- src/pathfinding/mod.rs | 2 - src/pathfinding/node.rs | 20 ------ tests/pathfinding.rs | 16 +++++ 7 files changed, 98 insertions(+), 166 deletions(-) create mode 100644 src/pathfinding.rs delete mode 100644 src/pathfinding/maze.rs delete mode 100644 src/pathfinding/mod.rs delete mode 100644 src/pathfinding/node.rs create mode 100644 tests/pathfinding.rs diff --git a/Cargo.lock b/Cargo.lock index a2b05bc..185f51d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1248,7 +1248,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "shlex", "syn", ] @@ -1631,7 +1631,7 @@ dependencies = [ "log", "rangemap", "rayon", - "rustc-hash", + "rustc-hash 1.1.0", "rustybuzz", "self_cell", "swash", @@ -1743,6 +1743,18 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +[[package]] +name = "deprecate-until" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a3767f826efbbe5a5ae093920b58b43b01734202be697e1354914e862e8e704" +dependencies = [ + "proc-macro2", + "quote", + "semver", + "syn", +] + [[package]] name = "derive_more" version = "1.0.0" @@ -2346,6 +2358,7 @@ dependencies = [ "claims", "glam", "hexx", + "pathfinding", "rand", "rstest", "serde", @@ -2420,6 +2433,15 @@ dependencies = [ "libc", ] +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits", +] + [[package]] name = "io-kit-sys" version = "0.4.1" @@ -2687,7 +2709,7 @@ dependencies = [ "indexmap", "log", "pp-rs", - "rustc-hash", + "rustc-hash 1.1.0", "spirv", "termcolor", "thiserror 1.0.69", @@ -2708,7 +2730,7 @@ dependencies = [ "once_cell", "regex", "regex-syntax 0.8.5", - "rustc-hash", + "rustc-hash 1.1.0", "thiserror 1.0.69", "tracing", "unicode-ident", @@ -3166,6 +3188,20 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pathfinding" +version = "4.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301ad6aa19104eeb9af172b3d6a4ab8a5ea26234890baf2fcb1cbbc3f05f674b" +dependencies = [ + "deprecate-until", + "indexmap", + "integer-sqrt", + "num-traits", + "rustc-hash 2.1.0", + "thiserror 2.0.3", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -3567,6 +3603,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" + [[package]] name = "rustc_version" version = "0.4.1" @@ -4283,7 +4325,7 @@ dependencies = [ "parking_lot", "profiling", "raw-window-handle", - "rustc-hash", + "rustc-hash 1.1.0", "smallvec", "thiserror 1.0.69", "wgpu-hal", @@ -4325,7 +4367,7 @@ dependencies = [ "range-alloc", "raw-window-handle", "renderdoc-sys", - "rustc-hash", + "rustc-hash 1.1.0", "smallvec", "thiserror 1.0.69", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index 9b2ad83..603250a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ thiserror = "2.0" bevy = { version = "0.15", optional = true } bevy_utils = { version = "0.15", optional = true } glam = { version = "0.29", optional = true } +pathfinding = { version = "4.13", optional = true } [dependencies.bevy_reflect] @@ -48,7 +49,7 @@ bevy_reflect = [ "hexx/bevy_reflect", "dep:glam", ] -pathfinding = [] +pathfinding = ["dep:pathfinding"] full = ["serde", "bevy", "pathfinding"] [profile.dev] diff --git a/src/pathfinding.rs b/src/pathfinding.rs new file mode 100644 index 0000000..ca56743 --- /dev/null +++ b/src/pathfinding.rs @@ -0,0 +1,32 @@ +use hexx::{EdgeDirection, Hex}; +use pathfinding::prelude::*; + +use crate::Maze; + +impl Maze { + pub fn find_path(&self, from: Hex, to: Hex) -> Option> { + let successors = |pos: &Hex| { + { + EdgeDirection::ALL_DIRECTIONS.iter().filter_map(|&dir| { + let neighbor = pos.neighbor(dir); + if let Some(current_tile) = self.get(pos) { + if let Some(_) = self.get(&neighbor) { + if !current_tile.walls.contains(dir) { + return Some((neighbor, 1)); // Cost of 1 for each step + } + } + } + None + }) + } + .collect::>() + }; + + let heuristic = |pos: &Hex| { + let diff = *pos - to; + (diff.x.abs() + diff.y.abs()) as u32 + }; + + astar(&from, successors, heuristic, |pos| *pos == to).map(|(path, _)| path) + } +} diff --git a/src/pathfinding/maze.rs b/src/pathfinding/maze.rs deleted file mode 100644 index feb18f3..0000000 --- a/src/pathfinding/maze.rs +++ /dev/null @@ -1,137 +0,0 @@ -use crate::Maze; -#[cfg(feature = "bevy_reflect")] -use bevy_utils::{HashMap, HashSet}; -use hexx::{EdgeDirection, Hex}; -#[cfg(not(feature = "bevy_reflect"))] -use std::collections::{HashMap, HashSet}; -use std::{collections::BinaryHeap, i32}; - -use super::node::Node; - -impl Maze { - pub fn find_path(&self, from: Hex, to: Hex) -> Option> { - let mut open_set = BinaryHeap::new(); - let mut came_from = HashMap::new(); - let mut g_score = HashMap::new(); - let mut closed_set = HashSet::new(); - - initialize_search(&mut open_set, &mut g_score, from, to); - while let Some(current) = open_set.pop() { - if current.position == to { - return Some(self.reconstruct_path(came_from, current.position)); - } - if !self.process_current_node( - current, - to, - &mut open_set, - &mut came_from, - &mut g_score, - &mut closed_set, - ) { - continue; - } - } - None - } - - fn is_valid_move(&self, from: &Hex, to: &Hex, direction: EdgeDirection) -> bool { - if let Some(current_tile) = self.get(from) { - if let Some(_) = self.get(to) { - return !current_tile.walls.contains(direction); - } - } - false - } - - fn process_current_node( - &self, - current: Node, - goal: Hex, - open_set: &mut BinaryHeap, - came_from: &mut HashMap, - g_score: &mut HashMap, - closed_set: &mut HashSet, - ) -> bool { - if closed_set.contains(¤t.position) { - return false; - } - closed_set.insert(current.position); - - self.process_neighbors(current, goal, open_set, came_from, g_score, closed_set); - - true - } - - fn process_neighbors( - &self, - current: Node, - goal: Hex, - open_set: &mut BinaryHeap, - came_from: &mut HashMap, - g_score: &mut HashMap, - closed_set: &HashSet, - ) { - for direction in EdgeDirection::ALL_DIRECTIONS { - let neighbor_pos = current.position.neighbor(direction); - if closed_set.contains(&neighbor_pos) { - continue; - } - if self.is_valid_move(¤t.position, &neighbor_pos, direction) { - self.update_neighbor(current, neighbor_pos, goal, open_set, came_from, g_score); - } - } - } - - fn update_neighbor( - &self, - current: Node, - neighbor_pos: Hex, - goal: Hex, - open_set: &mut BinaryHeap, - came_from: &mut HashMap, - g_score: &mut HashMap, - ) { - let tentative_g_score = g_score.get(¤t.position).unwrap() + 1; - - if tentative_g_score < *g_score.get(&neighbor_pos).unwrap_or(&i32::MAX) { - came_from.insert(neighbor_pos, current.position); - g_score.insert(neighbor_pos, tentative_g_score); - open_set.push(Node { - position: neighbor_pos, - f_score: tentative_g_score + heuristic(neighbor_pos, goal), - g_score: tentative_g_score, - }); - } - } - - fn reconstruct_path(&self, came_from: HashMap, current: Hex) -> Vec { - let mut path = vec![current]; - let mut current_pos = current; - while let Some(&prev) = came_from.get(¤t_pos) { - path.push(prev); - current_pos = prev; - } - path.reverse(); - path - } -} - -fn initialize_search( - open_set: &mut BinaryHeap, - g_score: &mut HashMap, - start: Hex, - goal: Hex, -) { - g_score.insert(start, 0); - open_set.push(Node { - position: start, - f_score: heuristic(start, goal), - g_score: 0, - }) -} - -fn heuristic(from: Hex, to: Hex) -> i32 { - // Manhatan distance for hex grid - let pos = from - to; - pos.x.abs() + pos.y.abs() -} diff --git a/src/pathfinding/mod.rs b/src/pathfinding/mod.rs deleted file mode 100644 index 2864822..0000000 --- a/src/pathfinding/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod maze; -mod node; diff --git a/src/pathfinding/node.rs b/src/pathfinding/node.rs deleted file mode 100644 index a27f7e1..0000000 --- a/src/pathfinding/node.rs +++ /dev/null @@ -1,20 +0,0 @@ -use hexx::Hex; - -#[derive(Clone, Copy, PartialEq, Eq)] -pub struct Node { - pub position: Hex, - pub f_score: i32, - pub g_score: i32, -} - -impl Ord for Node { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.f_score.cmp(&other.f_score) - } -} - -impl PartialOrd for Node { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} diff --git a/tests/pathfinding.rs b/tests/pathfinding.rs new file mode 100644 index 0000000..fd4d834 --- /dev/null +++ b/tests/pathfinding.rs @@ -0,0 +1,16 @@ +use claims::*; +use hexlab::MazeBuilder; +use hexx::{hex, Hex}; + +#[test] +fn basic_path() { + let maze = assert_ok!(MazeBuilder::new().with_seed(12345).with_radius(5).build()); + + let start = Hex::new(0, 0); + let goal = Hex::new(2, 0); + + assert_some_eq!( + maze.find_path(start, goal), + vec![start, hex(1, 0), hex(1, 1), hex(2, 1), goal] + ); +}