use spacewalk::{Adjacency, CellMap, Cost, FullGrid, Grid, Idx, Movement, Sq, Step, SubGrid};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Terrain {
Open,
Rough,
Wall,
}
fn field() -> (FullGrid<Sq>, CellMap<Terrain>) {
let g = FullGrid::square(12, 10, Adjacency::Four);
let terrain = CellMap::from_fn(&g, |c: Sq| match c {
Sq { x: 6, y: 5 } => Terrain::Open, Sq { x: 6, .. } => Terrain::Wall,
Sq { y: 2..=3, .. } => Terrain::Rough,
_ => Terrain::Open,
});
(g, terrain)
}
fn march<'a, B: Grid<Cell = Sq>>(
b: &'a B,
terrain: &'a CellMap<Terrain>,
) -> Movement<impl Fn(Step<Sq>) -> Option<Cost> + 'a> {
Movement::scan(b, move |s: Step<Sq>| match terrain[b.to_root(s.to)] {
Terrain::Open => Some(10),
Terrain::Rough => Some(30),
Terrain::Wall => None,
})
}
#[test]
fn a_movement_range_is_a_board_a_route_cannot_leave() {
let (g, terrain) = field();
let unit = g.at(Sq::new(2, 5));
let budget = 40;
let reach = g.reachable(unit, budget, &march(&g, &terrain));
let range: SubGrid<Sq> = g.subset(reach.iter().map(|&(i, _)| i));
assert!(
range.len() > 1 && range.len() < g.len(),
"some of the board"
);
let from = range.at(Sq::new(2, 5));
let far = range
.indices()
.max_by_key(|&i| range.distance(from, i))
.unwrap();
let route = range.path(from, far, &march(&range, &terrain)).unwrap();
assert!(
route.cost() <= budget,
"the shading promised this was affordable"
);
for &i in route.steps() {
assert!(
reach.iter().any(|&(r, _)| r == range.to_root(i)),
"{:?} was walked through but never shaded",
range.coord(i),
);
}
}
#[test]
fn a_region_reads_the_games_own_data_through_the_root() {
let (g, terrain) = field();
let centre = g.at(Sq::new(6, 4));
let blast = g.within(centre, 0, 1);
assert_eq!(
terrain.len(),
g.len(),
"the map is sized by the whole board"
);
assert!(blast.len() < terrain.len());
let walls = blast
.indices()
.filter(|&i| terrain[blast.to_root(i)] == Terrain::Wall)
.count();
assert_eq!(walls, 2, "the wall above the door, and the one below it");
assert_eq!(blast.of_root(centre), blast.index_of(Sq::new(6, 4)));
assert_eq!(blast.of_root(g.at(Sq::new(0, 0))), None);
}
#[test]
fn an_area_of_effect_is_split_by_a_wall_that_runs_through_it() {
let (g, terrain) = field();
let centre = g.at(Sq::new(6, 1));
let blast = g.within(centre, 0, 2);
let open = |i: Idx| terrain[blast.to_root(i)] != Terrain::Wall;
assert!(!blast.is_connected(open), "the wall runs through the blast");
let west = blast.component(blast.at(Sq::new(4, 1)), open);
let east = blast.component(blast.at(Sq::new(8, 1)), open);
assert!(
west.cells().all(|c| !east.contains(c)),
"two separate areas"
);
}
#[test]
fn a_region_of_a_region_still_maps_back_to_the_board_in_one_hop() {
let (g, terrain) = field();
let eye = g.at(Sq::new(2, 5));
let seen = g.visible_from(eye, 4, |i| terrain[i] == Terrain::Wall);
let near = seen.subset(
seen.indices()
.filter(|&i| seen.distance(seen.at(Sq::new(2, 5)), i) <= 2),
);
assert!(near.len() < seen.len());
for i in near.indices() {
assert_eq!(near.coord(i), g.coord(near.to_root(i)));
}
}
#[test]
fn sight_stops_at_the_wall_but_range_does_not() {
let (g, terrain) = field();
let eye = g.at(Sq::new(4, 5));
let blocked = |i: Idx| terrain[i] == Terrain::Wall;
let range = g.within(eye, 0, 4);
let sight = g.visible_from(eye, 4, blocked);
assert!(range.contains(Sq::new(4, 1)), "four north, over the rough");
assert!(sight.contains(Sq::new(4, 1)), "and nothing is in the way");
assert!(range.contains(Sq::new(7, 4)), "past the wall, but in range");
assert!(!sight.contains(Sq::new(7, 4)), "and the wall hides it");
let (a, b) = (range.at(Sq::new(4, 5)), range.at(Sq::new(4, 1)));
assert_eq!(range.distance(a, b), g.distance(eye, g.at(Sq::new(4, 1))));
}