This commit is contained in:
2026-06-13 16:24:29 -05:00
parent 78547696d7
commit 49d32eedf5
9 changed files with 410 additions and 15 deletions
+98
View File
@@ -0,0 +1,98 @@
use std::collections::{BTreeMap, HashMap};
use std::rc::Rc;
use std::cell::RefCell;
use crate::archetype::Archetype;
use crate::board::Board;
use crate::game::GameState;
use crate::utils::{Direction, Player, PortalDef};
use crate::world::World;
/// Builds a 3×3 board with the player at `(px, py)` and the given portals.
fn make_board(px: i32, py: i32, portals: Vec<PortalDef>) -> Board {
Board {
name: "test".into(),
width: 3,
height: 3,
cells: vec![(Archetype::Empty.default_glyph(), Archetype::Empty); 9],
floor: vec![Archetype::Empty.default_glyph(); 9],
floor_spec: None,
player: Player { x: px, y: py },
objects: BTreeMap::new(),
next_object_id: 1,
portals,
board_script_name: None,
load_errors: Vec::new(),
}
}
/// Two-board world used by all tests in this module.
///
/// - `"b1"`: player at `(0, 0)`, portal `"to_b2"` at `(2, 0)` → `"b2"` / `"from_b1"`
/// - `"b2"`: player at `(0, 0)`, portal `"from_b1"` at `(1, 1)` → `"b1"` / `"to_b2"`
fn two_board_world() -> World {
let b1 = make_board(0, 0, vec![
PortalDef { name: "to_b2".into(), x: 2, y: 0, target_map: "b2".into(), target_entry: "from_b1".into() },
]);
let b2 = make_board(0, 0, vec![
PortalDef { name: "from_b1".into(), x: 1, y: 1, target_map: "b1".into(), target_entry: "to_b2".into() },
]);
World {
name: "test".into(),
start: "b1".into(),
scripts: HashMap::new(),
boards: HashMap::from([
("b1".to_string(), Rc::new(RefCell::new(b1))),
("b2".to_string(), Rc::new(RefCell::new(b2))),
]),
}
}
#[test]
fn enter_board_switches_board() {
let mut game = GameState::from_world(two_board_world());
game.enter_board("b2", "from_b1");
assert_eq!(game.current_board_name(), "b2");
}
#[test]
fn enter_board_places_player_at_arrival_portal() {
let mut game = GameState::from_world(two_board_world());
game.enter_board("b2", "from_b1");
let board = game.board();
// Arrival portal "from_b1" is at (1, 1) on b2.
assert_eq!(board.player.x, 1);
assert_eq!(board.player.y, 1);
}
#[test]
fn enter_board_sets_board_transition() {
let mut game = GameState::from_world(two_board_world());
game.enter_board("b2", "from_b1");
assert_eq!(game.board_transition, Some(1.0));
}
#[test]
fn enter_board_unknown_board_logs_error() {
let mut game = GameState::from_world(two_board_world());
game.enter_board("nope", "entry");
assert_eq!(game.current_board_name(), "b1", "board should not change");
assert!(!game.log.is_empty(), "an error should be logged");
}
#[test]
fn enter_board_unknown_entry_logs_error() {
let mut game = GameState::from_world(two_board_world());
game.enter_board("b2", "nope");
assert_eq!(game.current_board_name(), "b1", "board should not change");
assert!(!game.log.is_empty(), "an error should be logged");
}
#[test]
fn try_move_onto_portal_switches_board() {
let world = two_board_world();
// Start the player one cell west of the portal at (2, 0).
world.boards["b1"].borrow_mut().player = Player { x: 1, y: 0 };
let mut game = GameState::from_world(world);
game.try_move(Direction::East);
assert_eq!(game.current_board_name(), "b2");
}
+1
View File
@@ -1,6 +1,7 @@
mod grid_errors;
mod object_placement;
mod player_placement;
mod portal_placement;
mod round_trip;
use crate::board::Board;
@@ -0,0 +1,47 @@
use super::{load_board, map_3x1};
fn portal_toml(portals: &str) -> String {
format!(
"{}{}",
map_3x1("...", ""),
portals
)
}
#[test]
fn portal_duplicate_name_drops_second() {
let toml = portal_toml(
"[[portals]]\nname = \"a\"\nx = 0\ny = 0\ntarget_map = \"x\"\ntarget_entry = \"b\"\n\
[[portals]]\nname = \"a\"\nx = 1\ny = 0\ntarget_map = \"x\"\ntarget_entry = \"c\"\n",
);
let board = load_board(&toml);
assert_eq!(board.portals.len(), 1, "second portal with duplicate name should be dropped");
assert_eq!(board.portals[0].x, 0);
assert!(!board.is_valid(), "duplicate name should be a load error");
}
#[test]
fn portal_palette_char_places_portal_at_grid_position() {
let toml = format!(
"{}{}",
map_3x1("1..", ""),
"[[portals]]\nname = \"p\"\npalette = \"1\"\ntarget_map = \"x\"\ntarget_entry = \"e\"\n"
);
let board = load_board(&toml);
assert_eq!(board.portals.len(), 1);
assert_eq!(board.portals[0].x, 0);
assert_eq!(board.portals[0].y, 0);
assert_eq!(board.portals[0].name, "p");
}
#[test]
fn portal_palette_char_colliding_with_object_is_dropped() {
let toml = format!(
"{}{}{}",
map_3x1("1..", ""),
"[[objects]]\npalette = \"1\"\ntile = 64\nfg = \"#ffffff\"\nbg = \"#000000\"\n",
"[[portals]]\nname = \"p\"\npalette = \"1\"\ntarget_map = \"x\"\ntarget_entry = \"e\"\n"
);
let board = load_board(&toml);
assert_eq!(board.portals.len(), 0, "portal whose palette char is already an object char should be dropped");
}
+1
View File
@@ -1,5 +1,6 @@
mod actions;
mod collision;
mod game_portals;
mod map_file;
mod movement;
mod scripting;