use crate::hex_coord::HexCoord;
use crate::wall6_grid::Wall6Grid;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct WeightedHexAdjacencyList {
pub width: usize,
pub height: usize,
pub neighbors: Vec<Vec<(HexCoord, f32)>>,
}
impl WeightedHexAdjacencyList {
pub fn from_wall_grid_with<F>(value: &Wall6Grid, mut weight_fn: F) -> Self
where
F: FnMut(HexCoord, HexCoord) -> f32,
{
let mut neighbors = vec![Vec::with_capacity(6); value.width() * value.height()];
for from in value.coords() {
let idx = (from.r as usize) * value.width() + (from.q as usize);
let weighted_neighbors = value
.open_neighbors(from)
.map(|to| (to, weight_fn(from, to)))
.collect();
neighbors[idx] = weighted_neighbors;
}
Self {
width: value.width(),
height: value.height(),
neighbors,
}
}
pub fn neighbors(&self, coord: HexCoord) -> &[(HexCoord, f32)] {
&self.neighbors[self.linearize_coord(coord)]
}
pub fn get_neighbors(&self, coord: HexCoord) -> Option<&[(HexCoord, f32)]> {
if coord.q < 0
|| coord.q >= self.width as isize
|| coord.r < 0
|| coord.r >= self.height as isize
{
return None;
}
Some(self.neighbors(coord))
}
#[inline]
fn linearize_coord(&self, coord: HexCoord) -> usize {
(coord.r as usize) * self.width + (coord.q as usize)
}
}
impl From<&Wall6Grid> for WeightedHexAdjacencyList {
fn from(value: &Wall6Grid) -> Self {
Self::from_wall_grid_with(value, |_, _| 1.0)
}
}
#[cfg(all(test, feature = "generator-hex-recursive-backtracker"))]
mod tests {
use super::*;
use crate::generators::RecursiveBacktracker6;
use crate::representations::HexAdjacencyList;
fn make_hex_maze() -> Wall6Grid {
RecursiveBacktracker6::new_from_seed(42).generate(5, 5)
}
#[test]
fn default_weights_are_one() {
let list = WeightedHexAdjacencyList::from(&make_hex_maze());
assert!(
list.neighbors
.iter()
.all(|neighbors| neighbors.iter().all(|(_, weight)| *weight == 1.0))
);
}
#[test]
fn custom_weight_fn_applied() {
let maze = make_hex_maze();
let list = WeightedHexAdjacencyList::from_wall_grid_with(&maze, |from, to| {
(from.q + from.r + to.q + to.r) as f32 + 0.5
});
for from in maze.coords() {
for (to, weight) in list.neighbors(from) {
assert_eq!(*weight, (from.q + from.r + to.q + to.r) as f32 + 0.5);
}
}
}
#[test]
fn get_neighbors_returns_none_out_of_bounds() {
let maze = make_hex_maze();
let list = WeightedHexAdjacencyList::from(&maze);
assert!(
list.get_neighbors(HexCoord::new(list.width as isize, 0))
.is_none()
);
assert!(
list.get_neighbors(HexCoord::new(0, list.height as isize))
.is_none()
);
}
#[test]
fn neighbor_count_matches_unweighted() {
let maze = make_hex_maze();
let weighted = WeightedHexAdjacencyList::from(&maze);
let unweighted = HexAdjacencyList::from(&maze);
for coord in maze.coords() {
assert_eq!(
weighted.neighbors(coord).len(),
unweighted.neighbors(coord).len()
);
}
}
}