multi board
This commit is contained in:
+38
-114
@@ -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));
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user