use crate::coord::{Idx, Sq};
use crate::grid::Grid;
use crate::path::Step;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CornerRule {
#[default]
Strict,
Loose,
Free,
}
pub fn corner_gate<'a, B: Grid<Cell = Sq> + ?Sized>(
g: &'a B,
rule: CornerRule,
enterable: impl Fn(Idx) -> bool + 'a,
) -> impl Fn(Step<Sq>) -> bool + 'a {
move |s| {
let Some((a, b)) = s.dir.flanks() else {
return true; };
let open = |d| g.step(s.from, d).is_some_and(&enterable);
match rule {
CornerRule::Strict => open(a) && open(b),
CornerRule::Loose => open(a) || open(b),
CornerRule::Free => true,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::full::{Adjacency, FullGrid};
use crate::path::{Cost, Movement};
fn diagonal_through(walls: &[Sq], rule: CornerRule) -> Option<usize> {
let g = FullGrid::square(3, 3, Adjacency::Eight);
let open = |i: Idx| !walls.contains(&g.coord(i));
let gate = corner_gate(&g, rule, open);
let m = Movement::scan(&g, |s| (open(s.to) && gate(s)).then_some(10 as Cost));
let from = g.at(Sq::new(0, 0));
let to = g.at(Sq::new(1, 1));
g.path(from, to, &m).map(|p| p.len())
}
#[test]
fn with_no_walls_every_rule_takes_the_diagonal() {
for rule in [CornerRule::Strict, CornerRule::Loose, CornerRule::Free] {
assert_eq!(diagonal_through(&[], rule), Some(1), "{rule:?}");
}
}
#[test]
fn strict_corners_stop_a_unit_clipping_past_a_single_wall() {
use CornerRule::{Free, Loose, Strict};
for (rule, n) in [(Strict, 2), (Loose, 1), (Free, 1)] {
let len = diagonal_through(&[Sq::new(1, 0)], rule);
assert_eq!(len, Some(n), "{rule:?}");
}
}
#[test]
fn only_free_corners_squeeze_between_two_walls() {
let both = [Sq::new(1, 0), Sq::new(0, 1)];
assert_eq!(diagonal_through(&both, CornerRule::Strict), None);
assert_eq!(diagonal_through(&both, CornerRule::Loose), None);
assert_eq!(diagonal_through(&both, CornerRule::Free), Some(1));
}
#[test]
fn a_unit_standing_on_a_corner_blocks_it_just_as_a_wall_does() {
assert_eq!(
diagonal_through(&[Sq::new(1, 0)], CornerRule::Strict),
Some(2)
);
}
#[test]
fn orthogonal_steps_never_have_a_corner_to_cut() {
let g = FullGrid::square(3, 3, Adjacency::Four);
let gate = corner_gate(&g, CornerRule::Strict, |_| false);
let from = g.at(Sq::new(1, 1));
for (dir, to) in g.neighbors(from) {
assert!(gate(Step { from, to, dir }), "{dir:?}");
}
}
}