mirror of
https://github.com/kristoferssolo/hexlab.git
synced 2025-10-21 19:40:34 +00:00
Merge pull request #4 from kristoferssolo/feature/pathfinding
This commit is contained in:
commit
cafcb3545f
60
Cargo.lock
generated
60
Cargo.lock
generated
@ -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"
|
||||
@ -2338,7 +2350,7 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
|
||||
|
||||
[[package]]
|
||||
name = "hexlab"
|
||||
version = "0.5.3"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"bevy",
|
||||
"bevy_reflect",
|
||||
@ -2346,6 +2358,7 @@ dependencies = [
|
||||
"claims",
|
||||
"glam",
|
||||
"hexx",
|
||||
"pathfinding",
|
||||
"rand",
|
||||
"rstest",
|
||||
"serde",
|
||||
@ -2354,9 +2367,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hexx"
|
||||
version = "0.19.0"
|
||||
version = "0.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34b47b6f7ee46bba869534a92306b7e7f549bec38114a4ba0288b9321617db22"
|
||||
checksum = "4b450e02a24a4a981c895be4cd2752e2401996c545971309730c4e812b984691"
|
||||
dependencies = [
|
||||
"bevy_reflect",
|
||||
"glam",
|
||||
@ -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",
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "hexlab"
|
||||
authors = ["Kristofers Solo <dev@kristofers.xyz>"]
|
||||
version = "0.5.3"
|
||||
version = "0.6.0"
|
||||
edition = "2021"
|
||||
description = "A hexagonal maze generation and manipulation library"
|
||||
repository = "https://github.com/kristoferssolo/hexlab"
|
||||
@ -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,8 @@ bevy_reflect = [
|
||||
"hexx/bevy_reflect",
|
||||
"dep:glam",
|
||||
]
|
||||
full = ["serde", "bevy"]
|
||||
pathfinding = ["dep:pathfinding"]
|
||||
full = ["serde", "bevy", "pathfinding"]
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1 # Better compile times with some optimization
|
||||
|
||||
@ -52,6 +52,8 @@ mod builder;
|
||||
pub mod errors;
|
||||
mod generator;
|
||||
mod maze;
|
||||
#[cfg(feature = "pathfinding")]
|
||||
mod pathfinding;
|
||||
mod tile;
|
||||
pub mod traits;
|
||||
mod walls;
|
||||
|
||||
33
src/pathfinding.rs
Normal file
33
src/pathfinding.rs
Normal file
@ -0,0 +1,33 @@
|
||||
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| {
|
||||
// Manhatan distance
|
||||
let diff = *pos - to;
|
||||
(diff.x.abs() + diff.y.abs() + diff.z().abs()) as u32 / 2
|
||||
};
|
||||
|
||||
astar(&from, successors, heuristic, |pos| *pos == to).map(|(path, _)| path)
|
||||
}
|
||||
}
|
||||
79
tests/pathfinding.rs
Normal file
79
tests/pathfinding.rs
Normal file
@ -0,0 +1,79 @@
|
||||
use claims::*;
|
||||
use hexlab::MazeBuilder;
|
||||
use hexx::{hex, EdgeDirection, 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]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_with_walls() {
|
||||
let mut maze = assert_ok!(MazeBuilder::new().with_seed(12345).with_radius(5).build());
|
||||
let start = Hex::new(0, 0);
|
||||
let goal = Hex::new(2, 0);
|
||||
|
||||
// Block direct path with wall
|
||||
assert_ok!(maze.add_tile_wall(&start, EdgeDirection::FLAT_SOUTH));
|
||||
|
||||
// Should find alternative path or no path
|
||||
let path = maze.find_path(start, goal);
|
||||
if let Some(path) = path {
|
||||
// If path exists, verify it's valid
|
||||
assert!(path.len() > 3); // Should be longer than direct path
|
||||
assert_eq!(path.first(), Some(&start));
|
||||
assert_eq!(path.last(), Some(&goal));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_to_self() {
|
||||
let maze = assert_ok!(MazeBuilder::new().with_seed(12345).with_radius(5).build());
|
||||
let pos = Hex::new(0, 0);
|
||||
|
||||
assert_some_eq!(maze.find_path(pos, pos), vec![pos]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_path_exists() {
|
||||
let mut maze = assert_ok!(MazeBuilder::new().with_seed(12345).with_radius(5).build());
|
||||
let start = Hex::new(0, 0);
|
||||
let goal = Hex::new(2, 0);
|
||||
|
||||
// Surround start with walls
|
||||
for dir in EdgeDirection::ALL_DIRECTIONS {
|
||||
assert_ok!(maze.add_tile_wall(&start, dir));
|
||||
}
|
||||
|
||||
assert_none!(maze.find_path(start, goal));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_in_larger_maze() {
|
||||
let maze = assert_ok!(MazeBuilder::new().with_seed(12345).with_radius(10).build());
|
||||
let start = Hex::new(-5, -5);
|
||||
let goal = Hex::new(5, 5);
|
||||
|
||||
let path = assert_some!(maze.find_path(start, goal));
|
||||
|
||||
// Basic path properties
|
||||
assert_eq!(path.first(), Some(&start));
|
||||
assert_eq!(path.last(), Some(&goal));
|
||||
|
||||
// Path should be continuous
|
||||
for window in path.windows(2) {
|
||||
let current = window[0];
|
||||
let next = window[1];
|
||||
assert!(EdgeDirection::ALL_DIRECTIONS
|
||||
.iter()
|
||||
.any(|&dir| current.neighbor(dir) == next));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user