#![cfg(feature = "serde")]
use spacewalk::{Adjacency, Dir6, Dir8, FullGrid, Grid, Hex, Sq};
#[test]
fn coordinates_round_trip() {
let cells = [Sq::new(0, 0), Sq::new(-7, 12), Sq::new(i32::MIN, i32::MAX)];
for c in cells {
let json = serde_json::to_string(&c).unwrap();
assert_eq!(serde_json::from_str::<Sq>(&json).unwrap(), c, "{json}");
}
for h in [
Hex::new(0, 0),
Hex::new(3, -5),
Hex::new(i32::MAX, i32::MIN),
] {
let json = serde_json::to_string(&h).unwrap();
assert_eq!(serde_json::from_str::<Hex>(&json).unwrap(), h, "{json}");
}
}
#[test]
fn directions_round_trip() {
for d in Dir8::ALL {
let json = serde_json::to_string(&d).unwrap();
assert_eq!(serde_json::from_str::<Dir8>(&json).unwrap(), d, "{json}");
}
for d in Dir6::ALL {
let json = serde_json::to_string(&d).unwrap();
assert_eq!(serde_json::from_str::<Dir6>(&json).unwrap(), d, "{json}");
}
}
#[test]
fn a_saved_index_from_before_a_filter_is_caught() {
let full = FullGrid::square(3, 3, Adjacency::Four);
let hero = full.at(Sq::new(1, 1));
assert_eq!(hero.get(), 4);
let after = full.filtered(|c| c != Sq::new(0, 0));
if cfg!(debug_assertions) {
let boom = std::panic::catch_unwind(|| after.coord(hero));
let msg = *boom.unwrap_err().downcast::<String>().unwrap();
assert!(msg.contains("different grid"), "{msg}");
} else {
assert_eq!(after.coord(hero), Sq::new(2, 1));
}
}
#[test]
fn an_out_of_range_stale_index_at_least_gets_caught() {
let full = FullGrid::square(3, 3, Adjacency::Four);
let corner = full.at(Sq::new(2, 2)); let after = full.filtered(|c| c != Sq::new(0, 0));
let boom = std::panic::catch_unwind(|| after.coord(corner));
let msg = *boom.unwrap_err().downcast::<String>().unwrap();
if cfg!(debug_assertions) {
assert!(msg.contains("different grid"), "{msg}");
return;
}
assert!(msg.contains("not on this grid"), "{msg}");
assert!(
msg.contains("filtered"),
"and it names the likely culprit: {msg}"
);
}
#[test]
fn a_saved_coordinate_survives_the_same_change() {
let full = FullGrid::square(3, 3, Adjacency::Four);
let hero = full.at(Sq::new(2, 2));
let saved = serde_json::to_string(&full.coord(hero)).unwrap();
let after = full.filtered(|c| c != Sq::new(0, 0));
let loaded: Sq = serde_json::from_str(&saved).unwrap();
let rehomed = after.index_of(loaded).expect("the cell still exists");
assert_eq!(
after.coord(rehomed),
Sq::new(2, 2),
"still exactly where we left him"
);
assert_ne!(
rehomed, hero,
"under a different index, which is the whole point"
);
}
#[test]
fn a_coordinate_that_no_longer_exists_says_so() {
let full = FullGrid::square(3, 3, Adjacency::Four);
let doomed = full.coord(full.at(Sq::new(0, 0)));
let after = full.filtered(|c| c != Sq::new(0, 0));
let saved = serde_json::to_string(&doomed).unwrap();
let loaded: Sq = serde_json::from_str(&saved).unwrap();
assert_eq!(
after.index_of(loaded),
None,
"the cell is gone, and we are told so"
);
}
#[test]
fn a_grid_is_rebuilt_from_its_definition_not_deserialized() {
#[derive(serde::Serialize, serde::Deserialize)]
struct Board {
w: i32,
h: i32,
adjacency: Adjacency,
}
let saved = serde_json::to_string(&Board {
w: 8,
h: 8,
adjacency: Adjacency::Eight,
})
.unwrap();
let b: Board = serde_json::from_str(&saved).unwrap();
let g = FullGrid::square(b.w, b.h, b.adjacency);
let original = FullGrid::square(8, 8, Adjacency::Eight);
assert_eq!(g.len(), original.len());
assert_eq!(
g.cells().collect::<Vec<_>>(),
original.cells().collect::<Vec<_>>(),
"cell for cell, in the same order"
);
}
#[test]
fn rebuilding_from_cells_restores_the_same_indices_not_merely_an_equal_board() {
use spacewalk::{Dir8, Metric};
let original = FullGrid::square(6, 6, Adjacency::Eight)
.filtered(|c| (c.x + c.y) % 3 != 0) .filtered(|c| c != Sq::new(5, 5));
let saved = serde_json::to_string(&original.cells().collect::<Vec<_>>()).unwrap();
let cells: Vec<Sq> = serde_json::from_str(&saved).unwrap();
let restored = FullGrid::new(cells, &Dir8::ALL, Metric::CHEBYSHEV);
assert_eq!(restored.len(), original.len());
for i in original.indices() {
assert_eq!(
restored.coord(i),
original.coord(i),
"index {i} means the same cell"
);
}
for i in original.indices() {
let a: Vec<_> = original.neighbors(i).collect();
let b: Vec<_> = restored.neighbors(i).collect();
assert_eq!(a, b, "the neighbourhood of {i} survived");
}
}
#[test]
fn a_cell_map_survives_only_when_the_cells_that_fix_its_order_are_saved_with_it() {
use spacewalk::{CellMap, Dir8, Metric};
let g = FullGrid::square(5, 5, Adjacency::Four).filtered(|c| c != Sq::new(2, 2));
let height = CellMap::from_fn(&g, |c: Sq| c.x * 10 + c.y);
let saved = serde_json::to_string(&(g.cells().collect::<Vec<_>>(), &height)).unwrap();
let (cells, loaded): (Vec<Sq>, CellMap<i32>) = serde_json::from_str(&saved).unwrap();
let restored = FullGrid::new(cells, &Dir8::ORTHO, Metric::MANHATTAN);
for i in restored.indices() {
let c = restored.coord(i);
assert_eq!(loaded[i], c.x * 10 + c.y, "index {i} still means {c:?}");
}
}
#[test]
fn a_whole_game_state_round_trips_through_coordinates() {
use spacewalk::Movement;
#[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug)]
struct Save {
walls: Vec<Sq>,
units: Vec<(Sq, u8)>,
}
let g = FullGrid::square(8, 8, Adjacency::Eight);
let before = Save {
walls: vec![Sq::new(3, 3), Sq::new(3, 4)],
units: vec![(Sq::new(0, 0), 1), (Sq::new(7, 7), 2)],
};
let json = serde_json::to_string(&before).unwrap();
let after: Save = serde_json::from_str(&json).unwrap();
assert_eq!(before, after);
let walls = after.walls.clone();
let m = Movement::scan(&g, |s| (!walls.contains(&g.coord(s.to))).then_some(10));
let from = g.at(after.units[0].0);
let to = g.at(after.units[1].0);
assert!(
g.path(from, to, &m).is_some(),
"the loaded game still plays"
);
}