mirror of
https://github.com/kristoferssolo/hexlab.git
synced 2025-10-21 19:40:34 +00:00
feat(pathfinding): use pathfinding crate
This commit is contained in:
parent
6cd7550086
commit
4eab4d1198
54
Cargo.lock
generated
54
Cargo.lock
generated
@ -1248,7 +1248,7 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"regex",
|
"regex",
|
||||||
"rustc-hash",
|
"rustc-hash 1.1.0",
|
||||||
"shlex",
|
"shlex",
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
@ -1631,7 +1631,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"rangemap",
|
"rangemap",
|
||||||
"rayon",
|
"rayon",
|
||||||
"rustc-hash",
|
"rustc-hash 1.1.0",
|
||||||
"rustybuzz",
|
"rustybuzz",
|
||||||
"self_cell",
|
"self_cell",
|
||||||
"swash",
|
"swash",
|
||||||
@ -1743,6 +1743,18 @@ version = "2.6.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2"
|
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]]
|
[[package]]
|
||||||
name = "derive_more"
|
name = "derive_more"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@ -2346,6 +2358,7 @@ dependencies = [
|
|||||||
"claims",
|
"claims",
|
||||||
"glam",
|
"glam",
|
||||||
"hexx",
|
"hexx",
|
||||||
|
"pathfinding",
|
||||||
"rand",
|
"rand",
|
||||||
"rstest",
|
"rstest",
|
||||||
"serde",
|
"serde",
|
||||||
@ -2420,6 +2433,15 @@ dependencies = [
|
|||||||
"libc",
|
"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]]
|
[[package]]
|
||||||
name = "io-kit-sys"
|
name = "io-kit-sys"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
@ -2687,7 +2709,7 @@ dependencies = [
|
|||||||
"indexmap",
|
"indexmap",
|
||||||
"log",
|
"log",
|
||||||
"pp-rs",
|
"pp-rs",
|
||||||
"rustc-hash",
|
"rustc-hash 1.1.0",
|
||||||
"spirv",
|
"spirv",
|
||||||
"termcolor",
|
"termcolor",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
@ -2708,7 +2730,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"regex",
|
"regex",
|
||||||
"regex-syntax 0.8.5",
|
"regex-syntax 0.8.5",
|
||||||
"rustc-hash",
|
"rustc-hash 1.1.0",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
"tracing",
|
"tracing",
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
@ -3166,6 +3188,20 @@ version = "1.0.15"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
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]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.1"
|
version = "2.3.1"
|
||||||
@ -3567,6 +3603,12 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-hash"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc_version"
|
name = "rustc_version"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
@ -4283,7 +4325,7 @@ dependencies = [
|
|||||||
"parking_lot",
|
"parking_lot",
|
||||||
"profiling",
|
"profiling",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"rustc-hash",
|
"rustc-hash 1.1.0",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
"wgpu-hal",
|
"wgpu-hal",
|
||||||
@ -4325,7 +4367,7 @@ dependencies = [
|
|||||||
"range-alloc",
|
"range-alloc",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"renderdoc-sys",
|
"renderdoc-sys",
|
||||||
"rustc-hash",
|
"rustc-hash 1.1.0",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
|
|||||||
@ -26,6 +26,7 @@ thiserror = "2.0"
|
|||||||
bevy = { version = "0.15", optional = true }
|
bevy = { version = "0.15", optional = true }
|
||||||
bevy_utils = { version = "0.15", optional = true }
|
bevy_utils = { version = "0.15", optional = true }
|
||||||
glam = { version = "0.29", optional = true }
|
glam = { version = "0.29", optional = true }
|
||||||
|
pathfinding = { version = "4.13", optional = true }
|
||||||
|
|
||||||
|
|
||||||
[dependencies.bevy_reflect]
|
[dependencies.bevy_reflect]
|
||||||
@ -48,7 +49,7 @@ bevy_reflect = [
|
|||||||
"hexx/bevy_reflect",
|
"hexx/bevy_reflect",
|
||||||
"dep:glam",
|
"dep:glam",
|
||||||
]
|
]
|
||||||
pathfinding = []
|
pathfinding = ["dep:pathfinding"]
|
||||||
full = ["serde", "bevy", "pathfinding"]
|
full = ["serde", "bevy", "pathfinding"]
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
|
|||||||
32
src/pathfinding.rs
Normal file
32
src/pathfinding.rs
Normal file
@ -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<Vec<Hex>> {
|
||||||
|
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::<Vec<_>>()
|
||||||
|
};
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<Vec<Hex>> {
|
|
||||||
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<Node>,
|
|
||||||
came_from: &mut HashMap<Hex, Hex>,
|
|
||||||
g_score: &mut HashMap<Hex, i32>,
|
|
||||||
closed_set: &mut HashSet<Hex>,
|
|
||||||
) -> 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<Node>,
|
|
||||||
came_from: &mut HashMap<Hex, Hex>,
|
|
||||||
g_score: &mut HashMap<Hex, i32>,
|
|
||||||
closed_set: &HashSet<Hex>,
|
|
||||||
) {
|
|
||||||
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<Node>,
|
|
||||||
came_from: &mut HashMap<Hex, Hex>,
|
|
||||||
g_score: &mut HashMap<Hex, i32>,
|
|
||||||
) {
|
|
||||||
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<Hex, Hex>, current: Hex) -> Vec<Hex> {
|
|
||||||
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<Node>,
|
|
||||||
g_score: &mut HashMap<Hex, i32>,
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
mod maze;
|
|
||||||
mod node;
|
|
||||||
@ -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<std::cmp::Ordering> {
|
|
||||||
Some(self.cmp(other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
16
tests/pathfinding.rs
Normal file
16
tests/pathfinding.rs
Normal file
@ -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]
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user