use crate::maze::node::Node;
pub(crate) const SCALE: u32 = 100;
pub fn manhattan_heuristic(node: Node, goal: Node) -> u32 {
let (dr, dc) = delta(node, goal);
(dr + dc) * SCALE
}
pub fn euclidean_heuristic(node: Node, goal: Node) -> u32 {
let (dr, dc) = delta(node, goal);
((dr as f64).hypot(dc as f64) * SCALE as f64) as u32
}
pub fn chebyshev_heuristic(node: Node, goal: Node) -> u32 {
let (dr, dc) = delta(node, goal);
dr.max(dc) * SCALE
}
pub fn octile_heuristic(node: Node, goal: Node) -> u32 {
let (dr, dc) = delta(node, goal);
let (major, minor) = if dr >= dc { (dr, dc) } else { (dc, dr) };
((major as f64 + (std::f64::consts::SQRT_2 - 1.0) * minor as f64) * SCALE as f64) as u32
}
pub fn hex_heuristic(node: Node, goal: Node) -> u32 {
let (q1, r1, s1) = offset_to_cube(node.row as i32, node.col as i32);
let (q2, r2, s2) = offset_to_cube(goal.row as i32, goal.col as i32);
let dist = ((q1 - q2).unsigned_abs() + (r1 - r2).unsigned_abs() + (s1 - s2).unsigned_abs()) / 2;
dist * SCALE
}
pub fn dijkstra_heuristic(_: Node, _: Node) -> u32 {
0
}
#[inline]
fn delta(a: Node, b: Node) -> (u32, u32) {
let dr = (a.row as i32 - b.row as i32).unsigned_abs();
let dc = (a.col as i32 - b.col as i32).unsigned_abs();
(dr, dc)
}
#[inline]
fn offset_to_cube(row: i32, col: i32) -> (i32, i32, i32) {
let q = col - (row - (row & 1)) / 2;
let r = row;
let s = -q - r;
(q, r, s)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::maze::NodeFactory;
fn nodes() -> (Node, Node) {
let f = NodeFactory::new(10, 10);
(f.at(1, 2).unwrap(), f.at(4, 6).unwrap())
}
#[test]
fn heuristics_return_expected_values() {
let (a, b) = nodes();
assert_eq!(manhattan_heuristic(a, b), 700);
assert_eq!(euclidean_heuristic(a, b), 500);
assert_eq!(chebyshev_heuristic(a, b), 400);
assert_eq!(octile_heuristic(a, b), 524);
assert_eq!(dijkstra_heuristic(a, b), 0);
}
#[test]
fn hex_heuristic_exact_for_adjacent_cells() {
let f = NodeFactory::new(10, 10);
let center = f.at(4, 4).unwrap();
for neighbor in [
f.at(4, 5), f.at(5, 4), f.at(5, 3), f.at(4, 3), f.at(3, 3), f.at(3, 4), ]
.into_iter()
.flatten()
{
assert_eq!(
hex_heuristic(center, neighbor),
SCALE,
"center→{:?} should be exactly SCALE={}",
neighbor,
SCALE
);
}
}
#[test]
fn all_heuristics_are_non_negative_and_zero_for_same_node() {
let f = NodeFactory::new(5, 5);
let n = f.at(2, 3).unwrap();
assert_eq!(manhattan_heuristic(n, n), 0);
assert_eq!(euclidean_heuristic(n, n), 0);
assert_eq!(chebyshev_heuristic(n, n), 0);
assert_eq!(octile_heuristic(n, n), 0);
assert_eq!(hex_heuristic(n, n), 0);
assert_eq!(dijkstra_heuristic(n, n), 0);
}
#[test]
fn heuristic_ordering_matches_expected_dominance() {
let (a, b) = nodes();
let cheb = chebyshev_heuristic(a, b);
let eucl = euclidean_heuristic(a, b);
let oct = octile_heuristic(a, b);
let manh = manhattan_heuristic(a, b);
assert!(cheb <= eucl, "chebyshev ({cheb}) should be ≤ euclidean ({eucl})");
assert!(eucl <= oct, "euclidean ({eucl}) should be ≤ octile ({oct})");
assert!(oct <= manh, "octile ({oct}) should be ≤ manhattan ({manh})");
}
}