portals
This commit is contained in:
@@ -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,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,5 +1,6 @@
|
||||
mod actions;
|
||||
mod collision;
|
||||
mod game_portals;
|
||||
mod map_file;
|
||||
mod movement;
|
||||
mod scripting;
|
||||
|
||||
Reference in New Issue
Block a user