wip 6 fixed tests
This commit is contained in:
+3
-74
@@ -501,12 +501,11 @@ impl Board {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod tests {
|
pub(crate) mod tests {
|
||||||
use std::assert_matches;
|
|
||||||
use super::Board;
|
use super::Board;
|
||||||
use crate::builtin::Builtin;
|
use crate::builtin::Builtin;
|
||||||
use crate::floor::Floor;
|
use crate::floor::Floor;
|
||||||
use crate::glyph::Glyph;
|
use crate::glyph::Glyph;
|
||||||
use crate::utils::{Direction, ObjectId, Point};
|
use crate::utils::ObjectId;
|
||||||
use color::Rgba8;
|
use color::Rgba8;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use crate::object_def::ObjectDef;
|
use crate::object_def::ObjectDef;
|
||||||
@@ -703,76 +702,6 @@ pub(crate) mod tests {
|
|||||||
assert!(!board.in_bounds((0, 2)));
|
assert!(!board.in_bounds((0, 2)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn can_shift_only_checks_the_cell_ahead() {
|
|
||||||
// Source must be pushable.
|
|
||||||
let mut board = open_board(4, 1, (3, 0));
|
|
||||||
assert!(!board.can_shift(0, 0, Direction::East)); // empty source
|
|
||||||
|
|
||||||
// Crate with open space ahead: shiftable.
|
|
||||||
crate_at(&mut board, 0, 0);
|
|
||||||
assert!(board.can_shift(0, 0, Direction::East));
|
|
||||||
assert_matches!(board.get((0, 0)), Some(Tile::Object(_)));
|
|
||||||
|
|
||||||
// Crate with another pushable crate ahead: still shiftable (unlike can_push,
|
|
||||||
// which would follow the chain to the wall and fail).
|
|
||||||
let mut board = open_board(4, 1, (3, 0));
|
|
||||||
crate_at(&mut board, 0, 0);
|
|
||||||
crate_at(&mut board, 1, 0);
|
|
||||||
wall_at(&mut board, 2, 0);
|
|
||||||
assert!(board.can_shift(0, 0, Direction::East));
|
|
||||||
assert!(!board.can_push(0, 0, Direction::East));
|
|
||||||
|
|
||||||
// Crate with a non-pushable wall ahead: not shiftable.
|
|
||||||
let mut board = open_board(3, 1, (2, 0));
|
|
||||||
crate_at(&mut board, 0, 0);
|
|
||||||
wall_at(&mut board, 1, 0);
|
|
||||||
assert!(!board.can_shift(0, 0, Direction::East));
|
|
||||||
|
|
||||||
// Crate at the board edge facing off-board: not shiftable.
|
|
||||||
let mut board = open_board(2, 1, (0, 0));
|
|
||||||
crate_at(&mut board, 1, 0);
|
|
||||||
assert!(!board.can_shift(1, 0, Direction::East));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn can_shift_treats_the_player_as_a_blocker() {
|
|
||||||
// The player is always a blocker for a shift — even a grab gem may not shift
|
|
||||||
// onto it (grab now fires only on player movement, not on being shifted in).
|
|
||||||
let mut board = open_board(2, 1, (1, 0));
|
|
||||||
gem_at(&mut board, 0, 0);
|
|
||||||
assert!(!board.can_shift(0, 0, Direction::East));
|
|
||||||
|
|
||||||
// A plain crate likewise may not shift onto the player.
|
|
||||||
let mut board = open_board(2, 1, (1, 0));
|
|
||||||
crate_at(&mut board, 0, 0);
|
|
||||||
assert!(!board.can_shift(0, 0, Direction::East));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn push_into_player_pushes_player() {
|
|
||||||
// Crate shoved east into the player slides the player along into open space.
|
|
||||||
let mut board = open_board(4, 1, (2, 0));
|
|
||||||
crate_at(&mut board, 1, 0);
|
|
||||||
assert!(board.can_push(1, 0, Direction::East));
|
|
||||||
board.push(1, 0, Direction::East);
|
|
||||||
assert!(board.get((1, 0)).is_none());
|
|
||||||
assert!(is_builtin(&board, 2, 0,"crate"));
|
|
||||||
assert_eq!(board.player_pos(), Point { x: 3, y: 0 });
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn push_into_player_blocked_by_wall() {
|
|
||||||
// Player backed against a wall: push has nowhere to go, nothing moves.
|
|
||||||
let mut board = open_board(4, 1, (2, 0));
|
|
||||||
crate_at(&mut board, 1, 0);
|
|
||||||
wall_at(&mut board, 3, 0);
|
|
||||||
assert!(!board.can_push(1, 0, Direction::East));
|
|
||||||
board.push(1, 0, Direction::East); // no-op
|
|
||||||
assert!(is_builtin(&board, 1, 0, "crate"));
|
|
||||||
assert_eq!(board.player_pos(), Point { x: 2, y: 0 });
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn glyph_at_uses_floor_for_empty_and_grid_for_solid() {
|
fn glyph_at_uses_floor_for_empty_and_grid_for_solid() {
|
||||||
// Player parked at (2,0) so it doesn't overlap either asserted cell.
|
// Player parked at (2,0) so it doesn't overlap either asserted cell.
|
||||||
@@ -812,7 +741,7 @@ pub(crate) mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn apply_shift_wall_stops_cascade_but_empty_limits_it() {
|
fn apply_shift_wall_stops_cascade_but_empty_limits_it() {
|
||||||
// A non-pushable Wall is immobile. Backward cascade from the wall traces
|
// A wall is immobile (`mobile = false`). Backward cascade from the wall traces
|
||||||
// through preceding solids until it hits empty, marking those as blocked.
|
// 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.
|
// Solids on the *other* side of the empty (outside the blocked region) still move.
|
||||||
let mut board = open_board(6, 1, (5, 0));
|
let mut board = open_board(6, 1, (5, 0));
|
||||||
@@ -823,7 +752,7 @@ pub(crate) mod tests {
|
|||||||
crate_at(&mut board, 4, 0);
|
crate_at(&mut board, 4, 0);
|
||||||
|
|
||||||
// Cycle: idx 0 → idx 1 → idx 2 → idx 3 → idx 4 → idx 0 (wrap)
|
// Cycle: idx 0 → idx 1 → idx 2 → idx 3 → idx 4 → idx 0 (wrap)
|
||||||
// immobile = {2} (Wall, Pushable::No)
|
// immobile = {2} (the wall: Tile::shiftable() is false)
|
||||||
// Backward trace from idx 2: solids[2]=Some → blocked.insert(2), prev=1
|
// Backward trace from idx 2: solids[2]=Some → blocked.insert(2), prev=1
|
||||||
// solids[1]=None (empty) → break
|
// solids[1]=None (empty) → break
|
||||||
// blocked = {2}; Crate at (0,0) is NOT blocked
|
// blocked = {2}; Crate at (0,0) is NOT blocked
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use crate::glyph::Glyph;
|
use crate::glyph::Glyph;
|
||||||
use crate::utils::Pushable;
|
|
||||||
use color::Rgba8;
|
use color::Rgba8;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use crate::keys::KeyType;
|
use crate::keys::KeyType;
|
||||||
|
|||||||
@@ -483,9 +483,9 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn walking_onto_a_gem_grabs_it() {
|
fn walking_onto_a_gem_grabs_it() {
|
||||||
// A gem builtin object at (1,0), running scripts/gem.rhai. Its
|
// A gem builtin object at (1,0), running scripts/gem.rhai. Its bump hook
|
||||||
// EnterResponse::Grab means the player's step fires grab() rather than being
|
// banks the gem and calls die(), so the cell clears and the player's step
|
||||||
// blocked. The player starts at (0,0).
|
// completes instead of being blocked. The player starts at (0,0).
|
||||||
let mut board = open_board(3, 1, (0, 0));
|
let mut board = open_board(3, 1, (0, 0));
|
||||||
gem_at(&mut board, 1, 0);
|
gem_at(&mut board, 1, 0);
|
||||||
let mut game = GameState::new(board);
|
let mut game = GameState::new(board);
|
||||||
|
|||||||
@@ -2,12 +2,11 @@ use super::{log_texts, scripts_from};
|
|||||||
use crate::board::tests::{crate_at, is_builtin, object_at, open_board, wall_at};
|
use crate::board::tests::{crate_at, is_builtin, object_at, open_board, wall_at};
|
||||||
use crate::game::GameState;
|
use crate::game::GameState;
|
||||||
use crate::glyph::Glyph;
|
use crate::glyph::Glyph;
|
||||||
use crate::tile::EnterResponse;
|
|
||||||
use crate::utils::{ObjectId, Point};
|
use crate::utils::{ObjectId, Point};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
/// The cell object `id` currently occupies.
|
/// The cell object `id` currently occupies.
|
||||||
fn loc(game: &GameState, id: ObjectId) -> (usize, usize) {
|
fn loc(game: &GameState, id: ObjectId) -> Point {
|
||||||
game.board()
|
game.board()
|
||||||
.get_hookable(id)
|
.get_hookable(id)
|
||||||
.expect("object still on the board")
|
.expect("object still on the board")
|
||||||
@@ -22,13 +21,12 @@ fn glyph(game: &GameState, id: ObjectId) -> Glyph {
|
|||||||
.glyph()
|
.glyph()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds a game with one `Block` object at `(x, y)` running `src`, plus the player
|
/// Builds a game with one immobile object at `(x, y)` running `src`, plus the player
|
||||||
/// parked at `player`, and runs `init()`.
|
/// parked at `player`, and runs `init()`.
|
||||||
///
|
///
|
||||||
/// The mover is `EnterResponse::Block` — an ordinary solid that blocks others and
|
/// The mover is `mobile = false`: nothing else may relocate it (a `shift` skips it,
|
||||||
/// cannot be shoved, which is what a self-propelled scripted object should be. Its
|
/// and it has no `bump` hook to shove itself aside). That flag constrains only what
|
||||||
/// `enter_response` describes how it answers *others* entering its cell and should
|
/// *others* can do to it — it never restricts the object's own `move()`.
|
||||||
/// not constrain its own `move()`.
|
|
||||||
fn game_with_mover(
|
fn game_with_mover(
|
||||||
w: usize,
|
w: usize,
|
||||||
h: usize,
|
h: usize,
|
||||||
@@ -37,7 +35,7 @@ fn game_with_mover(
|
|||||||
src: &str,
|
src: &str,
|
||||||
) -> (GameState, ObjectId) {
|
) -> (GameState, ObjectId) {
|
||||||
let mut board = open_board(w, h, player);
|
let mut board = open_board(w, h, player);
|
||||||
let id = object_at(&mut board, at.0, at.1, "m", EnterResponse::Block);
|
let id = object_at(&mut board, at.0, at.1, "m", false);
|
||||||
let mut game = GameState::with_scripts(board, scripts_from(&[("m", src)]));
|
let mut game = GameState::with_scripts(board, scripts_from(&[("m", src)]));
|
||||||
game.run_init();
|
game.run_init();
|
||||||
(game, id)
|
(game, id)
|
||||||
@@ -47,23 +45,23 @@ fn game_with_mover(
|
|||||||
fn move_command_relocates_the_source_object() {
|
fn move_command_relocates_the_source_object() {
|
||||||
let (game, id) = game_with_mover(5, 3, (0, 0), (2, 1), "fn init(me) { move(East); }");
|
let (game, id) = game_with_mover(5, 3, (0, 0), (2, 1), "fn init(me) { move(East); }");
|
||||||
// East increments x by one; the object started at (2, 1).
|
// East increments x by one; the object started at (2, 1).
|
||||||
assert_eq!(loc(&game, id), (3, 1));
|
assert_eq!(loc(&game, id), Point { x: 3, y: 1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn move_into_a_wall_or_edge_is_a_noop() {
|
fn move_into_a_wall_or_edge_is_a_noop() {
|
||||||
// Object at the west edge moving west: out of bounds, ignored.
|
// Object at the west edge moving west: out of bounds, ignored.
|
||||||
let (game, id) = game_with_mover(5, 3, (0, 0), (0, 1), "fn init(me) { move(West); }");
|
let (game, id) = game_with_mover(5, 3, (0, 0), (0, 1), "fn init(me) { move(West); }");
|
||||||
assert_eq!(loc(&game, id), (0, 1));
|
assert_eq!(loc(&game, id), Point { x: 0, y: 1 });
|
||||||
|
|
||||||
// Object facing a wall: blocked, also ignored.
|
// Object facing a wall: blocked, also ignored.
|
||||||
let mut board = open_board(5, 3, (0, 0));
|
let mut board = open_board(5, 3, (0, 0));
|
||||||
let id = object_at(&mut board, 1, 1, "m", EnterResponse::Block);
|
let id = object_at(&mut board, 1, 1, "m", false);
|
||||||
wall_at(&mut board, 2, 1);
|
wall_at(&mut board, 2, 1);
|
||||||
let mut game =
|
let mut game =
|
||||||
GameState::with_scripts(board, scripts_from(&[("m", "fn init(me) { move(East); }")]));
|
GameState::with_scripts(board, scripts_from(&[("m", "fn init(me) { move(East); }")]));
|
||||||
game.run_init();
|
game.run_init();
|
||||||
assert_eq!(loc(&game, id), (1, 1));
|
assert_eq!(loc(&game, id), Point { x: 1, y: 1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -76,13 +74,13 @@ fn set_tile_command_changes_the_source_glyph() {
|
|||||||
fn object_pushes_crate_on_init() {
|
fn object_pushes_crate_on_init() {
|
||||||
// A scripted object moving east into a crate shoves it (the step_object path).
|
// A scripted object moving east into a crate shoves it (the step_object path).
|
||||||
let mut board = open_board(4, 1, (0, 0));
|
let mut board = open_board(4, 1, (0, 0));
|
||||||
let id = object_at(&mut board, 1, 0, "m", EnterResponse::Block);
|
let id = object_at(&mut board, 1, 0, "m", false);
|
||||||
crate_at(&mut board, 2, 0);
|
crate_at(&mut board, 2, 0);
|
||||||
let mut game =
|
let mut game =
|
||||||
GameState::with_scripts(board, scripts_from(&[("m", "fn init(me) { move(East); }")]));
|
GameState::with_scripts(board, scripts_from(&[("m", "fn init(me) { move(East); }")]));
|
||||||
game.run_init();
|
game.run_init();
|
||||||
|
|
||||||
assert_eq!(loc(&game, id), (2, 0));
|
assert_eq!(loc(&game, id), Point { x: 2, y: 0 });
|
||||||
let b = game.board();
|
let b = game.board();
|
||||||
assert!(is_builtin(&b, 3, 0, "crate")); // crate shoved one east
|
assert!(is_builtin(&b, 3, 0, "crate")); // crate shoved one east
|
||||||
assert!(b.get((1, 0)).is_none()); // the object's old cell is vacated
|
assert!(b.get((1, 0)).is_none()); // the object's old cell is vacated
|
||||||
@@ -92,18 +90,18 @@ fn object_pushes_crate_on_init() {
|
|||||||
fn object_push_into_player() {
|
fn object_push_into_player() {
|
||||||
// A scripted object moving into the player pushes the player when there's room.
|
// A scripted object moving into the player pushes the player when there's room.
|
||||||
let (game, id) = game_with_mover(5, 1, (2, 0), (1, 0), "fn init(me) { move(East); }");
|
let (game, id) = game_with_mover(5, 1, (2, 0), (1, 0), "fn init(me) { move(East); }");
|
||||||
assert_eq!(loc(&game, id), (2, 0));
|
assert_eq!(loc(&game, id), Point { x: 2, y: 0 });
|
||||||
assert_eq!(game.board().player_pos(), Point { x: 3, y: 0 });
|
assert_eq!(game.board().player_pos(), Point { x: 3, y: 0 });
|
||||||
|
|
||||||
// With a wall behind the player, the object is blocked and nothing moves.
|
// With a wall behind the player, the object is blocked and nothing moves.
|
||||||
let mut board = open_board(4, 1, (2, 0));
|
let mut board = open_board(4, 1, (2, 0));
|
||||||
let id = object_at(&mut board, 1, 0, "m", EnterResponse::Block);
|
let id = object_at(&mut board, 1, 0, "m", false);
|
||||||
wall_at(&mut board, 3, 0);
|
wall_at(&mut board, 3, 0);
|
||||||
let mut game =
|
let mut game =
|
||||||
GameState::with_scripts(board, scripts_from(&[("m", "fn init(me) { move(East); }")]));
|
GameState::with_scripts(board, scripts_from(&[("m", "fn init(me) { move(East); }")]));
|
||||||
game.run_init();
|
game.run_init();
|
||||||
|
|
||||||
assert_eq!(loc(&game, id), (1, 0));
|
assert_eq!(loc(&game, id), Point { x: 1, y: 0 });
|
||||||
assert_eq!(game.board().player_pos(), Point { x: 2, y: 0 });
|
assert_eq!(game.board().player_pos(), Point { x: 2, y: 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,16 +116,16 @@ fn move_cost_rate_limits_repeated_moves() {
|
|||||||
(1, 0),
|
(1, 0),
|
||||||
"fn init(me) { move(East); move(East); }",
|
"fn init(me) { move(East); move(East); }",
|
||||||
);
|
);
|
||||||
assert_eq!(loc(&game, id).0, 2); // first move applied (1 -> 2)
|
assert_eq!(loc(&game, id).x, 2); // first move applied (1 -> 2)
|
||||||
|
|
||||||
// 200 ms of ticks: still inside the cooldown, no further movement.
|
// 200 ms of ticks: still inside the cooldown, no further movement.
|
||||||
game.tick(Duration::from_millis(100));
|
game.tick(Duration::from_millis(100));
|
||||||
game.tick(Duration::from_millis(100));
|
game.tick(Duration::from_millis(100));
|
||||||
assert_eq!(loc(&game, id).0, 2);
|
assert_eq!(loc(&game, id).x, 2);
|
||||||
|
|
||||||
// Crossing the 250 ms mark releases the queued second move.
|
// Crossing the 250 ms mark releases the queued second move.
|
||||||
game.tick(Duration::from_millis(100));
|
game.tick(Duration::from_millis(100));
|
||||||
assert_eq!(loc(&game, id).0, 3);
|
assert_eq!(loc(&game, id).x, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -135,7 +133,7 @@ fn inline_delay_paces_subsequent_moves() {
|
|||||||
// move() always appends a Delay(250 ms) to the queue, so a second queued move
|
// move() always appends a Delay(250 ms) to the queue, so a second queued move
|
||||||
// is held back by that delay even if the first move was blocked by a wall.
|
// is held back by that delay even if the first move was blocked by a wall.
|
||||||
let mut board = open_board(3, 3, (0, 0));
|
let mut board = open_board(3, 3, (0, 0));
|
||||||
let id = object_at(&mut board, 1, 1, "m", EnterResponse::Block);
|
let id = object_at(&mut board, 1, 1, "m", false);
|
||||||
wall_at(&mut board, 2, 1);
|
wall_at(&mut board, 2, 1);
|
||||||
let mut game = GameState::with_scripts(
|
let mut game = GameState::with_scripts(
|
||||||
board,
|
board,
|
||||||
@@ -143,16 +141,16 @@ fn inline_delay_paces_subsequent_moves() {
|
|||||||
);
|
);
|
||||||
game.run_init();
|
game.run_init();
|
||||||
// First (eastward) move is blocked by the wall: object hasn't moved.
|
// First (eastward) move is blocked by the wall: object hasn't moved.
|
||||||
assert_eq!(loc(&game, id), (1, 1));
|
assert_eq!(loc(&game, id), Point { x: 1, y: 1 });
|
||||||
|
|
||||||
// The Delay(250 ms) appended by move(East) is still live, so South is pending.
|
// The Delay(250 ms) appended by move(East) is still live, so South is pending.
|
||||||
game.tick(Duration::from_millis(100));
|
game.tick(Duration::from_millis(100));
|
||||||
game.tick(Duration::from_millis(100));
|
game.tick(Duration::from_millis(100));
|
||||||
assert_eq!(loc(&game, id), (1, 1));
|
assert_eq!(loc(&game, id), Point { x: 1, y: 1 });
|
||||||
|
|
||||||
// Past 250 ms the queued South move resolves.
|
// Past 250 ms the queued South move resolves.
|
||||||
game.tick(Duration::from_millis(100));
|
game.tick(Duration::from_millis(100));
|
||||||
assert_eq!(loc(&game, id), (1, 2));
|
assert_eq!(loc(&game, id), Point { x: 1, y: 2 });
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -181,7 +179,7 @@ fn queue_clear_drops_pending_actions() {
|
|||||||
"fn init(me) { move(East); move(East); me.queue.clear(); }",
|
"fn init(me) { move(East); move(East); me.queue.clear(); }",
|
||||||
);
|
);
|
||||||
game.tick(Duration::from_millis(300));
|
game.tick(Duration::from_millis(300));
|
||||||
assert_eq!(loc(&game, id), (1, 0)); // never moved
|
assert_eq!(loc(&game, id), Point { x: 1, y: 0 }); // never moved
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -190,7 +188,7 @@ fn blocked_reports_solid_and_clear() {
|
|||||||
|
|
||||||
// Solid ahead (a wall): blocked() is true.
|
// Solid ahead (a wall): blocked() is true.
|
||||||
let mut board = open_board(3, 1, (0, 0));
|
let mut board = open_board(3, 1, (0, 0));
|
||||||
let id = object_at(&mut board, 1, 0, "b", EnterResponse::Block);
|
let id = object_at(&mut board, 1, 0, "b", false);
|
||||||
wall_at(&mut board, 2, 0);
|
wall_at(&mut board, 2, 0);
|
||||||
let mut game = GameState::with_scripts(board, scripts_from(&[("b", src)]));
|
let mut game = GameState::with_scripts(board, scripts_from(&[("b", src)]));
|
||||||
game.run_init();
|
game.run_init();
|
||||||
@@ -198,7 +196,7 @@ fn blocked_reports_solid_and_clear() {
|
|||||||
|
|
||||||
// Open ahead, nothing pending: blocked() is false.
|
// Open ahead, nothing pending: blocked() is false.
|
||||||
let mut board = open_board(3, 1, (0, 0));
|
let mut board = open_board(3, 1, (0, 0));
|
||||||
let id = object_at(&mut board, 1, 0, "b", EnterResponse::Block);
|
let id = object_at(&mut board, 1, 0, "b", false);
|
||||||
let mut game = GameState::with_scripts(board, scripts_from(&[("b", src)]));
|
let mut game = GameState::with_scripts(board, scripts_from(&[("b", src)]));
|
||||||
game.run_init();
|
game.run_init();
|
||||||
assert_eq!(glyph(&game, id).tile, 'N');
|
assert_eq!(glyph(&game, id).tile, 'N');
|
||||||
|
|||||||
@@ -30,8 +30,7 @@ fn portal(
|
|||||||
target_name: &str,
|
target_name: &str,
|
||||||
) -> Portal {
|
) -> Portal {
|
||||||
Portal {
|
Portal {
|
||||||
x,
|
location: (x, y).into(),
|
||||||
y,
|
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
target_board: target_board.to_string(),
|
target_board: target_board.to_string(),
|
||||||
target_name: target_name.to_string(),
|
target_name: target_name.to_string(),
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
mod actions;
|
mod actions;
|
||||||
// TODO(migration): map_file is not yet ported — it is blocked on the map files
|
|
||||||
// themselves still being pre-BoardSpec (see todo.md #1).
|
|
||||||
mod game_portals;
|
mod game_portals;
|
||||||
|
// TODO(migration): the map_file suite predates BoardSpec and is not ported yet.
|
||||||
// mod map_file;
|
// mod map_file;
|
||||||
mod movement;
|
mod movement;
|
||||||
mod scripting;
|
mod scripting;
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
//! Player push mechanics, driven through [`GameState::try_move`].
|
//! Player push mechanics, driven through [`GameState::try_move`].
|
||||||
//!
|
//!
|
||||||
//! Pushability now lives on the target's [`EnterResponse`]: `Push(Pushable::Any)`
|
//! Pushing is not a board primitive: [`GameState::resolve_move`] just fires the
|
||||||
//! for a crate, `Push(Horizontal)`/`Push(Vertical)` for the axis-locked ones, and
|
//! target's `bump` hook and then re-checks whether the cell it wanted has become
|
||||||
//! `Block` for a wall. `try_move` consults `Board::can_push` and, when the chain
|
//! free. A crate slides because `crate.rhai`'s `bump` pushes the cell ahead of it
|
||||||
//! can move, pushes from the *player's* cell — the player is itself pushable, so it
|
//! and then teleports itself into the space; an `hcrate`/`vcrate` returns early
|
||||||
//! rides along at the head of the chain.
|
//! from `bump` on the axis it refuses; a wall has no script at all, so nothing
|
||||||
|
//! ever clears its cell. The player is moved by the same recursion, so a chain
|
||||||
|
//! shoved into it slides it along.
|
||||||
|
|
||||||
use crate::board::tests::{add_floor, builtin_at, crate_at, is_builtin, open_board, plain_object_at, wall_at};
|
use crate::board::tests::{add_floor, builtin_at, crate_at, is_builtin, open_board, wall_at};
|
||||||
use crate::game::GameState;
|
use crate::game::GameState;
|
||||||
use crate::glyph::Glyph;
|
use crate::glyph::Glyph;
|
||||||
use crate::tile::EnterResponse;
|
use crate::utils::{Direction, Point};
|
||||||
use crate::utils::{Direction, Point, Pushable};
|
|
||||||
use color::Rgba8;
|
use color::Rgba8;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -105,27 +106,9 @@ fn cascade_blocked_by_wall_moves_nothing() {
|
|||||||
assert!(is_builtin(&b, 2, 0, "crate"));
|
assert!(is_builtin(&b, 2, 0, "crate"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn player_pushes_pushable_solid_object() {
|
|
||||||
// A plain object whose EnterResponse is Push(Any) is shoved exactly like a
|
|
||||||
// crate — the crate builtin has no privileged status, it just carries that
|
|
||||||
// same enter response.
|
|
||||||
let mut board = open_board(4, 1, (0, 0));
|
|
||||||
let id = plain_object_at(&mut board, 1, 0, EnterResponse::Push(Pushable::Any));
|
|
||||||
let mut game = GameState::new(board);
|
|
||||||
game.try_move(Direction::East);
|
|
||||||
|
|
||||||
let b = game.board();
|
|
||||||
assert_eq!(b.player_pos(), Point { x: 1, y: 0 });
|
|
||||||
assert_eq!(
|
|
||||||
b.get_hookable(id).expect("object still on the board").location(),
|
|
||||||
(2, 0)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hcrate_pushes_east_but_not_north() {
|
fn hcrate_pushes_east_but_not_north() {
|
||||||
// Pushing east: Push(Horizontal) allows it, so the HCrate slides.
|
// Pushing east: hcrate.rhai accepts the axis, so the HCrate slides.
|
||||||
let mut board = open_board(4, 1, (0, 0));
|
let mut board = open_board(4, 1, (0, 0));
|
||||||
builtin_at(&mut board, 1, 0, "hcrate");
|
builtin_at(&mut board, 1, 0, "hcrate");
|
||||||
let mut game = GameState::new(board);
|
let mut game = GameState::new(board);
|
||||||
@@ -136,8 +119,8 @@ fn hcrate_pushes_east_but_not_north() {
|
|||||||
assert!(is_builtin(&b, 2, 0, "hcrate"));
|
assert!(is_builtin(&b, 2, 0, "hcrate"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pushing north into an HCrate: the direction isn't allowed, so can_push fails
|
// Pushing north into an HCrate: its bump hook returns early on the vertical
|
||||||
// and the move degrades to a bump — nothing moves.
|
// axis, so its cell never clears and nothing moves.
|
||||||
let mut board = open_board(1, 4, (0, 3));
|
let mut board = open_board(1, 4, (0, 3));
|
||||||
builtin_at(&mut board, 0, 2, "hcrate");
|
builtin_at(&mut board, 0, 2, "hcrate");
|
||||||
let mut game = GameState::new(board);
|
let mut game = GameState::new(board);
|
||||||
@@ -150,7 +133,7 @@ fn hcrate_pushes_east_but_not_north() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn vcrate_pushes_north_but_not_east() {
|
fn vcrate_pushes_north_but_not_east() {
|
||||||
// Pushing north: Push(Vertical) allows it, so the VCrate slides.
|
// Pushing north: vcrate.rhai accepts the axis, so the VCrate slides.
|
||||||
let mut board = open_board(1, 4, (0, 3));
|
let mut board = open_board(1, 4, (0, 3));
|
||||||
builtin_at(&mut board, 0, 2, "vcrate");
|
builtin_at(&mut board, 0, 2, "vcrate");
|
||||||
let mut game = GameState::new(board);
|
let mut game = GameState::new(board);
|
||||||
@@ -161,7 +144,8 @@ fn vcrate_pushes_north_but_not_east() {
|
|||||||
assert!(is_builtin(&b, 0, 1, "vcrate"));
|
assert!(is_builtin(&b, 0, 1, "vcrate"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pushing east into a VCrate: blocked, nothing moves.
|
// Pushing east into a VCrate: its bump hook refuses the horizontal axis,
|
||||||
|
// so nothing moves.
|
||||||
let mut board = open_board(4, 1, (0, 0));
|
let mut board = open_board(4, 1, (0, 0));
|
||||||
builtin_at(&mut board, 1, 0, "vcrate");
|
builtin_at(&mut board, 1, 0, "vcrate");
|
||||||
let mut game = GameState::new(board);
|
let mut game = GameState::new(board);
|
||||||
|
|||||||
@@ -2,12 +2,11 @@ use super::{log_texts, scripts_from};
|
|||||||
use crate::board::Board;
|
use crate::board::Board;
|
||||||
use crate::board::tests::{object_at, open_board, plain_object_at, sensor_at};
|
use crate::board::tests::{object_at, open_board, plain_object_at, sensor_at};
|
||||||
use crate::game::{GameState, ScrollLine};
|
use crate::game::{GameState, ScrollLine};
|
||||||
use crate::tile::EnterResponse;
|
|
||||||
use crate::utils::{Direction, ObjectId, Point};
|
use crate::utils::{Direction, ObjectId, Point};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
/// Builds a 2×1 board with a single `Block` object at (0,0) that optionally
|
/// Builds a 2×1 board with a single immobile object at (0,0) that optionally
|
||||||
/// references a script, the player parked at (1,0), and the `(name, source)`
|
/// references a script, the player parked at (1,0), and the `(name, source)`
|
||||||
/// entries as a script map. Returns the object's id alongside them.
|
/// entries as a script map. Returns the object's id alongside them.
|
||||||
///
|
///
|
||||||
@@ -19,8 +18,8 @@ fn board_with_object(
|
|||||||
) -> (Board, HashMap<String, String>, ObjectId) {
|
) -> (Board, HashMap<String, String>, ObjectId) {
|
||||||
let mut board = open_board(2, 1, (1, 0));
|
let mut board = open_board(2, 1, (1, 0));
|
||||||
let id = match object_script {
|
let id = match object_script {
|
||||||
Some(name) => object_at(&mut board, 0, 0, name, EnterResponse::Block),
|
Some(name) => object_at(&mut board, 0, 0, name, false),
|
||||||
None => plain_object_at(&mut board, 0, 0, EnterResponse::Block),
|
None => plain_object_at(&mut board, 0, 0, false),
|
||||||
};
|
};
|
||||||
(board, scripts_from(scripts), id)
|
(board, scripts_from(scripts), id)
|
||||||
}
|
}
|
||||||
@@ -35,7 +34,7 @@ fn has_tag(game: &GameState, id: ObjectId, tag: &str) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The cell object `id` currently occupies.
|
/// The cell object `id` currently occupies.
|
||||||
fn loc(game: &GameState, id: ObjectId) -> (usize, usize) {
|
fn loc(game: &GameState, id: ObjectId) -> Point {
|
||||||
game.board()
|
game.board()
|
||||||
.get_hookable(id)
|
.get_hookable(id)
|
||||||
.expect("object still on the board")
|
.expect("object still on the board")
|
||||||
@@ -113,7 +112,7 @@ fn compile_and_unknown_script_errors_are_logged() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn script_reads_board_through_view() {
|
fn script_reads_board_through_view() {
|
||||||
let mut board = open_board(5, 3, (3, 1));
|
let mut board = open_board(5, 3, (3, 1));
|
||||||
object_at(&mut board, 2, 1, "r", EnterResponse::Block);
|
object_at(&mut board, 2, 1, "r", false);
|
||||||
let mut game = GameState::with_scripts(
|
let mut game = GameState::with_scripts(
|
||||||
board,
|
board,
|
||||||
scripts_from(&[("r", "fn init(me) { log(Player.x.to_string()); }")]),
|
scripts_from(&[("r", "fn init(me) { log(Player.x.to_string()); }")]),
|
||||||
@@ -127,8 +126,8 @@ fn commands_are_routed_to_their_own_source_object() {
|
|||||||
// Two objects with different scripts move in opposite directions; each must
|
// Two objects with different scripts move in opposite directions; each must
|
||||||
// affect only itself (the per-call tag routes the command to its source queue).
|
// affect only itself (the per-call tag routes the command to its source queue).
|
||||||
let mut board = open_board(5, 3, (0, 0));
|
let mut board = open_board(5, 3, (0, 0));
|
||||||
let east = object_at(&mut board, 0, 1, "e", EnterResponse::Block);
|
let east = object_at(&mut board, 0, 1, "e", false);
|
||||||
let west = object_at(&mut board, 4, 1, "w", EnterResponse::Block);
|
let west = object_at(&mut board, 4, 1, "w", false);
|
||||||
let mut game = GameState::with_scripts(
|
let mut game = GameState::with_scripts(
|
||||||
board,
|
board,
|
||||||
scripts_from(&[
|
scripts_from(&[
|
||||||
@@ -137,8 +136,8 @@ fn commands_are_routed_to_their_own_source_object() {
|
|||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
game.run_init();
|
game.run_init();
|
||||||
assert_eq!(loc(&game, east), (1, 1)); // moved east from 0
|
assert_eq!(loc(&game, east), Point { x: 1, y: 1 }); // moved east from 0
|
||||||
assert_eq!(loc(&game, west), (3, 1)); // moved west from 4
|
assert_eq!(loc(&game, west), Point { x: 3, y: 1 }); // moved west from 4
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -150,7 +149,7 @@ fn start_map_greeter_runs_init() {
|
|||||||
// Keep the failure message short: a TomlError's Display embeds the whole file.
|
// Keep the failure message short: a TomlError's Display embeds the whole file.
|
||||||
let mut world = crate::world::load(path).unwrap_or_else(|e| {
|
let mut world = crate::world::load(path).unwrap_or_else(|e| {
|
||||||
let first = e.to_string().lines().next().unwrap_or_default().to_string();
|
let first = e.to_string().lines().next().unwrap_or_default().to_string();
|
||||||
panic!("load start.toml failed (see todo.md #1, maps are still pre-BoardSpec): {first}")
|
panic!("load start.toml failed: {first}")
|
||||||
});
|
});
|
||||||
// Pin to the "start" board regardless of the world's current default entry point.
|
// Pin to the "start" board regardless of the world's current default entry point.
|
||||||
world.start = "start".to_string();
|
world.start = "start".to_string();
|
||||||
@@ -222,8 +221,8 @@ fn objects_with_tag_returns_matching_ids() {
|
|||||||
// A 5×1 board with two objects; one has tag "enemy". The script on the first
|
// A 5×1 board with two objects; one has tag "enemy". The script on the first
|
||||||
// queries Board.tagged("enemy") and logs the count and the id it found.
|
// queries Board.tagged("enemy") and logs the count and the id it found.
|
||||||
let mut board = open_board(5, 1, (4, 0));
|
let mut board = open_board(5, 1, (4, 0));
|
||||||
object_at(&mut board, 0, 0, "q", EnterResponse::Block);
|
object_at(&mut board, 0, 0, "q", false);
|
||||||
let enemy = object_at(&mut board, 1, 0, "none", EnterResponse::Block);
|
let enemy = object_at(&mut board, 1, 0, "none", false);
|
||||||
board
|
board
|
||||||
.scripting_mut(enemy)
|
.scripting_mut(enemy)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -274,8 +273,8 @@ fn object_id_for_name_finds_by_name() {
|
|||||||
// A board with two objects; one is named. The querying object uses Board.named
|
// A board with two objects; one is named. The querying object uses Board.named
|
||||||
// to find it and logs its id, then confirms a miss returns ().
|
// to find it and logs its id, then confirms a miss returns ().
|
||||||
let mut board = open_board(5, 1, (4, 0));
|
let mut board = open_board(5, 1, (4, 0));
|
||||||
object_at(&mut board, 0, 0, "q", EnterResponse::Block);
|
object_at(&mut board, 0, 0, "q", false);
|
||||||
let target = object_at(&mut board, 1, 0, "none", EnterResponse::Block);
|
let target = object_at(&mut board, 1, 0, "none", false);
|
||||||
board.scripting_mut(target).unwrap().name = Some("target".to_string());
|
board.scripting_mut(target).unwrap().name = Some("target".to_string());
|
||||||
let mut game = GameState::with_scripts(
|
let mut game = GameState::with_scripts(
|
||||||
board,
|
board,
|
||||||
@@ -303,10 +302,10 @@ fn object_id_for_name_finds_by_name() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn player_bump_reports_the_direction_it_came_from() {
|
fn player_bump_reports_the_direction_it_came_from() {
|
||||||
// The player walks East into a Block object; the object is bumped from the West
|
// The player walks East into a scripted solid; the object is bumped from the West
|
||||||
// side (opposite the player's travel) and the player does not move onto it.
|
// side (opposite the player's travel) and the player does not move onto it.
|
||||||
let mut board = open_board(3, 1, (0, 0));
|
let mut board = open_board(3, 1, (0, 0));
|
||||||
object_at(&mut board, 1, 0, "b", EnterResponse::Block);
|
object_at(&mut board, 1, 0, "b", false);
|
||||||
let mut game = GameState::with_scripts(
|
let mut game = GameState::with_scripts(
|
||||||
board,
|
board,
|
||||||
scripts_from(&[("b", "fn bump(me, dir) { log(`bumped from ${dir}`); }")]),
|
scripts_from(&[("b", "fn bump(me, dir) { log(`bumped from ${dir}`); }")]),
|
||||||
@@ -322,7 +321,7 @@ fn player_bump_reports_the_direction_it_came_from() {
|
|||||||
fn bump_direction_supports_comparison_and_offset() {
|
fn bump_direction_supports_comparison_and_offset() {
|
||||||
// A bump handler can compare the direction and read its dx/dy to locate the bumper.
|
// A bump handler can compare the direction and read its dx/dy to locate the bumper.
|
||||||
let mut board = open_board(3, 1, (0, 0));
|
let mut board = open_board(3, 1, (0, 0));
|
||||||
object_at(&mut board, 1, 0, "b", EnterResponse::Block);
|
object_at(&mut board, 1, 0, "b", false);
|
||||||
let mut game = GameState::with_scripts(
|
let mut game = GameState::with_scripts(
|
||||||
board,
|
board,
|
||||||
scripts_from(&[(
|
scripts_from(&[(
|
||||||
@@ -341,10 +340,10 @@ fn bump_direction_supports_comparison_and_offset() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn scroll_opens_on_player_bump() {
|
fn scroll_opens_on_player_bump() {
|
||||||
// A Block object's bump() calls scroll([text, [choice, display]]). The bump
|
// A solid object's bump() calls scroll([text, [choice, display]]). The bump
|
||||||
// resolves inside try_move, so active_scroll is set with the right variants.
|
// resolves inside try_move, so active_scroll is set with the right variants.
|
||||||
let mut board = open_board(3, 1, (0, 0));
|
let mut board = open_board(3, 1, (0, 0));
|
||||||
object_at(&mut board, 1, 0, "s", EnterResponse::Block);
|
object_at(&mut board, 1, 0, "s", false);
|
||||||
let mut game = GameState::with_scripts(
|
let mut game = GameState::with_scripts(
|
||||||
board,
|
board,
|
||||||
scripts_from(&[(
|
scripts_from(&[(
|
||||||
@@ -370,7 +369,7 @@ fn scroll_opens_on_player_bump() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn handle_scroll_without_choice_clears_it() {
|
fn handle_scroll_without_choice_clears_it() {
|
||||||
let mut board = open_board(3, 1, (0, 0));
|
let mut board = open_board(3, 1, (0, 0));
|
||||||
object_at(&mut board, 1, 0, "s", EnterResponse::Block);
|
object_at(&mut board, 1, 0, "s", false);
|
||||||
let mut game = GameState::with_scripts(
|
let mut game = GameState::with_scripts(
|
||||||
board,
|
board,
|
||||||
scripts_from(&[("s", r#"fn bump(me, dir) { scroll(["Hello"]); }"#)]),
|
scripts_from(&[("s", r#"fn bump(me, dir) { scroll(["Hello"]); }"#)]),
|
||||||
@@ -389,7 +388,7 @@ fn handle_scroll_with_choice_dispatches_send_to_source() {
|
|||||||
// Setting choice on the scroll before a tick fires send() on the source object;
|
// Setting choice on the scroll before a tick fires send() on the source object;
|
||||||
// fn eat() logging "eaten" confirms the dispatch arrived.
|
// fn eat() logging "eaten" confirms the dispatch arrived.
|
||||||
let mut board = open_board(3, 1, (0, 0));
|
let mut board = open_board(3, 1, (0, 0));
|
||||||
object_at(&mut board, 1, 0, "s", EnterResponse::Block);
|
object_at(&mut board, 1, 0, "s", false);
|
||||||
let mut game = GameState::with_scripts(
|
let mut game = GameState::with_scripts(
|
||||||
board,
|
board,
|
||||||
scripts_from(&[(
|
scripts_from(&[(
|
||||||
@@ -424,8 +423,8 @@ fn a_later_object_sees_an_earlier_objects_move_this_tick() {
|
|||||||
// this tick, B observes it as blocked — under a collect-all-then-apply model B
|
// this tick, B observes it as blocked — under a collect-all-then-apply model B
|
||||||
// would have seen (1,0) still empty.
|
// would have seen (1,0) still empty.
|
||||||
let mut board = open_board(3, 2, (2, 1));
|
let mut board = open_board(3, 2, (2, 1));
|
||||||
let a = object_at(&mut board, 0, 0, "a", EnterResponse::Block);
|
let a = object_at(&mut board, 0, 0, "a", false);
|
||||||
object_at(&mut board, 1, 1, "b", EnterResponse::Block);
|
object_at(&mut board, 1, 1, "b", false);
|
||||||
let mut game = GameState::with_scripts(
|
let mut game = GameState::with_scripts(
|
||||||
board,
|
board,
|
||||||
scripts_from(&[
|
scripts_from(&[
|
||||||
@@ -441,7 +440,7 @@ fn a_later_object_sees_an_earlier_objects_move_this_tick() {
|
|||||||
game.tick(Duration::from_millis(16));
|
game.tick(Duration::from_millis(16));
|
||||||
|
|
||||||
// A moved onto (1,0), and B saw it there the same tick.
|
// A moved onto (1,0), and B saw it there the same tick.
|
||||||
assert_eq!(loc(&game, a), (1, 0));
|
assert_eq!(loc(&game, a), Point { x: 1, y: 0 });
|
||||||
assert_eq!(log_texts(&game), vec!["blocked"]);
|
assert_eq!(log_texts(&game), vec!["blocked"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,7 +452,7 @@ fn tick_is_gated_on_an_empty_queue() {
|
|||||||
// draining the engine skips re-running `tick`. With 100 ms frames the object steps
|
// draining the engine skips re-running `tick`. With 100 ms frames the object steps
|
||||||
// once (x=1) then waits ~0.25s before the next call fires.
|
// once (x=1) then waits ~0.25s before the next call fires.
|
||||||
let mut board = open_board(6, 1, (5, 0));
|
let mut board = open_board(6, 1, (5, 0));
|
||||||
let id = object_at(&mut board, 0, 0, "m", EnterResponse::Block);
|
let id = object_at(&mut board, 0, 0, "m", false);
|
||||||
let mut game = GameState::with_scripts(
|
let mut game = GameState::with_scripts(
|
||||||
board,
|
board,
|
||||||
// Note: no `if me.queue.length == 0` guard — the engine provides it.
|
// Note: no `if me.queue.length == 0` guard — the engine provides it.
|
||||||
@@ -466,12 +465,12 @@ fn tick_is_gated_on_an_empty_queue() {
|
|||||||
for _ in 0..3 {
|
for _ in 0..3 {
|
||||||
game.tick(Duration::from_millis(100));
|
game.tick(Duration::from_millis(100));
|
||||||
}
|
}
|
||||||
assert_eq!(loc(&game, id).0, 1);
|
assert_eq!(loc(&game, id).x, 1);
|
||||||
|
|
||||||
// Once the Delay fully drains the queue empties, so the next frame calls `tick`
|
// Once the Delay fully drains the queue empties, so the next frame calls `tick`
|
||||||
// again and the object takes its second step.
|
// again and the object takes its second step.
|
||||||
game.tick(Duration::from_millis(100));
|
game.tick(Duration::from_millis(100));
|
||||||
assert_eq!(loc(&game, id).0, 2);
|
assert_eq!(loc(&game, id).x, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -487,8 +486,8 @@ fn a_send_cycle_defers_instead_of_recursing() {
|
|||||||
// recurse until the stack blows. That this test *returns at all* is half the
|
// recurse until the stack blows. That this test *returns at all* is half the
|
||||||
// assertion; the log growth is the other half.
|
// assertion; the log growth is the other half.
|
||||||
let mut board = open_board(3, 1, (2, 0));
|
let mut board = open_board(3, 1, (2, 0));
|
||||||
let a = object_at(&mut board, 0, 0, "a", EnterResponse::Block);
|
let a = object_at(&mut board, 0, 0, "a", false);
|
||||||
let b = object_at(&mut board, 1, 0, "b", EnterResponse::Block);
|
let b = object_at(&mut board, 1, 0, "b", false);
|
||||||
let mut game = GameState::with_scripts(
|
let mut game = GameState::with_scripts(
|
||||||
board,
|
board,
|
||||||
scripts_from(&[
|
scripts_from(&[
|
||||||
|
|||||||
@@ -6,36 +6,6 @@ use serde::{Deserialize, Serialize};
|
|||||||
use crate::log::LogLine;
|
use crate::log::LogLine;
|
||||||
use crate::script::Registerable;
|
use crate::script::Registerable;
|
||||||
|
|
||||||
/// Which directions a solid may be pushed in.
|
|
||||||
///
|
|
||||||
/// Only meaningful for `solid` cells (a non-solid cell is passable, so nothing is
|
|
||||||
/// ever pushed into it). `Crate` is [`Pushable::Any`]; the directional crates
|
|
||||||
/// constrain pushes to one axis.
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "lowercase")]
|
|
||||||
pub enum Pushable {
|
|
||||||
/// Cannot be pushed.
|
|
||||||
No,
|
|
||||||
/// Pushable in any direction.
|
|
||||||
Any,
|
|
||||||
/// Pushable east/west only.
|
|
||||||
Horizontal,
|
|
||||||
/// Pushable north/south only.
|
|
||||||
Vertical,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pushable {
|
|
||||||
/// Whether a push in `dir` is allowed.
|
|
||||||
pub fn allows(self, dir: Direction) -> bool {
|
|
||||||
match self {
|
|
||||||
Pushable::No => false,
|
|
||||||
Pushable::Any => true,
|
|
||||||
Pushable::Horizontal => matches!(dir, Direction::East | Direction::West),
|
|
||||||
Pushable::Vertical => matches!(dir, Direction::North | Direction::South),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A stable, unique identifier for a board object.
|
/// A stable, unique identifier for a board object.
|
||||||
///
|
///
|
||||||
/// Ids are handed out by [`Board::add_object`] from the per-board
|
/// Ids are handed out by [`Board::add_object`] from the per-board
|
||||||
@@ -303,30 +273,3 @@ impl LogSink {
|
|||||||
std::mem::take(&mut self.0.borrow_mut())
|
std::mem::take(&mut self.0.borrow_mut())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::Pushable;
|
|
||||||
use crate::utils::Direction;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn pushable_allows_only_its_axis() {
|
|
||||||
for d in [
|
|
||||||
Direction::North,
|
|
||||||
Direction::South,
|
|
||||||
Direction::East,
|
|
||||||
Direction::West,
|
|
||||||
] {
|
|
||||||
assert!(!Pushable::No.allows(d));
|
|
||||||
assert!(Pushable::Any.allows(d));
|
|
||||||
}
|
|
||||||
assert!(Pushable::Horizontal.allows(Direction::East));
|
|
||||||
assert!(Pushable::Horizontal.allows(Direction::West));
|
|
||||||
assert!(!Pushable::Horizontal.allows(Direction::North));
|
|
||||||
assert!(!Pushable::Horizontal.allows(Direction::South));
|
|
||||||
assert!(Pushable::Vertical.allows(Direction::North));
|
|
||||||
assert!(Pushable::Vertical.allows(Direction::South));
|
|
||||||
assert!(!Pushable::Vertical.allows(Direction::East));
|
|
||||||
assert!(!Pushable::Vertical.allows(Direction::West));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user