enter hook

This commit is contained in:
2026-07-11 11:47:45 -05:00
parent cdeae455dc
commit b1b723fd1b
10 changed files with 371 additions and 43 deletions
+8
View File
@@ -59,6 +59,14 @@ fn scripted_object(x: usize, y: usize, script: &str) -> ObjectDef {
o
}
/// Returns a **non-solid** `ObjectDef` at `(x, y)` bound to the named script — the
/// kind that receives an `enter` hook when a solid relocates onto its cell.
fn nonsolid_object(x: usize, y: usize, script: &str) -> ObjectDef {
let mut o = scripted_object(x, y, script);
o.solid = false;
o
}
/// Flattens each log line into a single string for easy assertions.
fn log_texts(game: &GameState) -> Vec<String> {
game.log
+111 -2
View File
@@ -1,5 +1,5 @@
use super::{board_with_object, log_texts, scripted_object, scripts_from};
use crate::board::tests::open_board;
use super::{board_with_object, log_texts, nonsolid_object, scripted_object, scripts_from};
use crate::board::tests::{crate_at, open_board};
use crate::game::{GameState, ScrollLine};
use crate::utils::Direction;
use std::time::Duration;
@@ -406,3 +406,112 @@ fn a_send_cycle_terminates_via_the_called_guard() {
// A.poke's re-send to B.poke is a repeat key and is skipped, so the cascade stops.
assert_eq!(log_texts(&game), vec!["B", "A"]);
}
// ── enter hook ──────────────────────────────────────────────────────────────
// `enter(me, dir)` fires on a non-solid object when a solid relocates onto its
// cell. `dir` is the side the entrant came from (opposite the travel direction
// for a cardinal move/push; best-effort for teleport/shift).
#[test]
fn player_walking_onto_a_nonsolid_fires_enter_from_the_travel_side() {
// The player walks East onto a non-solid trigger; it entered from the West.
let board = open_board(3, 1, (0, 0), vec![nonsolid_object(1, 0, "e")]);
let mut game = GameState::with_scripts(
board,
scripts_from(&[("e", "fn enter(m, dir) { log(`entered from ${dir}`); }")]),
);
game.run_init();
game.try_move(Direction::East);
// The player is not blocked by a non-solid — it moves onto the cell.
assert_eq!((game.board().player.x, game.board().player.y), (1, 0));
assert!(log_texts(&game).iter().any(|t| t == "entered from West"));
}
#[test]
fn object_moving_onto_a_nonsolid_fires_enter() {
// A solid mover ticks East onto a non-solid trigger sharing the destination.
let mover = scripted_object(0, 0, "m");
let trigger = nonsolid_object(1, 0, "e");
let board = open_board(3, 1, (2, 0), vec![mover, trigger]);
let mut game = GameState::with_scripts(
board,
scripts_from(&[
("m", "fn tick(m, dt) { if m.queue.length == 0 { move(East); } }"),
("e", "fn enter(m, dir) { log(`entered from ${dir}`); }"),
]),
);
game.run_init();
game.tick(Duration::from_millis(16));
assert!(log_texts(&game).iter().any(|t| t == "entered from West"));
}
#[test]
fn pushing_a_crate_onto_a_nonsolid_fires_enter() {
// Player pushes a crate East onto a non-solid trigger's cell; the crate is the
// solid entrant, so `enter` fires (from West).
let mut board = open_board(4, 1, (0, 0), vec![nonsolid_object(2, 0, "e")]);
crate_at(&mut board, 1, 0);
let mut game = GameState::with_scripts(
board,
scripts_from(&[("e", "fn enter(m, dir) { log(`entered from ${dir}`); }")]),
);
game.run_init();
game.try_move(Direction::East);
assert!(log_texts(&game).iter().any(|t| t == "entered from West"));
}
#[test]
fn teleporting_onto_a_nonsolid_fires_enter() {
// A solid object teleports itself from (0,0) onto the non-solid trigger at (1,0).
let mover = scripted_object(0, 0, "t");
let trigger = nonsolid_object(1, 0, "e");
let board = open_board(3, 1, (2, 0), vec![mover, trigger]);
let mut game = GameState::with_scripts(
board,
scripts_from(&[
("t", "fn init(m) { teleport(m.id, 1, 0); }"),
("e", "fn enter(m, dir) { log(`entered from ${dir}`); }"),
]),
);
game.run_init();
// Best-effort direction: the jump was one cell east, so it entered from West.
assert!(log_texts(&game).iter().any(|t| t == "entered from West"));
}
#[test]
fn shifting_a_crate_onto_a_nonsolid_fires_enter() {
// An object shifts the ring [(1,0),(2,0)]: the crate at (1,0) rotates onto the
// non-solid trigger at (2,0), firing `enter` (best-effort West).
let shifter = nonsolid_object(0, 0, "s");
let trigger = nonsolid_object(2, 0, "e");
let mut board = open_board(4, 1, (3, 0), vec![shifter, trigger]);
crate_at(&mut board, 1, 0);
let mut game = GameState::with_scripts(
board,
scripts_from(&[
("s", "fn init(m) { shift([[1, 0], [2, 0]]); }"),
("e", "fn enter(m, dir) { log(`entered from ${dir}`); }"),
]),
);
game.run_init();
assert!(log_texts(&game).iter().any(|t| t == "entered from West"));
}
#[test]
fn a_nonsolid_mover_onto_a_nonsolid_does_not_fire_enter() {
// Only *solid* entrants trigger enter: a non-solid mover walking onto another
// non-solid must NOT fire it.
let mover = nonsolid_object(0, 0, "m");
let trigger = nonsolid_object(1, 0, "e");
let board = open_board(3, 1, (2, 0), vec![mover, trigger]);
let mut game = GameState::with_scripts(
board,
scripts_from(&[
("m", "fn tick(m, dt) { if m.queue.length == 0 { move(East); } }"),
("e", "fn enter(m, dir) { log(`entered from ${dir}`); }"),
]),
);
game.run_init();
game.tick(Duration::from_millis(16));
assert!(!log_texts(&game).iter().any(|t| t.starts_with("entered")));
}