tests
This commit is contained in:
+101
-91
@@ -995,110 +995,120 @@ pub(crate) mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn apply_swap_swaps_two_terrain_cells() {
|
fn apply_shift_out_of_bounds_rejects_immediately() {
|
||||||
// A crate and a wall trade places in one batch (read-all then write-all).
|
// apply_shift validates all cells upfront; any out-of-bounds cell causes immediate failure.
|
||||||
let mut board = open_board(3, 1, (1, 0), vec![]);
|
|
||||||
crate_at(&mut board, 0, 0);
|
|
||||||
wall_at(&mut board, 2, 0);
|
|
||||||
let errs = board.apply_swap(&[(0, 0, 2, 0), (2, 0, 0, 0)]);
|
|
||||||
assert!(errs.is_empty());
|
|
||||||
assert_eq!(board.get(0, 0, 0).1, Archetype::Wall);
|
|
||||||
assert_eq!(board.get(0, 2, 0).1, Archetype::Crate);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn apply_swap_cycle_propagates_empty() {
|
|
||||||
// The spec example: move a->b and b->c with a empty ⇒ a empty, b empty,
|
|
||||||
// c holds what b held (the empty written into b doesn't block b->c).
|
|
||||||
let mut board = open_board(4, 1, (3, 0), vec![]);
|
|
||||||
// a = (0,0) empty, b = (1,0) crate, c = (2,0) wall.
|
|
||||||
crate_at(&mut board, 1, 0);
|
|
||||||
wall_at(&mut board, 2, 0);
|
|
||||||
let errs = board.apply_swap(&[(0, 0, 1, 0), (1, 0, 2, 0)]);
|
|
||||||
assert!(errs.is_empty());
|
|
||||||
assert_eq!(board.get(0, 0, 0).1, Archetype::Empty);
|
|
||||||
assert_eq!(board.get(0, 1, 0).1, Archetype::Empty);
|
|
||||||
assert_eq!(board.get(0, 2, 0).1, Archetype::Crate);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn apply_swap_empty_onto_object_removes_it() {
|
|
||||||
// Moving an empty source onto an object despawns the object.
|
|
||||||
let mut board = open_board(3, 1, (2, 0), vec![ObjectDef::new(1, 0)]);
|
|
||||||
let errs = board.apply_swap(&[(0, 0, 1, 0)]);
|
|
||||||
assert!(errs.is_empty());
|
|
||||||
assert!(board.solid_object_id_at(1, 0).is_none());
|
|
||||||
assert!(board.objects.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn apply_swap_terrain_onto_terrain_removes_displaced() {
|
|
||||||
// Moving a crate onto a wall clears the source and removes the wall.
|
|
||||||
let mut board = open_board(3, 1, (2, 0), vec![]);
|
let mut board = open_board(3, 1, (2, 0), vec![]);
|
||||||
crate_at(&mut board, 0, 0);
|
crate_at(&mut board, 0, 0);
|
||||||
wall_at(&mut board, 1, 0);
|
let errs = board.apply_shift(&[(0, 0), (9, 0)]);
|
||||||
let errs = board.apply_swap(&[(0, 0, 1, 0)]);
|
|
||||||
assert!(errs.is_empty());
|
|
||||||
assert_eq!(board.get(0, 0, 0).1, Archetype::Empty);
|
|
||||||
assert_eq!(board.get(0, 1, 0).1, Archetype::Crate);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn apply_swap_moves_object_and_player() {
|
|
||||||
// An object and the player relocate (swap places) in one batch.
|
|
||||||
let mut board = open_board(3, 1, (0, 0), vec![ObjectDef::new(2, 0)]);
|
|
||||||
let errs = board.apply_swap(&[(0, 0, 2, 0), (2, 0, 0, 0)]);
|
|
||||||
assert!(errs.is_empty());
|
|
||||||
assert_eq!((board.player.x, board.player.y), (2, 0));
|
|
||||||
assert_eq!((board.objects[&1].x, board.objects[&1].y), (0, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn apply_swap_out_of_bounds_skips_and_logs() {
|
|
||||||
let mut board = open_board(3, 1, (2, 0), vec![]);
|
|
||||||
crate_at(&mut board, 0, 0);
|
|
||||||
let errs = board.apply_swap(&[(0, 0, 9, 0)]);
|
|
||||||
assert_eq!(errs.len(), 1);
|
assert_eq!(errs.len(), 1);
|
||||||
assert_eq!(board.get(0, 0, 0).1, Archetype::Crate); // unchanged
|
assert_eq!(board.get(0, 0, 0).1, Archetype::Crate); // unchanged
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn apply_swap_will_not_overwrite_player() {
|
fn apply_shift_wall_stops_cascade_but_empty_limits_it() {
|
||||||
// A crate moved onto the (non-relocating) player is rejected and logged.
|
// A non-pushable Wall is immobile. Backward cascade from the wall traces
|
||||||
let mut board = open_board(3, 1, (1, 0), vec![]);
|
// through preceding solids until it hits empty, marking those as blocked.
|
||||||
|
// Solids on the *other* side of the empty (outside the blocked region) still move.
|
||||||
|
let mut board = open_board(6, 1, (5, 0), vec![]);
|
||||||
crate_at(&mut board, 0, 0);
|
crate_at(&mut board, 0, 0);
|
||||||
let errs = board.apply_swap(&[(0, 0, 1, 0)]);
|
// (1,0) stays empty
|
||||||
assert_eq!(errs.len(), 1);
|
wall_at(&mut board, 2, 0);
|
||||||
assert_eq!((board.player.x, board.player.y), (1, 0)); // player kept its cell
|
crate_at(&mut board, 3, 0);
|
||||||
assert_eq!(board.get(0, 0, 0).1, Archetype::Empty); // source still vacated
|
crate_at(&mut board, 4, 0);
|
||||||
|
|
||||||
|
// Cycle: idx 0 → idx 1 → idx 2 → idx 3 → idx 4 → idx 0 (wrap)
|
||||||
|
// immobile = {2} (Wall, Pushable::No)
|
||||||
|
// Backward trace from idx 2: solids[2]=Some → blocked.insert(2), prev=1
|
||||||
|
// solids[1]=None (empty) → break
|
||||||
|
// blocked = {2}; Crate at (0,0) is NOT blocked
|
||||||
|
// Result: Crate(0,0)→(1,0), empty→no-op, Wall stays at (2,0),
|
||||||
|
// Crate(3,0)→(4,0), Crate(4,0)→(0,0) wrap
|
||||||
|
let _errs = board.apply_shift(&[(0, 0), (1, 0), (2, 0), (3, 0), (4, 0)]);
|
||||||
|
|
||||||
|
assert_eq!(board.get(0, 0, 0).1, Archetype::Crate); // wrapped from (4,0)
|
||||||
|
assert_eq!(board.get(0, 1, 0).1, Archetype::Crate); // moved from (0,0)
|
||||||
|
assert_eq!(board.get(0, 2, 0).1, Archetype::Wall); // blocked, immobile
|
||||||
|
assert_eq!(board.get(0, 3, 0).1, Archetype::Empty); // cleared, crate moved
|
||||||
|
assert_eq!(board.get(0, 4, 0).1, Archetype::Crate); // moved from (3,0)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn apply_swap_grab_onto_player_is_refused_like_any_solid() {
|
fn apply_shift_hcrate_moves_horizontally_vcrate_stays() {
|
||||||
// A grab gem swapped onto the player is no longer special: it is refused and
|
// In a horizontal (same-row) adjacent cycle:
|
||||||
// logged (the player wins its cell), and left at its source. Grab fires only
|
// - HCrate (Pushable::Horizontal) moves freely (hmove, not blocked).
|
||||||
// when the player walks onto a grab thing, not when one is swapped in.
|
// - VCrate (Pushable::Vertical) is immobile (Vertical && hmove).
|
||||||
let mut gem = ObjectDef::new(0, 0);
|
// Both can share the same pattern without blocking each other's neighbors.
|
||||||
gem.grab = true;
|
|
||||||
gem.pushable = true;
|
// Subcase A: HCrate moves
|
||||||
let mut board = open_board(2, 1, (1, 0), vec![gem]);
|
{
|
||||||
let errs = board.apply_swap(&[(0, 0, 1, 0)]);
|
let mut board = open_board(4, 1, (3, 0), vec![]);
|
||||||
assert_eq!(errs.len(), 1); // refusal logged
|
stamp(&mut board, 0, 0, Archetype::HCrate);
|
||||||
assert_eq!((board.player.x, board.player.y), (1, 0)); // player kept its cell
|
crate_at(&mut board, 1, 0);
|
||||||
assert_eq!((board.objects[&1].x, board.objects[&1].y), (0, 0)); // gem stayed
|
// (2,0) empty
|
||||||
|
|
||||||
|
let _errs = board.apply_shift(&[(0, 0), (1, 0), (2, 0)]);
|
||||||
|
|
||||||
|
assert_eq!(board.get(0, 0, 0).1, Archetype::Empty); // HCrate moved out
|
||||||
|
assert_eq!(board.get(0, 1, 0).1, Archetype::HCrate); // moved from (0,0)
|
||||||
|
assert_eq!(board.get(0, 2, 0).1, Archetype::Crate); // moved from (1,0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subcase B: VCrate stays; Crate behind it still moves
|
||||||
|
{
|
||||||
|
let mut board = open_board(4, 1, (3, 0), vec![]);
|
||||||
|
stamp(&mut board, 0, 0, Archetype::VCrate);
|
||||||
|
crate_at(&mut board, 1, 0);
|
||||||
|
// (2,0) empty
|
||||||
|
|
||||||
|
// VCrate (idx 0): target (1,0), origin (0,0), hmove=true.
|
||||||
|
// Pushable::Vertical && hmove=true → immobile
|
||||||
|
// Backward: prev=idx 2, solids[2]=None → stop. blocked={0}.
|
||||||
|
// Crate at (1,0) is NOT in blocked, so it moves.
|
||||||
|
let _errs = board.apply_shift(&[(0, 0), (1, 0), (2, 0)]);
|
||||||
|
|
||||||
|
assert_eq!(board.get(0, 0, 0).1, Archetype::VCrate); // immobile
|
||||||
|
assert_eq!(board.get(0, 1, 0).1, Archetype::Empty); // crate moved out
|
||||||
|
assert_eq!(board.get(0, 2, 0).1, Archetype::Crate); // moved from (1,0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn apply_swap_overlap_after_refused_player_write_deletes_the_other_solid() {
|
fn apply_shift_vcrate_moves_vertically_hcrate_stays() {
|
||||||
// object (0,0)→player (1,0) [refused, stays at (0,0)] while a crate (2,0)→(0,0)
|
// In a vertical (same-column) adjacent cycle:
|
||||||
// moves into the object's cell. The sweep keeps the object and deletes the
|
// - VCrate (Pushable::Vertical) moves freely (vmove, not blocked).
|
||||||
// crate; one error logs the refusal, one logs the overlap.
|
// - HCrate (Pushable::Horizontal) is immobile (Horizontal && vmove).
|
||||||
let mut board = open_board(3, 1, (1, 0), vec![ObjectDef::new(0, 0)]);
|
|
||||||
crate_at(&mut board, 2, 0);
|
// Subcase A: VCrate moves
|
||||||
let errs = board.apply_swap(&[(0, 0, 1, 0), (2, 0, 0, 0)]);
|
{
|
||||||
assert_eq!(errs.len(), 2); // the refusal and the overlap were logged
|
let mut board = open_board(1, 4, (0, 3), vec![]);
|
||||||
assert_eq!((board.objects[&1].x, board.objects[&1].y), (0, 0)); // object kept
|
stamp(&mut board, 0, 0, Archetype::VCrate);
|
||||||
assert_eq!(board.get(0, 0, 0).1, Archetype::Empty); // crate deleted
|
stamp(&mut board, 0, 1, Archetype::Crate);
|
||||||
assert_eq!((board.player.x, board.player.y), (1, 0)); // player untouched
|
// (0,2) empty
|
||||||
|
|
||||||
|
let _errs = board.apply_shift(&[(0, 0), (0, 1), (0, 2)]);
|
||||||
|
|
||||||
|
assert_eq!(board.get(0, 0, 0).1, Archetype::Empty); // VCrate moved out
|
||||||
|
assert_eq!(board.get(0, 0, 1).1, Archetype::VCrate); // moved from (0,0)
|
||||||
|
assert_eq!(board.get(0, 0, 2).1, Archetype::Crate); // moved from (0,1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subcase B: HCrate stays; Crate behind it still moves
|
||||||
|
{
|
||||||
|
let mut board = open_board(1, 4, (0, 3), vec![]);
|
||||||
|
stamp(&mut board, 0, 0, Archetype::HCrate);
|
||||||
|
stamp(&mut board, 0, 1, Archetype::Crate);
|
||||||
|
// (0,2) empty
|
||||||
|
|
||||||
|
// HCrate (idx 0): target (0,1), origin (0,0), vmove=true.
|
||||||
|
// Pushable::Horizontal && vmove=true → immobile
|
||||||
|
// Backward: prev=idx 2, solids[2]=None → stop. blocked={0}.
|
||||||
|
// Crate at (0,1) is NOT in blocked, so it moves.
|
||||||
|
let _errs = board.apply_shift(&[(0, 0), (0, 1), (0, 2)]);
|
||||||
|
|
||||||
|
assert_eq!(board.get(0, 0, 0).1, Archetype::HCrate); // immobile
|
||||||
|
assert_eq!(board.get(0, 0, 1).1, Archetype::Empty); // crate moved out
|
||||||
|
assert_eq!(board.get(0, 0, 2).1, Archetype::Crate); // moved from (0,1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -582,12 +582,12 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn script_swap_moves_crates_simultaneously() {
|
fn script_shift_rotates_crates() {
|
||||||
// Swap the crate at (2,0) with the empty cell at (3,0): one ends up empty,
|
// Rotate the crate at (2,0) with the empty cell at (3,0) in a two-cell cycle:
|
||||||
// the other holds the crate, applied in a single batch.
|
// crate moves to (3,0), empty moves back to (2,0).
|
||||||
let mut game = game_with_object_script(
|
let mut game = game_with_object_script(
|
||||||
5,
|
5,
|
||||||
"fn tick(dt) { if Queue.length() == 0 { swap([[2, 0, 3, 0]]); } }",
|
"fn tick(dt) { if Queue.length() == 0 { shift([[2, 0], [3, 0]]); } }",
|
||||||
);
|
);
|
||||||
game.tick(Duration::from_millis(16));
|
game.tick(Duration::from_millis(16));
|
||||||
let b = game.board();
|
let b = game.board();
|
||||||
|
|||||||
Reference in New Issue
Block a user