From 0f045c39a3bf2b4f75b0a3568b10af4353a24c26 Mon Sep 17 00:00:00 2001 From: Ross Andrews Date: Tue, 11 Aug 2026 23:01:12 -0500 Subject: [PATCH] wip 6 fixed tests --- kiln-core/src/board.rs | 77 ++--------------------------- kiln-core/src/builtin.rs | 1 - kiln-core/src/game.rs | 6 +-- kiln-core/src/tests/actions.rs | 52 ++++++++++--------- kiln-core/src/tests/game_portals.rs | 3 +- kiln-core/src/tests/mod.rs | 3 +- kiln-core/src/tests/movement.rs | 46 ++++++----------- kiln-core/src/tests/scripting.rs | 59 +++++++++++----------- kiln-core/src/utils.rs | 57 --------------------- 9 files changed, 77 insertions(+), 227 deletions(-) diff --git a/kiln-core/src/board.rs b/kiln-core/src/board.rs index cba2229..cf598b4 100644 --- a/kiln-core/src/board.rs +++ b/kiln-core/src/board.rs @@ -501,12 +501,11 @@ impl Board { #[cfg(test)] pub(crate) mod tests { - use std::assert_matches; use super::Board; use crate::builtin::Builtin; use crate::floor::Floor; use crate::glyph::Glyph; - use crate::utils::{Direction, ObjectId, Point}; + use crate::utils::ObjectId; use color::Rgba8; use std::collections::HashMap; use crate::object_def::ObjectDef; @@ -703,76 +702,6 @@ pub(crate) mod tests { 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] fn glyph_at_uses_floor_for_empty_and_grid_for_solid() { // Player parked at (2,0) so it doesn't overlap either asserted cell. @@ -812,7 +741,7 @@ pub(crate) mod tests { #[test] 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. // Solids on the *other* side of the empty (outside the blocked region) still move. let mut board = open_board(6, 1, (5, 0)); @@ -823,7 +752,7 @@ pub(crate) mod tests { 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) + // immobile = {2} (the wall: Tile::shiftable() is false) // 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 diff --git a/kiln-core/src/builtin.rs b/kiln-core/src/builtin.rs index 14c27f9..84f6f6c 100644 --- a/kiln-core/src/builtin.rs +++ b/kiln-core/src/builtin.rs @@ -1,6 +1,5 @@ use std::collections::HashMap; use crate::glyph::Glyph; -use crate::utils::Pushable; use color::Rgba8; use lazy_static::lazy_static; use crate::keys::KeyType; diff --git a/kiln-core/src/game.rs b/kiln-core/src/game.rs index 9e86c3e..1ba0fbc 100644 --- a/kiln-core/src/game.rs +++ b/kiln-core/src/game.rs @@ -483,9 +483,9 @@ mod tests { #[test] fn walking_onto_a_gem_grabs_it() { - // A gem builtin object at (1,0), running scripts/gem.rhai. Its - // EnterResponse::Grab means the player's step fires grab() rather than being - // blocked. The player starts at (0,0). + // A gem builtin object at (1,0), running scripts/gem.rhai. Its bump hook + // banks the gem and calls die(), so the cell clears and the player's step + // completes instead of being blocked. The player starts at (0,0). let mut board = open_board(3, 1, (0, 0)); gem_at(&mut board, 1, 0); let mut game = GameState::new(board); diff --git a/kiln-core/src/tests/actions.rs b/kiln-core/src/tests/actions.rs index 243776d..a63aa4a 100644 --- a/kiln-core/src/tests/actions.rs +++ b/kiln-core/src/tests/actions.rs @@ -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::game::GameState; use crate::glyph::Glyph; -use crate::tile::EnterResponse; use crate::utils::{ObjectId, Point}; use std::time::Duration; /// The cell object `id` currently occupies. -fn loc(game: &GameState, id: ObjectId) -> (usize, usize) { +fn loc(game: &GameState, id: ObjectId) -> Point { game.board() .get_hookable(id) .expect("object still on the board") @@ -22,13 +21,12 @@ fn glyph(game: &GameState, id: ObjectId) -> 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()`. /// -/// The mover is `EnterResponse::Block` — an ordinary solid that blocks others and -/// cannot be shoved, which is what a self-propelled scripted object should be. Its -/// `enter_response` describes how it answers *others* entering its cell and should -/// not constrain its own `move()`. +/// The mover is `mobile = false`: nothing else may relocate it (a `shift` skips it, +/// and it has no `bump` hook to shove itself aside). That flag constrains only what +/// *others* can do to it — it never restricts the object's own `move()`. fn game_with_mover( w: usize, h: usize, @@ -37,7 +35,7 @@ fn game_with_mover( src: &str, ) -> (GameState, ObjectId) { 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)])); game.run_init(); (game, id) @@ -47,23 +45,23 @@ fn game_with_mover( fn move_command_relocates_the_source_object() { 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). - assert_eq!(loc(&game, id), (3, 1)); + assert_eq!(loc(&game, id), Point { x: 3, y: 1 }); } #[test] fn move_into_a_wall_or_edge_is_a_noop() { // 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); }"); - assert_eq!(loc(&game, id), (0, 1)); + assert_eq!(loc(&game, id), Point { x: 0, y: 1 }); // Object facing a wall: blocked, also ignored. 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); let mut game = GameState::with_scripts(board, scripts_from(&[("m", "fn init(me) { move(East); }")])); game.run_init(); - assert_eq!(loc(&game, id), (1, 1)); + assert_eq!(loc(&game, id), Point { x: 1, y: 1 }); } #[test] @@ -76,13 +74,13 @@ fn set_tile_command_changes_the_source_glyph() { fn object_pushes_crate_on_init() { // A scripted object moving east into a crate shoves it (the step_object path). 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); let mut game = GameState::with_scripts(board, scripts_from(&[("m", "fn init(me) { move(East); }")])); game.run_init(); - assert_eq!(loc(&game, id), (2, 0)); + assert_eq!(loc(&game, id), Point { x: 2, y: 0 }); let b = game.board(); 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 @@ -92,18 +90,18 @@ fn object_pushes_crate_on_init() { fn object_push_into_player() { // 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); }"); - 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 }); // With a wall behind the player, the object is blocked and nothing moves. 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); let mut game = GameState::with_scripts(board, scripts_from(&[("m", "fn init(me) { move(East); }")])); 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 }); } @@ -118,16 +116,16 @@ fn move_cost_rate_limits_repeated_moves() { (1, 0), "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. 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. game.tick(Duration::from_millis(100)); - assert_eq!(loc(&game, id).0, 3); + assert_eq!(loc(&game, id).x, 3); } #[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 // 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 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); let mut game = GameState::with_scripts( board, @@ -143,16 +141,16 @@ fn inline_delay_paces_subsequent_moves() { ); game.run_init(); // 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. 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. game.tick(Duration::from_millis(100)); - assert_eq!(loc(&game, id), (1, 2)); + assert_eq!(loc(&game, id), Point { x: 1, y: 2 }); } #[test] @@ -181,7 +179,7 @@ fn queue_clear_drops_pending_actions() { "fn init(me) { move(East); move(East); me.queue.clear(); }", ); 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] @@ -190,7 +188,7 @@ fn blocked_reports_solid_and_clear() { // Solid ahead (a wall): blocked() is true. 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); let mut game = GameState::with_scripts(board, scripts_from(&[("b", src)])); game.run_init(); @@ -198,7 +196,7 @@ fn blocked_reports_solid_and_clear() { // Open ahead, nothing pending: blocked() is false. 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)])); game.run_init(); assert_eq!(glyph(&game, id).tile, 'N'); diff --git a/kiln-core/src/tests/game_portals.rs b/kiln-core/src/tests/game_portals.rs index 77a2624..64e29c6 100644 --- a/kiln-core/src/tests/game_portals.rs +++ b/kiln-core/src/tests/game_portals.rs @@ -30,8 +30,7 @@ fn portal( target_name: &str, ) -> Portal { Portal { - x, - y, + location: (x, y).into(), name: name.to_string(), target_board: target_board.to_string(), target_name: target_name.to_string(), diff --git a/kiln-core/src/tests/mod.rs b/kiln-core/src/tests/mod.rs index 604ffe8..feda5a7 100644 --- a/kiln-core/src/tests/mod.rs +++ b/kiln-core/src/tests/mod.rs @@ -1,7 +1,6 @@ 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; +// TODO(migration): the map_file suite predates BoardSpec and is not ported yet. // mod map_file; mod movement; mod scripting; diff --git a/kiln-core/src/tests/movement.rs b/kiln-core/src/tests/movement.rs index 8c5eaf4..ab21a95 100644 --- a/kiln-core/src/tests/movement.rs +++ b/kiln-core/src/tests/movement.rs @@ -1,16 +1,17 @@ //! Player push mechanics, driven through [`GameState::try_move`]. //! -//! Pushability now lives on the target's [`EnterResponse`]: `Push(Pushable::Any)` -//! for a crate, `Push(Horizontal)`/`Push(Vertical)` for the axis-locked ones, and -//! `Block` for a wall. `try_move` consults `Board::can_push` and, when the chain -//! can move, pushes from the *player's* cell — the player is itself pushable, so it -//! rides along at the head of the chain. +//! Pushing is not a board primitive: [`GameState::resolve_move`] just fires the +//! target's `bump` hook and then re-checks whether the cell it wanted has become +//! free. A crate slides because `crate.rhai`'s `bump` pushes the cell ahead of it +//! and then teleports itself into the space; an `hcrate`/`vcrate` returns early +//! 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::glyph::Glyph; -use crate::tile::EnterResponse; -use crate::utils::{Direction, Point, Pushable}; +use crate::utils::{Direction, Point}; use color::Rgba8; #[test] @@ -105,27 +106,9 @@ fn cascade_blocked_by_wall_moves_nothing() { 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] 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)); builtin_at(&mut board, 1, 0, "hcrate"); let mut game = GameState::new(board); @@ -136,8 +119,8 @@ fn hcrate_pushes_east_but_not_north() { assert!(is_builtin(&b, 2, 0, "hcrate")); } - // Pushing north into an HCrate: the direction isn't allowed, so can_push fails - // and the move degrades to a bump — nothing moves. + // Pushing north into an HCrate: its bump hook returns early on the vertical + // axis, so its cell never clears and nothing moves. let mut board = open_board(1, 4, (0, 3)); builtin_at(&mut board, 0, 2, "hcrate"); let mut game = GameState::new(board); @@ -150,7 +133,7 @@ fn hcrate_pushes_east_but_not_north() { #[test] 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)); builtin_at(&mut board, 0, 2, "vcrate"); let mut game = GameState::new(board); @@ -161,7 +144,8 @@ fn vcrate_pushes_north_but_not_east() { 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)); builtin_at(&mut board, 1, 0, "vcrate"); let mut game = GameState::new(board); diff --git a/kiln-core/src/tests/scripting.rs b/kiln-core/src/tests/scripting.rs index 34ff1f9..d17ce27 100644 --- a/kiln-core/src/tests/scripting.rs +++ b/kiln-core/src/tests/scripting.rs @@ -2,12 +2,11 @@ use super::{log_texts, scripts_from}; use crate::board::Board; use crate::board::tests::{object_at, open_board, plain_object_at, sensor_at}; use crate::game::{GameState, ScrollLine}; -use crate::tile::EnterResponse; use crate::utils::{Direction, ObjectId, Point}; use std::collections::HashMap; 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)` /// entries as a script map. Returns the object's id alongside them. /// @@ -19,8 +18,8 @@ fn board_with_object( ) -> (Board, HashMap, ObjectId) { let mut board = open_board(2, 1, (1, 0)); let id = match object_script { - Some(name) => object_at(&mut board, 0, 0, name, EnterResponse::Block), - None => plain_object_at(&mut board, 0, 0, EnterResponse::Block), + Some(name) => object_at(&mut board, 0, 0, name, false), + None => plain_object_at(&mut board, 0, 0, false), }; (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. -fn loc(game: &GameState, id: ObjectId) -> (usize, usize) { +fn loc(game: &GameState, id: ObjectId) -> Point { game.board() .get_hookable(id) .expect("object still on the board") @@ -113,7 +112,7 @@ fn compile_and_unknown_script_errors_are_logged() { #[test] fn script_reads_board_through_view() { 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( board, 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 // affect only itself (the per-call tag routes the command to its source queue). let mut board = open_board(5, 3, (0, 0)); - let east = object_at(&mut board, 0, 1, "e", EnterResponse::Block); - let west = object_at(&mut board, 4, 1, "w", EnterResponse::Block); + let east = object_at(&mut board, 0, 1, "e", false); + let west = object_at(&mut board, 4, 1, "w", false); let mut game = GameState::with_scripts( board, scripts_from(&[ @@ -137,8 +136,8 @@ fn commands_are_routed_to_their_own_source_object() { ]), ); game.run_init(); - assert_eq!(loc(&game, east), (1, 1)); // moved east from 0 - assert_eq!(loc(&game, west), (3, 1)); // moved west from 4 + assert_eq!(loc(&game, east), Point { x: 1, y: 1 }); // moved east from 0 + assert_eq!(loc(&game, west), Point { x: 3, y: 1 }); // moved west from 4 } #[test] @@ -150,7 +149,7 @@ fn start_map_greeter_runs_init() { // 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 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. 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 // queries Board.tagged("enemy") and logs the count and the id it found. let mut board = open_board(5, 1, (4, 0)); - object_at(&mut board, 0, 0, "q", EnterResponse::Block); - let enemy = object_at(&mut board, 1, 0, "none", EnterResponse::Block); + object_at(&mut board, 0, 0, "q", false); + let enemy = object_at(&mut board, 1, 0, "none", false); board .scripting_mut(enemy) .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 // to find it and logs its id, then confirms a miss returns (). let mut board = open_board(5, 1, (4, 0)); - object_at(&mut board, 0, 0, "q", EnterResponse::Block); - let target = object_at(&mut board, 1, 0, "none", EnterResponse::Block); + object_at(&mut board, 0, 0, "q", false); + let target = object_at(&mut board, 1, 0, "none", false); board.scripting_mut(target).unwrap().name = Some("target".to_string()); let mut game = GameState::with_scripts( board, @@ -303,10 +302,10 @@ fn object_id_for_name_finds_by_name() { #[test] 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. 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( board, 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() { // 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)); - object_at(&mut board, 1, 0, "b", EnterResponse::Block); + object_at(&mut board, 1, 0, "b", false); let mut game = GameState::with_scripts( board, scripts_from(&[( @@ -341,10 +340,10 @@ fn bump_direction_supports_comparison_and_offset() { #[test] 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. 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( board, scripts_from(&[( @@ -370,7 +369,7 @@ fn scroll_opens_on_player_bump() { #[test] fn handle_scroll_without_choice_clears_it() { 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( board, 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; // fn eat() logging "eaten" confirms the dispatch arrived. 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( board, 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 // would have seen (1,0) still empty. let mut board = open_board(3, 2, (2, 1)); - let a = object_at(&mut board, 0, 0, "a", EnterResponse::Block); - object_at(&mut board, 1, 1, "b", EnterResponse::Block); + let a = object_at(&mut board, 0, 0, "a", false); + object_at(&mut board, 1, 1, "b", false); let mut game = GameState::with_scripts( board, scripts_from(&[ @@ -441,7 +440,7 @@ fn a_later_object_sees_an_earlier_objects_move_this_tick() { game.tick(Duration::from_millis(16)); // 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"]); } @@ -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 // once (x=1) then waits ~0.25s before the next call fires. 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( board, // 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 { 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` // again and the object takes its second step. game.tick(Duration::from_millis(100)); - assert_eq!(loc(&game, id).0, 2); + assert_eq!(loc(&game, id).x, 2); } #[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 // assertion; the log growth is the other half. let mut board = open_board(3, 1, (2, 0)); - let a = object_at(&mut board, 0, 0, "a", EnterResponse::Block); - let b = object_at(&mut board, 1, 0, "b", EnterResponse::Block); + let a = object_at(&mut board, 0, 0, "a", false); + let b = object_at(&mut board, 1, 0, "b", false); let mut game = GameState::with_scripts( board, scripts_from(&[ diff --git a/kiln-core/src/utils.rs b/kiln-core/src/utils.rs index 85c87e2..6216b0e 100644 --- a/kiln-core/src/utils.rs +++ b/kiln-core/src/utils.rs @@ -6,36 +6,6 @@ use serde::{Deserialize, Serialize}; use crate::log::LogLine; 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. /// /// 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()) } } - -#[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)); - } -}