Fixed game.rs tests
This commit is contained in:
+44
-2
@@ -633,10 +633,10 @@ pub(crate) mod tests {
|
|||||||
use crate::builtin::Builtin;
|
use crate::builtin::Builtin;
|
||||||
use crate::floor::Floor;
|
use crate::floor::Floor;
|
||||||
use crate::glyph::Glyph;
|
use crate::glyph::Glyph;
|
||||||
use crate::utils::Direction;
|
use crate::utils::{Direction, ObjectId};
|
||||||
use color::Rgba8;
|
use color::Rgba8;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use crate::tile::{DrawLayer, IntoTile, Optics, ScriptAttributes, Sensor, Tile, TileSpec};
|
use crate::tile::{DrawLayer, IntoTile, Optics, ScriptAttributes, Sensor, SensorSpec, Tile, TileSpec};
|
||||||
|
|
||||||
/// Builds an all-empty `w×h` board.
|
/// Builds an all-empty `w×h` board.
|
||||||
///
|
///
|
||||||
@@ -685,6 +685,48 @@ pub(crate) mod tests {
|
|||||||
*board.get_mut(x, y) = Some(TileSpec::gem().into_tile(&mut board.next_object_id).unwrap());
|
*board.get_mut(x, y) = Some(TileSpec::gem().into_tile(&mut board.next_object_id).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Stamps the builtin named `kind` (any alias accepted by [`Builtin::from_name`],
|
||||||
|
/// e.g. `"spinner_cw"`, `"pusher_east"`) onto the grid, returning its id.
|
||||||
|
///
|
||||||
|
/// The generic counterpart to [`crate_at`]/[`wall_at`]/[`gem_at`]: `into_tile`
|
||||||
|
/// attaches the family's [`ScriptKey`](crate::tile::ScriptKey) and the
|
||||||
|
/// `BUILTIN_<kind>` tag, so the object is fully live with no world script pool.
|
||||||
|
/// Panics on an unknown `kind`.
|
||||||
|
pub(crate) fn builtin_at(board: &mut Board, x: usize, y: usize, kind: &str) -> ObjectId {
|
||||||
|
let tile = TileSpec::Builtin { kind: kind.to_string(), glyph: None }
|
||||||
|
.into_tile(&mut board.next_object_id)
|
||||||
|
.unwrap_or_else(|e| panic!("{e}"));
|
||||||
|
let id = match &tile {
|
||||||
|
Tile::Object(obj) => obj.scripting.id,
|
||||||
|
Tile::Player => unreachable!("a builtin never resolves to the player"),
|
||||||
|
};
|
||||||
|
*board.get_mut(x, y) = Some(tile);
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds an invisible, script-only [`Sensor`] at `(x, y)` running the world script
|
||||||
|
/// named `script`, returning its id.
|
||||||
|
///
|
||||||
|
/// This is how a test gets "a scripted thing that doesn't get in the way": every
|
||||||
|
/// object on the grid is solid now, so a script host that must not block movement
|
||||||
|
/// (or must share a cell) has to live off-grid in [`Board::sensors`].
|
||||||
|
pub(crate) fn sensor_at(board: &mut Board, x: usize, y: usize, script: &str) -> ObjectId {
|
||||||
|
let sensor = SensorSpec {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
script: Some(script.to_string()),
|
||||||
|
glyph: Glyph::transparent(),
|
||||||
|
optics: Optics::default(),
|
||||||
|
name: None,
|
||||||
|
tags: Vec::new(),
|
||||||
|
draw_layer: DrawLayer::Below,
|
||||||
|
}
|
||||||
|
.into_sensor(&mut board.next_object_id);
|
||||||
|
let id = sensor.scripting.id;
|
||||||
|
board.sensors.push(sensor);
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
/// Stamps a player cell onto the grid.
|
/// Stamps a player cell onto the grid.
|
||||||
pub(crate) fn player_at(board: &mut Board, x: usize, y: usize) {
|
pub(crate) fn player_at(board: &mut Board, x: usize, y: usize) {
|
||||||
*board.get_mut(x, y) = Some(TileSpec::player().into_tile(&mut board.next_object_id).unwrap());
|
*board.get_mut(x, y) = Some(TileSpec::player().into_tile(&mut board.next_object_id).unwrap());
|
||||||
|
|||||||
+83
-110
@@ -564,16 +564,17 @@ fn step_object(board: &mut Board, id: ObjectId, dir: Direction) {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::GameState;
|
use super::GameState;
|
||||||
use crate::Direction;
|
use crate::Direction;
|
||||||
use crate::builtin::Builtin;
|
use crate::board::tests::{
|
||||||
use crate::board::tests::{crate_at, gem_at, open_board, wall_at};
|
builtin_at, crate_at, gem_at, is_builtin, open_board, sensor_at, wall_at,
|
||||||
use crate::object_def::ObjectDef;
|
};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn walking_onto_a_gem_grabs_it() {
|
fn walking_onto_a_gem_grabs_it() {
|
||||||
// A gem terrain cell at (1,0); expanding turns it into the builtin gem
|
// A gem builtin object at (1,0), running scripts/gem.rhai. Its
|
||||||
// object running scripts/gem.rhai. The player starts at (0,0).
|
// EnterResponse::Grab means the player's step fires grab() rather than being
|
||||||
|
// blocked. The player starts at (0,0).
|
||||||
let mut board = open_board(3, 1, (0, 0));
|
let mut board = open_board(3, 1, (0, 0));
|
||||||
gem_at(&mut board, 1, 0);
|
gem_at(&mut board, 1, 0);
|
||||||
let mut game = GameState::new(board);
|
let mut game = GameState::new(board);
|
||||||
@@ -583,43 +584,20 @@ mod tests {
|
|||||||
|
|
||||||
// The gem was grabbed: gem count up, gem object gone, player on its cell.
|
// The gem was grabbed: gem count up, gem object gone, player on its cell.
|
||||||
assert_eq!(game.player.borrow().gems, 1);
|
assert_eq!(game.player.borrow().gems, 1);
|
||||||
|
assert!(game.board().all_ids().is_empty());
|
||||||
assert_eq!(game.board().player_pos(), (1, 0));
|
assert_eq!(game.board().player_pos(), (1, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn pushing_a_gem_into_the_player_does_not_grab() {
|
|
||||||
// A non-solid script object at (0,0) pushes the gem at (1,0) east toward the
|
|
||||||
// player at (2,0). Grab now fires only on player movement, so the gem is an
|
|
||||||
// ordinary solid here: the chain can't move (the player is backed against
|
|
||||||
// the board edge), so nothing happens and the gem is not collected.
|
|
||||||
let mut sobj = ObjectDef::new(0, 0);
|
|
||||||
sobj.behavior.solid = false;
|
|
||||||
sobj.scripting.script_name = Some("s".to_string());
|
|
||||||
let mut board = open_board(3, 1, (2, 0), vec![sobj]);
|
|
||||||
stamp(&mut board, 1, 0, Archetype::Builtin(Builtin::Gem, "gem"));
|
|
||||||
board.expand_builtin_archetypes();
|
|
||||||
let scripts = HashMap::from([(
|
|
||||||
"s".to_string(),
|
|
||||||
"fn tick(dt) { if Queue.length() == 0 { push(1, 0, East); } }".to_string(),
|
|
||||||
)]);
|
|
||||||
let mut game = GameState::with_scripts(board, scripts);
|
|
||||||
game.run_init();
|
|
||||||
|
|
||||||
game.tick(Duration::from_millis(16));
|
/// Builds a `GameState` with a script-only sensor at (0,0) running `src`, a crate
|
||||||
game.tick(Duration::from_millis(16));
|
/// at (2,0), and the player parked against the right-hand edge.
|
||||||
|
///
|
||||||
// No grab: the gem is untouched and the player never moved.
|
/// The script host is a [`Sensor`](crate::tile::Sensor) rather than an object:
|
||||||
assert_eq!(game.player.borrow().gems, 0);
|
/// every grid object is solid now, so a scripted thing that must not interfere
|
||||||
assert!(game.board().objects.values().any(|o| o.behavior.grab));
|
/// with movement has to live off-grid.
|
||||||
assert_eq!((game.board().player.x, game.board().player.y), (2, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Builds a `GameState` with one non-solid object running `src` as its script.
|
|
||||||
fn game_with_object_script(board_w: usize, src: &str) -> GameState {
|
fn game_with_object_script(board_w: usize, src: &str) -> GameState {
|
||||||
let mut obj = ObjectDef::new(0, 0);
|
let mut board = open_board(board_w, 1, (board_w - 1, 0));
|
||||||
obj.behavior.solid = false;
|
sensor_at(&mut board, 0, 0, "s");
|
||||||
obj.scripting.script_name = Some("s".to_string());
|
|
||||||
let mut board = open_board(board_w, 1, (board_w as i64 - 1, 0), vec![obj]);
|
|
||||||
crate_at(&mut board, 2, 0);
|
crate_at(&mut board, 2, 0);
|
||||||
let scripts = HashMap::from([("s".to_string(), src.to_string())]);
|
let scripts = HashMap::from([("s".to_string(), src.to_string())]);
|
||||||
let mut game = GameState::with_scripts(board, scripts);
|
let mut game = GameState::with_scripts(board, scripts);
|
||||||
@@ -630,28 +608,25 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn script_shift_rotates_crates() {
|
fn script_shift_rotates_crates() {
|
||||||
// Rotate the crate at (2,0) with the empty cell at (3,0) in a two-cell cycle:
|
// Rotate the crate at (2,0) with the empty cell at (3,0) in a two-cell cycle:
|
||||||
// crate moves to (3,0), empty moves back to (2,0).
|
// crate moves to (3,0), empty moves back to (2,0). No queue guard is needed —
|
||||||
let mut game = game_with_object_script(
|
// the host only calls `tick` when the object's queue has drained.
|
||||||
5,
|
let mut game = game_with_object_script(5, "fn tick(me, dt) { shift([[2, 0], [3, 0]]); }");
|
||||||
"fn tick(m,dt) { if m.queue.length == 0 { shift([[2, 0], [3, 0]]); } }",
|
|
||||||
);
|
|
||||||
game.tick(Duration::from_millis(16));
|
game.tick(Duration::from_millis(16));
|
||||||
let b = game.board();
|
let b = game.board();
|
||||||
assert_eq!(b.get(2, 0).1, Archetype::Empty);
|
assert!(b.get(2, 0).is_none());
|
||||||
assert_eq!(b.get(3, 0).1, Archetype::Builtin(Builtin::Crate, "crate"));
|
assert!(is_builtin(&b, 3, 0, "crate"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn script_passable_distinguishes_empty_solid_and_offboard() {
|
fn script_passable_distinguishes_empty_solid_and_offboard() {
|
||||||
// (1,0) empty, (2,0) a solid crate, (9,0) off the 4-wide board. The non-solid
|
// (1,0) empty, (2,0) a solid crate, (9,0) off the 4-wide board. The script
|
||||||
// object sits at (0,0); the player at (3,0). passable should report only the
|
// sensor sits at (0,0) — sensors never occupy a cell, so it does not make its
|
||||||
// genuinely empty in-bounds cell.
|
// own cell impassable; the player is at (3,0). `passable` should report only
|
||||||
let mut obj = ObjectDef::new(0, 0);
|
// the genuinely empty in-bounds cell.
|
||||||
obj.behavior.solid = false;
|
let mut board = open_board(4, 1, (3, 0));
|
||||||
obj.scripting.script_name = Some("s".to_string());
|
sensor_at(&mut board, 0, 0, "s");
|
||||||
let mut board = open_board(4, 1, (3, 0), vec![obj]);
|
|
||||||
crate_at(&mut board, 2, 0);
|
crate_at(&mut board, 2, 0);
|
||||||
let src = "fn init(m) { \
|
let src = "fn init(me) { \
|
||||||
log(if Board.passable(1, 0) { \"empty:yes\" } else { \"empty:no\" }); \
|
log(if Board.passable(1, 0) { \"empty:yes\" } else { \"empty:no\" }); \
|
||||||
log(if Board.passable(2, 0) { \"crate:yes\" } else { \"crate:no\" }); \
|
log(if Board.passable(2, 0) { \"crate:yes\" } else { \"crate:no\" }); \
|
||||||
log(if Board.passable(9, 0) { \"off:yes\" } else { \"off:no\" }); }";
|
log(if Board.passable(9, 0) { \"off:yes\" } else { \"off:no\" }); }";
|
||||||
@@ -669,15 +644,13 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn log_is_immediate_and_not_paced_by_the_queue() {
|
fn log_is_immediate_and_not_paced_by_the_queue() {
|
||||||
// `delay(5.0)` parks the object's action queue for 5 seconds, then `log()`
|
// `delay(5.0)` parks the sensor's action queue for 5 seconds, then `log()`
|
||||||
// fires. Because logging bypasses the queue entirely, the line must appear
|
// fires. Because logging bypasses the queue entirely, the line must appear
|
||||||
// right after run_init (dt = 0) — under the old queued-Action behavior it
|
// right after run_init (dt = 0) — under the old queued-Action behavior it
|
||||||
// would have been stuck behind the delay and absent here.
|
// would have been stuck behind the delay and absent here.
|
||||||
let mut obj = ObjectDef::new(0, 0);
|
let mut board = open_board(4, 1, (3, 0));
|
||||||
obj.behavior.solid = false;
|
sensor_at(&mut board, 0, 0, "s");
|
||||||
obj.scripting.script_name = Some("s".to_string());
|
let src = "fn init(me) { delay(5.0); log(\"immediate\"); }";
|
||||||
let board = open_board(4, 1, (3, 0), vec![obj]);
|
|
||||||
let src = "fn init(m) { delay(5.0); log(\"immediate\"); }";
|
|
||||||
let scripts = HashMap::from([("s".to_string(), src.to_string())]);
|
let scripts = HashMap::from([("s".to_string(), src.to_string())]);
|
||||||
let mut game = GameState::with_scripts(board, scripts);
|
let mut game = GameState::with_scripts(board, scripts);
|
||||||
game.run_init();
|
game.run_init();
|
||||||
@@ -690,52 +663,53 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds a 3×3 board with a non-solid clockwise spinner object (running the
|
/// Builds a board with a clockwise `spinner_cw` builtin at (1,1), plus crates and
|
||||||
/// real `scripts/spinner.rhai`) at the centre and the player parked on it.
|
/// walls at the given ring cells.
|
||||||
fn spinner_board(crates: &[(usize, usize)], walls: &[(usize, usize)]) -> GameState {
|
fn spinner_board(crates: &[(usize, usize)], walls: &[(usize, usize)]) -> GameState {
|
||||||
spinner_board_dir(crates, walls, false)
|
spinner_board_dir(crates, walls, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Like [`spinner_board`] but `ccw` attaches the `BUILTIN_spinner_ccw` tag so the
|
/// Like [`spinner_board`] but `ccw` stamps the `spinner_ccw` alias instead, so the
|
||||||
/// script spins counter-clockwise (clockwise is the default when no tag present).
|
/// shared `scripts/spinner.rhai` reads its `BUILTIN_spinner_ccw` tag and spins the
|
||||||
|
/// other way (clockwise is the default when the tag is absent).
|
||||||
|
///
|
||||||
|
/// The board is 4×3, not 3×3: the spinner is a solid object now, so the player
|
||||||
|
/// needs a cell of its own at (3,1) instead of being parked on the spinner, and
|
||||||
|
/// the 8-cell ring around (1,1) must stay fully in bounds or `apply_shift`
|
||||||
|
/// rejects the whole rotation. Ring coordinates are unchanged from the 3×3
|
||||||
|
/// layout — N(1,0) NE(2,0) E(2,1) SE(2,2) S(1,2) SW(0,2) W(0,1) NW(0,0).
|
||||||
fn spinner_board_dir(
|
fn spinner_board_dir(
|
||||||
crates: &[(usize, usize)],
|
crates: &[(usize, usize)],
|
||||||
walls: &[(usize, usize)],
|
walls: &[(usize, usize)],
|
||||||
ccw: bool,
|
ccw: bool,
|
||||||
) -> GameState {
|
) -> GameState {
|
||||||
let mut obj = ObjectDef::new(1, 1);
|
let mut board = open_board(4, 3, (3, 1));
|
||||||
obj.behavior.solid = false;
|
// Stamped first, so the spinner holds the lowest id and ticks before anything else.
|
||||||
obj.scripting.script_name = Some("spinner".to_string());
|
builtin_at(&mut board, 1, 1, if ccw { "spinner_ccw" } else { "spinner_cw" });
|
||||||
if ccw {
|
|
||||||
obj.scripting.tags.insert("BUILTIN_spinner_ccw".to_string());
|
|
||||||
}
|
|
||||||
let mut board = open_board(3, 3, (1, 1), vec![obj]);
|
|
||||||
for &(x, y) in crates {
|
for &(x, y) in crates {
|
||||||
crate_at(&mut board, x, y);
|
crate_at(&mut board, x, y);
|
||||||
}
|
}
|
||||||
for &(x, y) in walls {
|
for &(x, y) in walls {
|
||||||
wall_at(&mut board, x, y);
|
wall_at(&mut board, x, y);
|
||||||
}
|
}
|
||||||
let scripts = HashMap::from([(
|
// No world script pool: a builtin carries its own ScriptKey and source.
|
||||||
"spinner".to_string(),
|
let mut game = GameState::new(board);
|
||||||
include_str!("scripts/spinner.rhai").to_string(),
|
|
||||||
)]);
|
|
||||||
let mut game = GameState::with_scripts(board, scripts);
|
|
||||||
game.run_init();
|
game.run_init();
|
||||||
game
|
game
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn spinner_does_not_destroy_a_blocked_neighbour() {
|
fn spinner_does_not_destroy_a_blocked_neighbour() {
|
||||||
// Crates at N(1,0) and NE(2,0), a wall at E(2,1). NE can't rotate into the
|
// Crates at N(1,0) and NE(2,0), a wall at E(2,1). The wall is immobile, and
|
||||||
// wall, so N must not rotate onto NE — the old (can_shift-only) script
|
// `apply_shift` cascades that backward over the contiguous run of solids
|
||||||
// overwrote and destroyed NE's crate. The cascade leaves everything put.
|
// behind it, so N must not rotate onto NE — an earlier one-cell-lookahead
|
||||||
|
// version overwrote and destroyed NE's crate. Everything stays put.
|
||||||
let mut game = spinner_board(&[(1, 0), (2, 0)], &[(2, 1)]);
|
let mut game = spinner_board(&[(1, 0), (2, 0)], &[(2, 1)]);
|
||||||
game.tick(Duration::from_millis(16));
|
game.tick(Duration::from_millis(16));
|
||||||
let b = game.board();
|
let b = game.board();
|
||||||
assert_eq!(b.get(1, 0).1, Archetype::Builtin(Builtin::Crate, "crate")); // N kept
|
assert!(is_builtin(&b, 1, 0, "crate")); // N kept
|
||||||
assert_eq!(b.get(2, 0).1, Archetype::Builtin(Builtin::Crate, "crate")); // NE kept (not destroyed)
|
assert!(is_builtin(&b, 2, 0, "crate")); // NE kept (not destroyed)
|
||||||
assert_eq!(b.get(2, 1).1, Archetype::Builtin(Builtin::Wall, "wall")); // wall kept
|
assert!(is_builtin(&b, 2, 1, "wall")); // wall kept
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -745,10 +719,10 @@ mod tests {
|
|||||||
let mut game = spinner_board(&[(0, 1), (0, 0), (1, 0)], &[]);
|
let mut game = spinner_board(&[(0, 1), (0, 0), (1, 0)], &[]);
|
||||||
game.tick(Duration::from_millis(16));
|
game.tick(Duration::from_millis(16));
|
||||||
let b = game.board();
|
let b = game.board();
|
||||||
assert_eq!(b.get(2, 0).1, Archetype::Builtin(Builtin::Crate, "crate")); // NE: filled by N
|
assert!(is_builtin(&b, 2, 0, "crate")); // NE: filled by N
|
||||||
assert_eq!(b.get(1, 0).1, Archetype::Builtin(Builtin::Crate, "crate")); // N: filled by NW
|
assert!(is_builtin(&b, 1, 0, "crate")); // N: filled by NW
|
||||||
assert_eq!(b.get(0, 0).1, Archetype::Builtin(Builtin::Crate, "crate")); // NW: filled by W
|
assert!(is_builtin(&b, 0, 0, "crate")); // NW: filled by W
|
||||||
assert_eq!(b.get(0, 1).1, Archetype::Empty); // W: vacated (hole moved here)
|
assert!(b.get(0, 1).is_none()); // W: vacated (hole moved here)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -758,9 +732,9 @@ mod tests {
|
|||||||
let mut game = spinner_board_dir(&[(1, 0)], &[], true);
|
let mut game = spinner_board_dir(&[(1, 0)], &[], true);
|
||||||
game.tick(Duration::from_millis(16));
|
game.tick(Duration::from_millis(16));
|
||||||
let b = game.board();
|
let b = game.board();
|
||||||
assert_eq!(b.get(0, 0).1, Archetype::Builtin(Builtin::Crate, "crate")); // NW: crate rotated counter-clockwise
|
assert!(is_builtin(&b, 0, 0, "crate")); // NW: crate rotated counter-clockwise
|
||||||
assert_eq!(b.get(2, 0).1, Archetype::Empty); // NE: untouched
|
assert!(b.get(2, 0).is_none()); // NE: untouched
|
||||||
assert_eq!(b.get(1, 0).1, Archetype::Empty); // N: vacated
|
assert!(b.get(1, 0).is_none()); // N: vacated
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -768,31 +742,34 @@ mod tests {
|
|||||||
// The glyph character cycles '/'(47) '─'(0xC4) '\'(92) '│'(0xB3) one frame
|
// The glyph character cycles '/'(47) '─'(0xC4) '\'(92) '│'(0xB3) one frame
|
||||||
// per 0.5s rotation, while the colours stay put.
|
// per 0.5s rotation, while the colours stay put.
|
||||||
let mut game = spinner_board(&[], &[]);
|
let mut game = spinner_board(&[], &[]);
|
||||||
let id = *game.board().objects.keys().next().unwrap();
|
// With no crates or walls the spinner is the board's only object; the player
|
||||||
let (fg0, bg0) = {
|
// is a `Tile::Player`, which carries no id.
|
||||||
let b = game.board();
|
let id = game.board().all_ids()[0];
|
||||||
(b.objects[&id].glyph.fg, b.objects[&id].glyph.bg)
|
let glyph_of = |game: &GameState| {
|
||||||
|
game.board()
|
||||||
|
.get_hookable(id)
|
||||||
|
.expect("the spinner stays on the board")
|
||||||
|
.glyph()
|
||||||
};
|
};
|
||||||
|
let base = glyph_of(&game);
|
||||||
let mut seq = Vec::new();
|
let mut seq = Vec::new();
|
||||||
for _ in 0..5 {
|
for _ in 0..5 {
|
||||||
game.tick(Duration::from_secs_f64(0.5));
|
game.tick(Duration::from_secs_f64(0.5));
|
||||||
let b = game.board();
|
let g = glyph_of(&game);
|
||||||
seq.push(b.objects[&id].glyph.tile);
|
seq.push(g.tile);
|
||||||
assert_eq!(b.objects[&id].glyph.fg, fg0, "fg unchanged");
|
assert_eq!(g.fg, base.fg, "fg unchanged");
|
||||||
assert_eq!(b.objects[&id].glyph.bg, bg0, "bg unchanged");
|
assert_eq!(g.bg, base.bg, "bg unchanged");
|
||||||
}
|
}
|
||||||
assert_eq!(seq, vec![47, 0xC4, 92, 0xB3, 47]);
|
assert_eq!(seq, vec![47, 0xC4, 92, 0xB3, 47]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn set_key_gives_and_takes_keys() {
|
fn set_key_gives_and_takes_keys() {
|
||||||
let mut sobj = ObjectDef::new(0, 0);
|
let mut board = open_board(2, 1, (1, 0));
|
||||||
sobj.behavior.solid = false;
|
sensor_at(&mut board, 0, 0, "s");
|
||||||
sobj.scripting.script_name = Some("s".to_string());
|
|
||||||
let board = open_board(2, 1, (1, 0), vec![sobj]);
|
|
||||||
let scripts = HashMap::from([(
|
let scripts = HashMap::from([(
|
||||||
"s".to_string(),
|
"s".to_string(),
|
||||||
"fn init(m) { set_key(\"blue\", true); set_key(\"red\", true); }".to_string(),
|
r#"fn init(me) { set_key("blue", true); set_key("red", true); }"#.to_string(),
|
||||||
)]);
|
)]);
|
||||||
let mut game = GameState::with_scripts(board, scripts);
|
let mut game = GameState::with_scripts(board, scripts);
|
||||||
game.run_init();
|
game.run_init();
|
||||||
@@ -803,13 +780,11 @@ mod tests {
|
|||||||
assert!(!keys.cyan); // cyan was not set by the script
|
assert!(!keys.cyan); // cyan was not set by the script
|
||||||
|
|
||||||
// A second script can take a key.
|
// A second script can take a key.
|
||||||
let mut sobj2 = ObjectDef::new(0, 0);
|
let mut board2 = open_board(2, 1, (1, 0));
|
||||||
sobj2.behavior.solid = false;
|
sensor_at(&mut board2, 0, 0, "t");
|
||||||
sobj2.scripting.script_name = Some("t".to_string());
|
|
||||||
let board2 = open_board(2, 1, (1, 0), vec![sobj2]);
|
|
||||||
let scripts2 = HashMap::from([(
|
let scripts2 = HashMap::from([(
|
||||||
"t".to_string(),
|
"t".to_string(),
|
||||||
"fn init(m) { set_key(\"blue\", true); set_key(\"blue\", false); }".to_string(),
|
r#"fn init(me) { set_key("blue", true); set_key("blue", false); }"#.to_string(),
|
||||||
)]);
|
)]);
|
||||||
let mut game2 = GameState::with_scripts(board2, scripts2);
|
let mut game2 = GameState::with_scripts(board2, scripts2);
|
||||||
game2.run_init();
|
game2.run_init();
|
||||||
@@ -819,13 +794,11 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn set_key_unknown_color_logs_error() {
|
fn set_key_unknown_color_logs_error() {
|
||||||
let mut sobj = ObjectDef::new(0, 0);
|
let mut board = open_board(2, 1, (1, 0));
|
||||||
sobj.behavior.solid = false;
|
sensor_at(&mut board, 0, 0, "s");
|
||||||
sobj.scripting.script_name = Some("s".to_string());
|
|
||||||
let board = open_board(2, 1, (1, 0), vec![sobj]);
|
|
||||||
let scripts = HashMap::from([(
|
let scripts = HashMap::from([(
|
||||||
"s".to_string(),
|
"s".to_string(),
|
||||||
r#"fn init(m) { set_key("chartreuse", true); }"#.to_string(),
|
r#"fn init(me) { set_key("chartreuse", true); }"#.to_string(),
|
||||||
)]);
|
)]);
|
||||||
let mut game = GameState::with_scripts(board, scripts);
|
let mut game = GameState::with_scripts(board, scripts);
|
||||||
game.run_init();
|
game.run_init();
|
||||||
|
|||||||
Reference in New Issue
Block a user