use crate::coord::{Coord, Idx};
use crate::grid::{Grid, Sight};
use crate::path::Step;
pub fn height_gate<'a, B: Grid + ?Sized>(
g: &'a B,
top: impl Fn(Idx) -> i32 + 'a,
looks_from: impl Fn(Idx) -> i32 + 'a,
) -> impl Fn(Sight) -> bool + 'a {
move |s| {
let eye = i128::from(looks_from(s.eye));
let rise = i128::from(top(s.at)) - eye;
let fall = i128::from(looks_from(s.target)) - eye;
rise * i128::from(g.distance(s.eye, s.target)) > fall * i128::from(g.distance(s.eye, s.at))
}
}
pub fn climb_gate<'a, C: Coord>(
z: impl Fn(Idx) -> i32 + 'a,
max_rise: i32,
) -> impl Fn(Step<C>) -> bool + 'a {
move |s| i64::from(z(s.to)) - i64::from(z(s.from)) <= i64::from(max_rise)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cells::CellMap;
use crate::coord::Sq;
use crate::full::{Adjacency, FullGrid};
use crate::path::{Cost, Movement};
use alloc::vec::Vec;
fn ridge(hill: i32) -> (FullGrid<Sq>, CellMap<i32>) {
let g = FullGrid::square(9, 3, Adjacency::Eight);
let mut ground = CellMap::new(&g, 0i32);
ground[g.at(Sq::new(4, 1))] = hill;
(g, ground)
}
#[test]
fn a_hill_hides_what_is_behind_it() {
let (g, ground) = ridge(5);
let sight = height_gate(&g, |i| ground[i], |i| ground[i] + 1);
let eye = g.at(Sq::new(0, 1));
assert!(
!g.los_by(eye, g.at(Sq::new(8, 1)), &sight),
"across the hill"
);
assert!(g.los_by(eye, g.at(Sq::new(3, 1)), &sight), "short of it");
}
#[test]
fn enough_height_sees_over_the_hill() {
let (g, ground) = ridge(5);
let eye = g.at(Sq::new(0, 1));
let raised = |i: Idx| ground[i] + if i == eye { 10 } else { 1 };
let sight = height_gate(&g, |i| ground[i], raised);
assert!(g.los_by(eye, g.at(Sq::new(8, 1)), &sight));
}
#[test]
fn you_can_always_see_the_hilltop_you_are_looking_at() {
let (g, ground) = ridge(500);
let sight = height_gate(&g, |i| ground[i], |i| ground[i] + 1);
let eye = g.at(Sq::new(0, 1));
assert!(g.los_by(eye, g.at(Sq::new(4, 1)), &sight));
}
#[test]
fn sight_over_a_height_field_is_symmetric() {
let g = FullGrid::square(11, 11, Adjacency::Eight);
let ground = CellMap::from_fn(&g, |c: Sq| (c.x * 7 + c.y * 13) % 9);
let sight = height_gate(&g, |i| ground[i], |i| ground[i] + 2);
for a in g.indices() {
for b in g.indices() {
assert_eq!(
g.los_by(a, b, &sight),
g.los_by(b, a, &sight),
"{:?} <-> {:?}",
g.coord(a),
g.coord(b)
);
}
}
}
#[test]
fn a_climb_gate_measures_the_step_and_not_the_height() {
let g = FullGrid::square(5, 1, Adjacency::Four);
let stairs = CellMap::from_fn(&g, |c: Sq| c.x);
let climb = climb_gate(|i| stairs[i], 1);
let walk = Movement::scan(&g, |s| climb(s).then_some(10 as Cost));
let (bottom, top) = (g.at(Sq::new(0, 0)), g.at(Sq::new(4, 0)));
assert_eq!(g.path(bottom, top, &walk).map(|p| p.len()), Some(4));
let cliff: Vec<i32> = (0..5).map(|x| if x == 4 { 4 } else { 0 }).collect();
let steep = climb_gate(|i: Idx| cliff[i.raw() as usize], 1);
let hard = Movement::scan(&g, |s| steep(s).then_some(10 as Cost));
assert!(g.path(bottom, top, &hard).is_none());
}
}