use crate::coord::{Coord, Dir8, Idx, Metric, Sq, Tag};
use crate::full::{Adjacency, GridError, MAX_CELLS};
use crate::grid::{Grid, same_grid, slot};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RectGrid {
w: i32,
h: i32,
adj: Adjacency,
tag: Tag,
}
impl RectGrid {
#[must_use]
pub fn new(w: i32, h: i32, adj: Adjacency) -> Self {
Self::try_new(w, h, adj).unwrap_or_else(|error| panic!("{error}"))
}
pub fn try_new(w: i32, h: i32, adj: Adjacency) -> Result<Self, GridError> {
if w < 0 || h < 0 {
return Err(GridError::InvalidDimensions { w, h });
}
let cells_count = w as u64 * h as u64;
if cells_count > MAX_CELLS {
return Err(GridError::TooManyCells { cells: cells_count });
}
Ok(Self {
w,
h,
adj,
tag: Tag::of((0..h).flat_map(move |y| (0..w).map(move |x| Sq::new(x, y)))),
})
}
const fn idx(&self, i: u32) -> Idx {
Idx::new(self.tag, i)
}
}
impl Grid for RectGrid {
type Cell = Sq;
type Root = Self;
fn tag(&self) -> Tag {
self.tag
}
fn len(&self) -> usize {
self.w as usize * self.h as usize
}
fn coord(&self, i: Idx) -> Sq {
let cell = slot(self.len(), self.tag, i) as i32;
Sq::new(cell % self.w, cell / self.w)
}
fn index_of(&self, c: Sq) -> Option<Idx> {
let on = (0..self.w).contains(&c.x) && (0..self.h).contains(&c.y);
on.then(|| self.idx((c.y * self.w + c.x) as u32))
}
fn dirs(&self) -> &[Dir8] {
match self.adj {
Adjacency::Four => &Dir8::ORTHO,
Adjacency::Eight => &Dir8::ALL,
}
}
fn step(&self, i: Idx, d: Dir8) -> Option<Idx> {
let _ = slot(self.len(), self.tag, i);
if !self.dirs().contains(&d) {
return None;
}
self.index_of(self.coord(i).step(d))
}
fn neighbors(&self, i: Idx) -> impl Iterator<Item = (Dir8, Idx)> {
let c = self.coord(i);
self.dirs()
.iter()
.filter_map(move |&d| Some((d, self.index_of(c.step(d))?)))
}
fn in_neighbors(&self, j: Idx) -> impl Iterator<Item = (Dir8, Idx)> {
let c = self.coord(j);
self.dirs()
.iter()
.filter_map(move |&d| Some((d, self.index_of(c.step(d.opposite()))?)))
}
fn metric(&self) -> Metric<Sq> {
match self.adj {
Adjacency::Four => Metric::MANHATTAN,
Adjacency::Eight => Metric::CHEBYSHEV,
}
}
fn root(&self) -> &Self {
self
}
fn to_root(&self, i: Idx) -> Idx {
let _ = slot(self.len(), self.tag, i);
i
}
fn of_root(&self, i: Idx) -> Option<Idx> {
same_grid(self.tag, i);
((i.raw() as usize) < self.len()).then_some(i)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::full::FullGrid;
use crate::path::{Cost, Movement};
use alloc::vec::Vec;
fn back<B: Grid<Cell = Sq>>(b: &B, j: Idx) -> Vec<(Dir8, Idx)> {
let mut all: Vec<(Dir8, Idx)> = b.in_neighbors(j).collect();
all.sort_unstable_by_key(|&(d, i)| (i, d as u8));
all
}
#[test]
fn a_computed_rectangle_answers_exactly_as_a_stored_one_does() {
for adj in [Adjacency::Four, Adjacency::Eight] {
let (stored, rect) = (FullGrid::square(5, 3, adj), RectGrid::new(5, 3, adj));
assert_eq!(stored.len(), rect.len(), "{adj:?}");
assert_eq!(stored.dirs(), rect.dirs(), "{adj:?}");
for i in stored.indices() {
let c = stored.coord(i);
assert_eq!(rect.coord(i), c, "{adj:?} {i}");
assert_eq!(rect.index_of(c), Some(i), "{adj:?} {c:?}");
for &d in Dir8::ALL.iter() {
assert_eq!(stored.step(i, d), rect.step(i, d), "{adj:?} {c:?} {d:?}");
}
assert_eq!(back(&stored, i), back(&rect, i), "{adj:?} {c:?}");
}
for c in [Sq::new(-1, 0), Sq::new(0, -1), Sq::new(5, 2), Sq::new(4, 3)] {
assert_eq!(rect.index_of(c), None, "{adj:?} {c:?}");
}
let wall = Sq::new(2, 1);
let cost = |c: Sq| (c != wall).then_some(10 as Cost);
let (from, to) = (Sq::new(0, 1), Sq::new(4, 1));
let a = stored
.path(
stored.at(from),
stored.at(to),
&Movement::scan(&stored, |s| cost(stored.coord(s.to))),
)
.unwrap();
let b = rect
.path(
rect.at(from),
rect.at(to),
&Movement::scan(&rect, |s| cost(rect.coord(s.to))),
)
.unwrap();
assert_eq!((a.steps(), a.cost()), (b.steps(), b.cost()), "{adj:?}");
}
}
#[test]
fn fallible_constructor_reports_invalid_requests() {
assert!(matches!(
RectGrid::try_new(-1, 2, Adjacency::Four),
Err(GridError::InvalidDimensions { w: -1, h: 2 })
));
assert!(matches!(
RectGrid::try_new(4097, 4097, Adjacency::Four),
Err(GridError::TooManyCells { cells: 16_785_409 })
));
}
}