use spacewalk::{Adjacency, FullGrid, Graph, GraphError, Grid, Movement, Sq};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum Room {
Hall,
Kitchen,
Cellar,
Vault,
Attic,
}
use Room::*;
fn house() -> Graph<Room> {
Graph::new([
(Hall, Kitchen, 4),
(Hall, Cellar, 1),
(Cellar, Kitchen, 2),
(Kitchen, Vault, 5),
])
}
#[test]
fn a_route_is_found_by_the_names_the_caller_gave() {
let g = house();
let route = g.path(g.at(Hall), g.at(Vault)).unwrap();
let rooms: Vec<Room> = g.keys_of(route.steps().iter().copied()).collect();
assert_eq!(
rooms,
[Hall, Cellar, Kitchen, Vault],
"the cellar stairs beat the direct door"
);
assert_eq!(route.cost(), 8);
assert!(
g.path(g.at(Vault), g.at(Hall)).is_none(),
"the edges are directed"
);
}
#[test]
fn reach_runs_forward_and_backward() {
let g = house();
let named = |found: Vec<(spacewalk::Idx, u32)>| -> Vec<(Room, u32)> {
found.into_iter().map(|(i, c)| (g.key(i), c)).collect()
};
assert_eq!(
named(g.reachable(g.at(Hall), 3)),
[(Hall, 0), (Cellar, 1), (Kitchen, 3)],
"cheapest first, and the budget stops it short of the vault"
);
assert_eq!(
named(g.reaching(g.at(Vault), 100)),
[(Vault, 0), (Kitchen, 5), (Cellar, 7), (Hall, 8)],
"who can get to the vault, and for how much"
);
}
#[test]
fn nodes_are_numbered_in_the_order_the_edges_name_them() {
let g = Graph::new([(Hall, Kitchen, 1), (Attic, Attic, 0), (Kitchen, Cellar, 1)]);
assert_eq!(g.keys().collect::<Vec<_>>(), [Hall, Kitchen, Attic, Cellar]);
assert_eq!(g.len(), 4);
assert_eq!(g.index_of(Vault), None, "no edge names the vault");
let attic = g.at(Attic);
assert_eq!(
g.reachable(attic, 100),
[(attic, 0)],
"the attic leads nowhere"
);
assert_eq!(g.reaching(attic, 100), [(attic, 0)]);
}
#[test]
fn the_cheaper_of_two_doors_between_the_same_rooms_wins() {
let g = Graph::new([(Hall, Kitchen, 9), (Hall, Kitchen, 3)]);
assert_eq!(g.path(g.at(Hall), g.at(Kitchen)).unwrap().cost(), 3);
}
#[test]
fn a_cost_that_could_overflow_a_path_total_is_refused() {
let dear = u32::MAX / 2 + 1;
let built = Graph::try_new([(Hall, Kitchen, dear), (Kitchen, Vault, 1)]);
assert_eq!(
built.unwrap_err(),
GraphError::CostTooHigh {
cost: dear,
ceiling: u32::MAX / 2,
nodes: 3
}
);
}
#[test]
fn a_graph_and_a_grid_agree_about_the_same_map() {
let grid = FullGrid::square(7, 7, Adjacency::Eight);
let blocked = |c: Sq| c.x == 3 && c.y != 5;
let price = |to: Sq, diagonal: bool| {
(!blocked(to)).then_some(if to.y % 2 == 0 {
30
} else if diagonal {
14
} else {
10
})
};
let walk = Movement::scan(&grid, |s| price(grid.coord(s.to), s.dir.is_diagonal()));
let mut edges = Vec::new();
for i in grid.indices() {
for (d, j) in grid.neighbors(i) {
if let Some(cost) = price(grid.coord(j), d.is_diagonal()) {
edges.push((grid.coord(i), grid.coord(j), cost));
}
}
}
let graph = Graph::new(edges);
let (a, b) = (Sq::new(0, 0), Sq::new(6, 0));
assert_eq!(
graph.path(graph.at(a), graph.at(b)).unwrap().cost(),
grid.path(grid.at(a), grid.at(b), &walk).unwrap().cost(),
);
let mut by_graph: Vec<(Sq, u32)> = graph
.reaching(graph.at(b), 60)
.into_iter()
.map(|(i, c)| (graph.key(i), c))
.collect();
let mut by_grid: Vec<(Sq, u32)> = grid
.reaching(grid.at(b), 60, &walk)
.into_iter()
.map(|(i, c)| (grid.coord(i), c))
.collect();
by_graph.sort_unstable();
by_grid.sort_unstable();
assert_eq!(by_graph, by_grid);
assert!(by_grid.len() > 1, "the search went somewhere");
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "issued by a different graph")]
fn an_index_from_another_graph_is_refused() {
let here = Graph::new([(Hall, Kitchen, 1)]);
let there = Graph::new([(Vault, Cellar, 1)]);
let _ = there.key(here.at(Hall));
}