feat(pathfinding): add A*

This commit is contained in:
Kristofers Solo 2025-01-14 11:35:20 +02:00
parent fae8e91b54
commit 6cd7550086
6 changed files with 167 additions and 5 deletions

6
Cargo.lock generated
View File

@ -2338,7 +2338,7 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
[[package]] [[package]]
name = "hexlab" name = "hexlab"
version = "0.5.3" version = "0.6.0"
dependencies = [ dependencies = [
"bevy", "bevy",
"bevy_reflect", "bevy_reflect",
@ -2354,9 +2354,9 @@ dependencies = [
[[package]] [[package]]
name = "hexx" name = "hexx"
version = "0.19.0" version = "0.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34b47b6f7ee46bba869534a92306b7e7f549bec38114a4ba0288b9321617db22" checksum = "4b450e02a24a4a981c895be4cd2752e2401996c545971309730c4e812b984691"
dependencies = [ dependencies = [
"bevy_reflect", "bevy_reflect",
"glam", "glam",

View File

@ -1,7 +1,7 @@
[package] [package]
name = "hexlab" name = "hexlab"
authors = ["Kristofers Solo <dev@kristofers.xyz>"] authors = ["Kristofers Solo <dev@kristofers.xyz>"]
version = "0.5.3" version = "0.6.0"
edition = "2021" edition = "2021"
description = "A hexagonal maze generation and manipulation library" description = "A hexagonal maze generation and manipulation library"
repository = "https://github.com/kristoferssolo/hexlab" repository = "https://github.com/kristoferssolo/hexlab"
@ -48,7 +48,8 @@ bevy_reflect = [
"hexx/bevy_reflect", "hexx/bevy_reflect",
"dep:glam", "dep:glam",
] ]
full = ["serde", "bevy"] pathfinding = []
full = ["serde", "bevy", "pathfinding"]
[profile.dev] [profile.dev]
opt-level = 1 # Better compile times with some optimization opt-level = 1 # Better compile times with some optimization

View File

@ -52,6 +52,8 @@ mod builder;
pub mod errors; pub mod errors;
mod generator; mod generator;
mod maze; mod maze;
#[cfg(feature = "pathfinding")]
mod pathfinding;
mod tile; mod tile;
pub mod traits; pub mod traits;
mod walls; mod walls;

137
src/pathfinding/maze.rs Normal file
View File

@ -0,0 +1,137 @@
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(&current.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(&current.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(&current.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(&current_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()
}

2
src/pathfinding/mod.rs Normal file
View File

@ -0,0 +1,2 @@
mod maze;
mod node;

20
src/pathfinding/node.rs Normal file
View File

@ -0,0 +1,20 @@
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))
}
}