use spacewalk::height::{climb_gate, height_gate};
use spacewalk::{Adjacency, CellMap, Cost, FullGrid, Grid, Hex, Idx, Movement, Sq};
mod common;
fn valley() -> (FullGrid<Sq>, CellMap<i32>) {
let g = FullGrid::square(9, 4, Adjacency::Eight);
let ground = CellMap::from_fn(&g, |c: Sq| i32::from(c.x == 4 && c.y != 2) * 4);
(g, ground)
}
fn eyes<'a>(
g: &'a FullGrid<Sq>,
ground: &'a CellMap<i32>,
) -> impl Fn(spacewalk::Sight) -> bool + 'a {
height_gate(g, |i| ground[i], |i| ground[i] + 2)
}
#[test]
fn a_ridge_casts_dead_ground_behind_it() {
let (g, ground) = valley();
let sight = eyes(&g, &ground);
let scout = g.at(Sq::new(0, 1));
assert!(
!g.los_by(scout, g.at(Sq::new(8, 1)), &sight),
"the far side of the ridge is dead ground"
);
assert!(
g.los_by(scout, g.at(Sq::new(8, 2)), &sight),
"but the pass is open, and you can see clean through it"
);
}
#[test]
fn the_high_ground_sees_what_the_valley_floor_cannot() {
let (g, ground) = valley();
let sight = eyes(&g, &ground);
let target = g.at(Sq::new(8, 1));
let on_the_floor = g.at(Sq::new(0, 1));
let on_the_ridge = g.at(Sq::new(4, 1));
assert!(!g.los_by(on_the_floor, target, &sight));
assert!(
g.los_by(on_the_ridge, target, &sight),
"from up here, plainly"
);
}
#[test]
fn a_field_of_view_over_a_ridge_is_still_a_board() {
let (g, ground) = valley();
let sight = eyes(&g, &ground);
let archer = g.at(Sq::new(0, 1));
let seen = g.visible_from_by(archer, 8, &sight);
assert!(seen.contains(Sq::new(4, 1)), "the ridge itself is visible");
assert!(!seen.contains(Sq::new(8, 1)), "what it hides is not");
assert!(seen.contains(Sq::new(8, 2)), "and the pass is");
let walk = common::open(&g);
let inside = seen.path(seen.at(Sq::new(0, 1)), seen.at(Sq::new(8, 2)), &walk);
assert!(
inside.is_some(),
"the pass is walkable without leaving sight"
);
for i in seen.indices() {
assert!(g.contains(g.coord(seen.to_root(i))));
}
}
#[test]
fn a_cliff_turns_a_straight_march_into_a_detour() {
let (g, ground) = valley();
let climb = climb_gate(|i: Idx| ground[i], 2);
let walk = Movement::scan(&g, |s| climb(s).then_some(10 as Cost));
let west = g.at(Sq::new(0, 0));
let east = g.at(Sq::new(8, 0));
let route = g.path(west, east, &walk).expect("the pass is open");
let through: Vec<Sq> = route.cells(&g).collect();
assert!(
through.contains(&Sq::new(4, 2)),
"it must use the pass: {through:?}"
);
assert!(
!through.iter().any(|c| c.x == 4 && c.y != 2),
"and never sets foot on the ridge"
);
}
#[test]
fn you_may_drop_off_the_ridge_you_could_not_climb() {
let (g, ground) = valley();
let climb = climb_gate(|i: Idx| ground[i], 2);
let walk = Movement::scan(&g, |s| climb(s).then_some(10 as Cost));
let ridge = g.at(Sq::new(4, 1));
let floor = g.at(Sq::new(4, 2));
assert_eq!(g.path(ridge, floor, &walk).unwrap().len(), 1);
assert!(g.path(floor, ridge, &walk).is_none());
assert!(
!g.reachable(floor, 10_000, &walk)
.iter()
.any(|&(i, _)| i == ridge)
);
assert!(
g.reaching(floor, 10_000, &walk)
.iter()
.any(|&(i, _)| i == ridge)
);
}
#[test]
fn sight_over_a_height_field_is_symmetric_on_hexes_too() {
let g = FullGrid::hexagon(4);
let ground = CellMap::from_fn(&g, |c: Hex| (c.q * 5 + c.r * 11).rem_euclid(7));
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_flat_height_field_answers_exactly_as_no_height_field_at_all() {
let g = FullGrid::square(11, 11, Adjacency::Eight);
let flat = CellMap::new(&g, 0i32);
let sight = height_gate(&g, |i| flat[i], |i| flat[i] + 2);
for a in g.indices() {
for b in g.indices() {
assert!(
g.los_by(a, b, &sight),
"nothing on a flat board blocks sight"
);
assert_eq!(g.los_by(a, b, &sight), g.los(a, b, |_| false));
}
}
}