multi board

This commit is contained in:
2026-06-13 01:25:58 -05:00
parent e5c6affc41
commit 73c8a9bd3e
13 changed files with 456 additions and 445 deletions
+38 -114
View File
@@ -2,18 +2,12 @@ use std::time::Duration;
use crate::archetype::Archetype;
use crate::board::tests::{crate_at, open_board, wall_at};
use crate::game::GameState;
use super::{scripted_object, log_texts};
use super::{scripted_object, log_texts, scripts_from};
#[test]
fn move_command_relocates_the_source_object() {
let board = open_board(
5,
3,
(0, 0),
vec![scripted_object(2, 1, "m")],
&[("m", "fn init() { move(East); }")],
);
let mut game = GameState::new(board);
let board = open_board(5, 3, (0, 0), vec![scripted_object(2, 1, "m")]);
let mut game = GameState::with_scripts(board, scripts_from(&[("m", "fn init() { move(East); }")]));
game.run_init();
let b = game.board();
// East increments x by one; the object started at (2, 1).
@@ -23,28 +17,16 @@ fn move_command_relocates_the_source_object() {
#[test]
fn move_into_a_wall_or_edge_is_a_noop() {
// Object at the west edge moving west: out of bounds, ignored.
let board = open_board(
5,
3,
(0, 0),
vec![scripted_object(0, 1, "m")],
&[("m", "fn init() { move(West); }")],
);
let mut game = GameState::new(board);
let board = open_board(5, 3, (0, 0), vec![scripted_object(0, 1, "m")]);
let mut game = GameState::with_scripts(board, scripts_from(&[("m", "fn init() { move(West); }")]));
game.run_init();
assert_eq!(game.board().objects[&1].x, 0);
}
#[test]
fn set_tile_command_changes_the_source_glyph() {
let board = open_board(
5,
3,
(0, 0),
vec![scripted_object(2, 1, "s")],
&[("s", "fn init() { set_tile(7); }")],
);
let mut game = GameState::new(board);
let board = open_board(5, 3, (0, 0), vec![scripted_object(2, 1, "s")]);
let mut game = GameState::with_scripts(board, scripts_from(&[("s", "fn init() { set_tile(7); }")]));
game.run_init();
assert_eq!(game.board().objects[&1].glyph.tile, 7);
}
@@ -52,15 +34,9 @@ fn set_tile_command_changes_the_source_glyph() {
#[test]
fn object_pushes_crate_on_init() {
// A scripted object moving east into a crate shoves it (step_object path).
let mut board = open_board(
4,
1,
(0, 0),
vec![scripted_object(1, 0, "m")],
&[("m", "fn init() { move(East); }")],
);
let mut board = open_board(4, 1, (0, 0), vec![scripted_object(1, 0, "m")]);
crate_at(&mut board, 2, 0);
let mut game = GameState::new(board);
let mut game = GameState::with_scripts(board, scripts_from(&[("m", "fn init() { move(East); }")]));
game.run_init();
let b = game.board();
assert_eq!((b.objects[&1].x, b.objects[&1].y), (2, 0));
@@ -71,14 +47,8 @@ fn object_pushes_crate_on_init() {
#[test]
fn object_push_into_player() {
// A scripted object moving into the player pushes the player when there's room.
let board = open_board(
5,
1,
(2, 0),
vec![scripted_object(1, 0, "m")],
&[("m", "fn init() { move(East); }")],
);
let mut game = GameState::new(board);
let board = open_board(5, 1, (2, 0), vec![scripted_object(1, 0, "m")]);
let mut game = GameState::with_scripts(board, scripts_from(&[("m", "fn init() { move(East); }")]));
game.run_init();
{
let b = game.board();
@@ -87,15 +57,9 @@ fn object_push_into_player() {
}
// With a wall behind the player, the object is blocked and nothing moves.
let mut board = open_board(
4,
1,
(2, 0),
vec![scripted_object(1, 0, "m")],
&[("m", "fn init() { move(East); }")],
);
let mut board = open_board(4, 1, (2, 0), vec![scripted_object(1, 0, "m")]);
wall_at(&mut board, 3, 0);
let mut game = GameState::new(board);
let mut game = GameState::with_scripts(board, scripts_from(&[("m", "fn init() { move(East); }")]));
game.run_init();
let b = game.board();
assert_eq!((b.objects[&1].x, b.objects[&1].y), (1, 0));
@@ -106,14 +70,8 @@ fn object_push_into_player() {
fn move_cost_rate_limits_repeated_moves() {
// init queues two moves; only the first resolves immediately, the second must
// wait the full 250 ms cooldown.
let board = open_board(
5,
1,
(0, 0),
vec![scripted_object(1, 0, "m")],
&[("m", "fn init() { move(East); move(East); }")],
);
let mut game = GameState::new(board);
let board = open_board(5, 1, (0, 0), vec![scripted_object(1, 0, "m")]);
let mut game = GameState::with_scripts(board, scripts_from(&[("m", "fn init() { move(East); move(East); }")]));
game.run_init();
assert_eq!(game.board().objects[&1].x, 2); // first move applied (1 -> 2)
@@ -131,15 +89,9 @@ fn move_cost_rate_limits_repeated_moves() {
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),
vec![scripted_object(1, 1, "m")],
&[("m", "fn init() { move(East); move(South); }")],
);
let mut board = open_board(3, 3, (0, 0), vec![scripted_object(1, 1, "m")]);
wall_at(&mut board, 2, 1);
let mut game = GameState::new(board);
let mut game = GameState::with_scripts(board, scripts_from(&[("m", "fn init() { move(East); move(South); }")]));
game.run_init();
// First (eastward) move is blocked by the wall: object hasn't moved.
assert_eq!((game.board().objects[&1].x, game.board().objects[&1].y), (1, 1));
@@ -158,17 +110,11 @@ fn inline_delay_paces_subsequent_moves() {
fn queue_length_reports_pending_actions() {
// Two zero-cost actions are queued before the length is read, then all three
// (incl. the log) drain in one pump.
let board = open_board(
3,
1,
(0, 0),
vec![scripted_object(1, 0, "q")],
&[(
"q",
"fn init() { set_tile(5); set_tile(6); log(`len=${Queue.length()}`); }",
)],
);
let mut game = GameState::new(board);
let board = open_board(3, 1, (0, 0), vec![scripted_object(1, 0, "q")]);
let mut game = GameState::with_scripts(board, scripts_from(&[(
"q",
"fn init() { set_tile(5); set_tile(6); log(`len=${Queue.length()}`); }",
)]));
game.run_init();
assert!(log_texts(&game).iter().any(|t| t == "len=2"));
assert_eq!(game.board().objects[&1].glyph.tile, 6); // last set_tile won
@@ -177,14 +123,8 @@ fn queue_length_reports_pending_actions() {
#[test]
fn queue_clear_drops_pending_actions() {
// clear() empties the output queue mid-script, so the queued moves never run.
let board = open_board(
5,
1,
(0, 0),
vec![scripted_object(1, 0, "c")],
&[("c", "fn init() { move(East); move(East); Queue.clear(); }")],
);
let mut game = GameState::new(board);
let board = open_board(5, 1, (0, 0), vec![scripted_object(1, 0, "c")]);
let mut game = GameState::with_scripts(board, scripts_from(&[("c", "fn init() { move(East); move(East); Queue.clear(); }")]));
game.run_init();
game.tick(Duration::from_millis(300));
assert_eq!(game.board().objects[&1].x, 1); // never moved
@@ -193,33 +133,21 @@ fn queue_clear_drops_pending_actions() {
#[test]
fn blocked_reports_solid_and_clear() {
// Solid ahead (a wall): blocked() is true.
let mut board = open_board(
3,
1,
(0, 0),
vec![scripted_object(1, 0, "b")],
&[(
"b",
"fn init() { if blocked(East) { set_tile(9); } else { set_tile(7); } }",
)],
);
let mut board = open_board(3, 1, (0, 0), vec![scripted_object(1, 0, "b")]);
wall_at(&mut board, 2, 0);
let mut game = GameState::new(board);
let mut game = GameState::with_scripts(board, scripts_from(&[(
"b",
"fn init() { if blocked(East) { set_tile(9); } else { set_tile(7); } }",
)]));
game.run_init();
assert_eq!(game.board().objects[&1].glyph.tile, 9);
// Open ahead, nothing pending: blocked() is false.
let board = open_board(
3,
1,
(0, 0),
vec![scripted_object(1, 0, "b")],
&[(
"b",
"fn init() { if blocked(East) { set_tile(9); } else { set_tile(7); } }",
)],
);
let mut game = GameState::new(board);
let board = open_board(3, 1, (0, 0), vec![scripted_object(1, 0, "b")]);
let mut game = GameState::with_scripts(board, scripts_from(&[(
"b",
"fn init() { if blocked(East) { set_tile(9); } else { set_tile(7); } }",
)]));
game.run_init();
assert_eq!(game.board().objects[&1].glyph.tile, 7);
}
@@ -236,15 +164,11 @@ fn blocked_sees_earlier_objects_pending_move() {
scripted_object(1, 1, "mover"),
scripted_object(3, 1, "checker"),
],
&[
("mover", "fn tick(dt) { move(East); }"),
(
"checker",
"fn tick(dt) { if blocked(West) { set_tile(1); } else { set_tile(2); } }",
),
],
);
let mut game = GameState::new(board);
let mut game = GameState::with_scripts(board, scripts_from(&[
("mover", "fn tick(dt) { move(East); }"),
("checker", "fn tick(dt) { if blocked(West) { set_tile(1); } else { set_tile(2); } }"),
]));
game.tick(Duration::from_millis(16));
let b = game.board();
assert_eq!((b.objects[&1].x, b.objects[&1].y), (2, 1));
+5 -12
View File
@@ -1,7 +1,7 @@
use std::time::Duration;
use crate::board::tests::open_board;
use crate::game::GameState;
use super::{scripted_object, log_texts};
use super::{scripted_object, log_texts, scripts_from};
#[test]
fn collision_priority_resolves_in_array_order_and_bumps() {
@@ -12,18 +12,11 @@ fn collision_priority_resolves_in_array_order_and_bumps() {
2,
(0, 1), // player off the contested row
vec![scripted_object(0, 0, "e"), scripted_object(2, 0, "w")],
&[
(
"e",
"fn init() { move(East); } fn bump(id) { log(`o0 by ${id}`); }",
),
(
"w",
"fn init() { move(West); } fn bump(id) { log(`o1 by ${id}`); }",
),
],
);
let mut game = GameState::new(board);
let mut game = GameState::with_scripts(board, scripts_from(&[
("e", "fn init() { move(East); } fn bump(id) { log(`o0 by ${id}`); }"),
("w", "fn init() { move(West); } fn bump(id) { log(`o1 by ${id}`); }"),
]));
game.run_init();
// The bump fires during resolution but its log is emitted into obj0's queue;
// a later tick (past both objects' move cooldown) flushes it to the game log.
+15 -7
View File
@@ -4,20 +4,22 @@ mod map_file;
mod movement;
mod scripting;
use std::collections::BTreeMap;
use std::collections::{BTreeMap, HashMap};
use crate::archetype::Archetype;
use crate::board::Board;
use crate::object_def::ObjectDef;
use crate::utils::Player;
use crate::game::GameState;
/// Builds a 1×1 board with a single object that optionally references a
/// script, plus the given `(name, source)` script table entries.
fn board_with_object(object_script: Option<&str>, scripts: &[(&str, &str)]) -> Board {
/// Builds a 1×1 board with a single object that optionally references a script,
/// and returns both the board and the `(name, source)` entries as a script map.
///
/// Pass the returned scripts to [`GameState::with_scripts`] so they are compiled.
fn board_with_object(object_script: Option<&str>, scripts: &[(&str, &str)]) -> (Board, HashMap<String, String>) {
let mut object = ObjectDef::new(0, 0);
object.id = 1;
object.script_name = object_script.map(str::to_string);
Board {
let board = Board {
name: "test".into(),
width: 1,
height: 1,
@@ -30,10 +32,16 @@ fn board_with_object(object_script: Option<&str>, scripts: &[(&str, &str)]) -> B
portals: Vec::new(),
font: None,
zoom: 1,
scripts: scripts.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect(),
board_script_name: None,
load_errors: Vec::new(),
}
};
(board, scripts_from(scripts))
}
/// Converts a `(name, source)` slice into a `HashMap` suitable for
/// [`GameState::with_scripts`].
fn scripts_from(pairs: &[(&str, &str)]) -> HashMap<String, String> {
pairs.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect()
}
/// Returns an `ObjectDef` at `(x, y)` bound to the named script.
+11 -11
View File
@@ -11,7 +11,7 @@ fn pushing_a_crate_reveals_the_floor_underneath() {
// The cell a crate is pushed off of becomes Empty and glyph_at shows floor.
// After the push the player is at (1,0); check the cell the player vacated
// (0,0) — it is Empty and must reveal the floor glyph, not black-on-black.
let mut board = open_board(4, 1, (0, 0), vec![], &[]);
let mut board = open_board(4, 1, (0, 0), vec![]);
let floor_glyph = Glyph {
tile: ',' as u32,
fg: Rgba8 { r: 40, g: 60, b: 40, a: 255 },
@@ -29,7 +29,7 @@ fn pushing_a_crate_reveals_the_floor_underneath() {
#[test]
fn player_pushes_single_crate() {
let mut board = open_board(4, 1, (0, 0), vec![], &[]);
let mut board = open_board(4, 1, (0, 0), vec![]);
crate_at(&mut board, 1, 0);
let mut game = GameState::new(board);
game.try_move(Direction::East);
@@ -41,7 +41,7 @@ fn player_pushes_single_crate() {
#[test]
fn push_blocked_by_wall_moves_nothing() {
let mut board = open_board(4, 1, (0, 0), vec![], &[]);
let mut board = open_board(4, 1, (0, 0), vec![]);
crate_at(&mut board, 1, 0);
wall_at(&mut board, 2, 0);
let mut game = GameState::new(board);
@@ -55,7 +55,7 @@ fn push_blocked_by_wall_moves_nothing() {
#[test]
fn push_blocked_by_edge_moves_nothing() {
// Crate on the last column; player pushes it toward the board edge.
let mut board = open_board(2, 1, (0, 0), vec![], &[]);
let mut board = open_board(2, 1, (0, 0), vec![]);
crate_at(&mut board, 1, 0);
let mut game = GameState::new(board);
game.try_move(Direction::East);
@@ -66,7 +66,7 @@ fn push_blocked_by_edge_moves_nothing() {
#[test]
fn cascade_pushes_two_crates() {
let mut board = open_board(5, 1, (0, 0), vec![], &[]);
let mut board = open_board(5, 1, (0, 0), vec![]);
crate_at(&mut board, 1, 0);
crate_at(&mut board, 2, 0);
let mut game = GameState::new(board);
@@ -80,7 +80,7 @@ fn cascade_pushes_two_crates() {
#[test]
fn cascade_blocked_by_wall_moves_nothing() {
let mut board = open_board(5, 1, (0, 0), vec![], &[]);
let mut board = open_board(5, 1, (0, 0), vec![]);
crate_at(&mut board, 1, 0);
crate_at(&mut board, 2, 0);
wall_at(&mut board, 3, 0);
@@ -97,7 +97,7 @@ fn player_pushes_pushable_solid_object() {
// A solid, pushable object is shoved exactly like a crate.
let mut obj = ObjectDef::new(1, 0);
obj.pushable = true;
let board = open_board(4, 1, (0, 0), vec![obj], &[]);
let board = open_board(4, 1, (0, 0), vec![obj]);
let mut game = GameState::new(board);
game.try_move(Direction::East);
let b = game.board();
@@ -108,7 +108,7 @@ fn player_pushes_pushable_solid_object() {
#[test]
fn hcrate_pushes_east_but_not_north() {
// Pushing east: the HCrate slides.
let mut board = open_board(4, 1, (0, 0), vec![], &[]);
let mut board = open_board(4, 1, (0, 0), vec![]);
stamp(&mut board, 1, 0, Archetype::HCrate);
let mut game = GameState::new(board);
game.try_move(Direction::East);
@@ -119,7 +119,7 @@ fn hcrate_pushes_east_but_not_north() {
drop(b);
// Pushing north into an HCrate: blocked, nothing moves.
let mut board = open_board(1, 4, (0, 3), vec![], &[]);
let mut board = open_board(1, 4, (0, 3), vec![]);
stamp(&mut board, 0, 2, Archetype::HCrate);
let mut game = GameState::new(board);
game.try_move(Direction::North);
@@ -131,7 +131,7 @@ fn hcrate_pushes_east_but_not_north() {
#[test]
fn vcrate_pushes_north_but_not_east() {
// Pushing north: the VCrate slides.
let mut board = open_board(1, 4, (0, 3), vec![], &[]);
let mut board = open_board(1, 4, (0, 3), vec![]);
stamp(&mut board, 0, 2, Archetype::VCrate);
let mut game = GameState::new(board);
game.try_move(Direction::North);
@@ -142,7 +142,7 @@ fn vcrate_pushes_north_but_not_east() {
drop(b);
// Pushing east into a VCrate: blocked, nothing moves.
let mut board = open_board(4, 1, (0, 0), vec![], &[]);
let mut board = open_board(4, 1, (0, 0), vec![]);
stamp(&mut board, 1, 0, Archetype::VCrate);
let mut game = GameState::new(board);
game.try_move(Direction::East);
+61 -93
View File
@@ -2,15 +2,15 @@ use std::time::Duration;
use crate::board::tests::open_board;
use crate::game::{GameState, ScrollLine};
use crate::utils::Direction;
use super::{board_with_object, scripted_object, log_texts};
use super::{board_with_object, scripted_object, log_texts, scripts_from};
#[test]
fn init_runs_only_on_run_init_not_at_construction() {
let board = board_with_object(
let (board, scripts) = board_with_object(
Some("greet"),
&[("greet", r#"fn init() { log("hello"); }"#)],
);
let mut game = GameState::new(board);
let mut game = GameState::with_scripts(board, scripts);
// init must not fire during construction / deserialization.
assert!(game.log.is_empty());
@@ -20,11 +20,11 @@ fn init_runs_only_on_run_init_not_at_construction() {
#[test]
fn tick_calls_script_tick_with_elapsed_seconds() {
let board = board_with_object(
let (board, scripts) = board_with_object(
Some("t"),
&[("t", r#"fn tick(dt) { log(dt.to_string()); }"#)],
);
let mut game = GameState::new(board);
let mut game = GameState::with_scripts(board, scripts);
game.run_init();
assert!(game.log.is_empty());
@@ -37,16 +37,18 @@ fn tick_calls_script_tick_with_elapsed_seconds() {
#[test]
fn missing_hooks_and_no_script_are_noops() {
// Object with no script: nothing happens.
let mut game = GameState::new(board_with_object(None, &[]));
let (board, scripts) = board_with_object(None, &[]);
let mut game = GameState::with_scripts(board, scripts);
game.run_init();
game.tick(Duration::from_millis(33));
assert!(game.log.is_empty());
// Script defines neither init nor tick: also a no-op.
let mut game = GameState::new(board_with_object(
let (board, scripts) = board_with_object(
Some("e"),
&[("e", "fn other() { log(\"x\"); }")],
));
);
let mut game = GameState::with_scripts(board, scripts);
game.run_init();
game.tick(Duration::from_millis(33));
assert!(game.log.is_empty());
@@ -55,11 +57,13 @@ fn missing_hooks_and_no_script_are_noops() {
#[test]
fn compile_and_unknown_script_errors_are_logged() {
// A reference to a script name that isn't in the table.
let game = GameState::new(board_with_object(Some("ghost"), &[]));
let (board, scripts) = board_with_object(Some("ghost"), &[]);
let game = GameState::with_scripts(board, scripts);
assert!(log_texts(&game)[0].contains("unknown script 'ghost'"));
// A script that fails to compile is reported at construction time.
let game = GameState::new(board_with_object(Some("bad"), &[("bad", "fn init( {")]));
let (board, scripts) = board_with_object(Some("bad"), &[("bad", "fn init( {")]);
let game = GameState::with_scripts(board, scripts);
assert!(log_texts(&game)[0].contains("failed to compile"));
}
@@ -70,9 +74,8 @@ fn script_reads_board_through_view() {
3,
(3, 1),
vec![scripted_object(2, 1, "r")],
&[("r", "fn init() { log(Board.player_x.to_string()); }")],
);
let mut game = GameState::new(board);
let mut game = GameState::with_scripts(board, scripts_from(&[("r", "fn init() { log(Board.player_x.to_string()); }")]));
game.run_init();
assert_eq!(log_texts(&game), vec!["3"]);
}
@@ -86,12 +89,11 @@ fn commands_are_routed_to_their_own_source_object() {
3,
(0, 0),
vec![scripted_object(0, 1, "e"), scripted_object(4, 1, "w")],
&[
("e", "fn init() { move(East); }"),
("w", "fn init() { move(West); }"),
],
);
let mut game = GameState::new(board);
let mut game = GameState::with_scripts(board, scripts_from(&[
("e", "fn init() { move(East); }"),
("w", "fn init() { move(West); }"),
]));
game.run_init();
let b = game.board();
assert_eq!(b.objects[&1].x, 1); // moved east from 0
@@ -104,9 +106,8 @@ fn start_map_greeter_runs_init() {
// confirm the greeter read the board (interpolated message) and wrote to
// itself (set_tile). Also guards the example from drifting out of sync.
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../maps/start.toml");
let mut world = crate::world::load(path).expect("load start.toml");
let board = world.boards.remove(&world.start).expect("start board");
let mut game = GameState::new(board);
let world = crate::world::load(path).expect("load start.toml");
let mut game = GameState::from_world(world);
game.run_init();
assert!(
log_texts(&game).iter().any(|t| t.contains("hello from object")),
@@ -122,25 +123,22 @@ fn start_map_greeter_runs_init() {
fn set_tag_adds_and_removes_via_my_id() {
// A script calls set_tag(Me.id, "active", true) in init; the tag must be
// present on the object afterward.
let board = board_with_object(
let (board, scripts) = board_with_object(
Some("t"),
&[("t", r#"fn init() { set_tag(Me.id, "active", true); }"#)],
);
let mut game = GameState::new(board);
let mut game = GameState::with_scripts(board, scripts);
game.run_init();
assert!(game.board().objects[&1].tags.contains("active"));
// A script removes a pre-existing tag.
let board2 = board_with_object(
let (mut board2, scripts2) = board_with_object(
Some("t2"),
&[("t2", r#"fn init() { set_tag(Me.id, "active", false); }"#)],
);
// Seed the tag before construction.
let mut game2 = GameState::new({
let mut b = board2; // need mut to insert tag
b.objects.get_mut(&1).unwrap().tags.insert("active".to_string());
b
});
board2.objects.get_mut(&1).unwrap().tags.insert("active".to_string());
let mut game2 = GameState::with_scripts(board2, scripts2);
game2.run_init();
assert!(!game2.board().objects[&1].tags.contains("active"));
}
@@ -149,7 +147,7 @@ fn set_tag_adds_and_removes_via_my_id() {
fn has_tag_reads_own_tags() {
// set_tag queues an action; has_tag reads the board state. So set_tag in
// init() takes effect after init returns; a subsequent tick sees it.
let board = board_with_object(
let (board, scripts) = board_with_object(
Some("t"),
&[(
"t",
@@ -157,7 +155,7 @@ fn has_tag_reads_own_tags() {
fn tick(dt) { log(Me.has_tag("active").to_string()); }"#,
)],
);
let mut game = GameState::new(board);
let mut game = GameState::with_scripts(board, scripts);
game.run_init();
game.tick(Duration::from_millis(16));
assert!(log_texts(&game).iter().any(|t| t == "true"));
@@ -171,21 +169,15 @@ fn objects_with_tag_returns_matching_ids() {
let mut obj2 = scripted_object(1, 0, "none");
// obj2 (id=2) has the "enemy" tag; obj1 (id=1) does not.
obj2.tags.insert("enemy".to_string());
let board = open_board(
5,
1,
(4, 0),
vec![obj1, obj2],
&[
("q", r#"fn init() {
let infos = Board.tagged("enemy");
log(infos.len().to_string());
log(infos[0].id.to_string());
}"#),
("none", ""),
],
);
let mut game = GameState::new(board);
let board = open_board(5, 1, (4, 0), vec![obj1, obj2]);
let mut game = GameState::with_scripts(board, scripts_from(&[
("q", r#"fn init() {
let infos = Board.tagged("enemy");
log(infos.len().to_string());
log(infos[0].id.to_string());
}"#),
("none", ""),
]));
game.run_init();
let texts = log_texts(&game);
assert_eq!(texts[0], "1"); // exactly one match
@@ -195,21 +187,21 @@ fn objects_with_tag_returns_matching_ids() {
#[test]
fn my_name_returns_name_or_empty_string() {
// An object with a name set on its ObjectDef should see it via my_name().
let mut board = board_with_object(
let (mut board, scripts) = board_with_object(
Some("n"),
&[("n", r#"fn init() { log(Me.name); }"#)],
);
board.objects.get_mut(&1).unwrap().name = Some("beacon".to_string());
let mut game = GameState::new(board);
let mut game = GameState::with_scripts(board, scripts);
game.run_init();
assert_eq!(log_texts(&game), vec!["beacon"]);
// An unnamed object should get an empty string.
let board2 = board_with_object(
let (board2, scripts2) = board_with_object(
Some("n"),
&[("n", r#"fn init() { log(Me.name); }"#)],
);
let mut game2 = GameState::new(board2);
let mut game2 = GameState::with_scripts(board2, scripts2);
game2.run_init();
assert_eq!(log_texts(&game2), vec![""]);
}
@@ -221,21 +213,15 @@ fn object_id_for_name_finds_by_name() {
let obj1 = scripted_object(0, 0, "q");
let mut obj2 = scripted_object(1, 0, "none");
obj2.name = Some("target".to_string());
let board = open_board(
5,
1,
(4, 0),
vec![obj1, obj2],
&[
("q", r#"fn init() {
log(Board.named("target").id.to_string());
let miss = Board.named("missing");
log(if miss == () { "not_found" } else { miss.id.to_string() });
}"#),
("none", ""),
],
);
let mut game = GameState::new(board);
let board = open_board(5, 1, (4, 0), vec![obj1, obj2]);
let mut game = GameState::with_scripts(board, scripts_from(&[
("q", r#"fn init() {
log(Board.named("target").id.to_string());
let miss = Board.named("missing");
log(if miss == () { "not_found" } else { miss.id.to_string() });
}"#),
("none", ""),
]));
game.run_init();
let texts = log_texts(&game);
assert_eq!(texts[0], "2"); // obj2 is id 2
@@ -247,14 +233,8 @@ fn object_id_for_name_finds_by_name() {
fn player_bump_fires_with_negative_one() {
// The player walks into a solid scripted object; the object is bumped with -1
// and the player does not move onto it.
let board = open_board(
3,
1,
(0, 0),
vec![scripted_object(1, 0, "b")],
&[("b", "fn bump(id) { log(`bumped by ${id}`); }")],
);
let mut game = GameState::new(board);
let board = open_board(3, 1, (0, 0), vec![scripted_object(1, 0, "b")]);
let mut game = GameState::with_scripts(board, scripts_from(&[("b", "fn bump(id) { log(`bumped by ${id}`); }")]));
game.run_init();
game.try_move(Direction::East);
assert_eq!((game.board().player.x, game.board().player.y), (0, 0)); // blocked
@@ -268,12 +248,8 @@ fn scroll_opens_on_player_bump() {
// A solid object's bump() calls scroll([text, [choice, display]]). After the
// player bumps it and a tick resolves the queued action, active_scroll is set
// with the correct ScrollLine variants.
let board = open_board(
3, 1, (0, 0),
vec![scripted_object(1, 0, "s")],
&[("s", r#"fn bump(id) { scroll(["Hello world", ["eat", "Eat it"]]); }"#)],
);
let mut game = GameState::new(board);
let board = open_board(3, 1, (0, 0), vec![scripted_object(1, 0, "s")]);
let mut game = GameState::with_scripts(board, scripts_from(&[("s", r#"fn bump(id) { scroll(["Hello world", ["eat", "Eat it"]]); }"#)]));
game.run_init();
game.try_move(Direction::East);
game.tick(Duration::from_millis(16));
@@ -287,12 +263,8 @@ fn scroll_opens_on_player_bump() {
#[test]
fn close_scroll_without_choice_clears_it() {
let board = open_board(
3, 1, (0, 0),
vec![scripted_object(1, 0, "s")],
&[("s", r#"fn bump(id) { scroll(["Hello"]); }"#)],
);
let mut game = GameState::new(board);
let board = open_board(3, 1, (0, 0), vec![scripted_object(1, 0, "s")]);
let mut game = GameState::with_scripts(board, scripts_from(&[("s", r#"fn bump(id) { scroll(["Hello"]); }"#)]));
game.run_init();
game.try_move(Direction::East);
game.tick(Duration::from_millis(16));
@@ -306,15 +278,11 @@ fn close_scroll_without_choice_clears_it() {
fn close_scroll_with_choice_dispatches_send_to_source() {
// Closing with a choice string fires send() on the object that opened the
// scroll; fn eat() logging "eaten" confirms the dispatch arrived.
let board = open_board(
3, 1, (0, 0),
vec![scripted_object(1, 0, "s")],
&[("s", r#"
fn bump(id) { scroll(["Muffin?", ["eat", "Eat it"]]); }
fn eat() { log("eaten"); }
"#)],
);
let mut game = GameState::new(board);
let board = open_board(3, 1, (0, 0), vec![scripted_object(1, 0, "s")]);
let mut game = GameState::with_scripts(board, scripts_from(&[("s", r#"
fn bump(id) { scroll(["Muffin?", ["eat", "Eat it"]]); }
fn eat() { log("eaten"); }
"#)]));
game.run_init();
game.try_move(Direction::East);
game.tick(Duration::from_millis(16));