the huge refactor

This commit is contained in:
2026-07-24 21:52:05 -05:00
parent 5146cc9bcc
commit 917f4b1bf0
43 changed files with 1807 additions and 3267 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
use super::{log_texts, scripted_object, scripts_from};
use crate::archetype::Archetype;
use crate::builtin::Archetype;
use crate::board::tests::{crate_at, open_board, wall_at};
use crate::game::GameState;
use std::time::Duration;
+10 -9
View File
@@ -1,16 +1,17 @@
use crate::archetype::Archetype;
use crate::builtin::Archetype;
use crate::board::Board;
use crate::floor::Floor;
use crate::game::GameState;
use crate::glyph::Glyph;
use crate::utils::{Direction, PlayerPos, PortalDef};
use crate::utils::{Direction, PlayerPos};
use crate::world::World;
use std::cell::RefCell;
use std::collections::{BTreeMap, HashMap};
use std::rc::Rc;
use crate::portal::Portal;
/// Builds a 3×3 board with the player at `(px, py)` and the given portals.
fn make_board(px: i64, py: i64, portals: Vec<PortalDef>) -> Board {
fn make_board(px: i64, py: i64, portals: Vec<Portal>) -> Board {
Board {
name: "test".into(),
width: 3,
@@ -38,23 +39,23 @@ fn two_board_world() -> World {
let b1 = make_board(
0,
0,
vec![PortalDef {
vec![Portal {
name: "to_b2".into(),
x: 2,
y: 0,
target_map: "b2".into(),
target_entry: "from_b1".into(),
target_board: "b2".into(),
target_name: "from_b1".into(),
}],
);
let b2 = make_board(
0,
0,
vec![PortalDef {
vec![Portal {
name: "from_b1".into(),
x: 1,
y: 1,
target_map: "b1".into(),
target_entry: "to_b2".into(),
target_board: "b1".into(),
target_name: "to_b2".into(),
}],
);
World {
+2 -2
View File
@@ -1,5 +1,5 @@
use super::load_board;
use crate::archetype::Archetype;
use crate::builtin::Archetype;
#[test]
fn fill_builds_a_full_grid_of_one_char() {
@@ -23,7 +23,7 @@ palette = { "#" = { kind = "wall", tile = 35, fg = "#808080", bg = "#606060" } }
assert!(board.solid_at(x, y).unwrap().player());
} else {
let obj = &board.objects[&board.object_ids_at(x, y)[0]];
let tag = obj.tags.iter().next().unwrap();
let tag = obj.scripting.tags.iter().next().unwrap();
assert_eq!(tag, "BUILTIN_wall")
}
+1 -1
View File
@@ -1,5 +1,5 @@
use super::{grid, load_board, map};
use crate::archetype::Archetype;
use crate::builtin::Archetype;
use crate::board::Board;
use crate::map_file::MapFile;
@@ -1,5 +1,5 @@
use super::{grid, load_board, map, map_3x1_object};
use crate::archetype::Archetype;
use crate::builtin::Archetype;
/// Palette shorthand.
const PLAYER: (&str, &str) = ("@", "kind = \"player\"");
@@ -30,8 +30,8 @@ fn duplicate_name_clears_second_but_keeps_both_objects() {
),
));
assert_eq!(board.objects.len(), 2, "both objects survive");
assert_eq!(board.objects[&1].name.as_deref(), Some("gate"));
assert_eq!(board.objects[&2].name, None);
assert_eq!(board.objects[&1].scripting.name.as_deref(), Some("gate"));
assert_eq!(board.objects[&2].scripting.name, None);
assert!(!board.is_valid(), "duplicate name is a nonfatal load error");
}
@@ -66,9 +66,9 @@ fn palette_char_multi_occurrence_only_first_keeps_name() {
),
));
assert_eq!(board.objects.len(), 3);
assert_eq!(board.objects[&1].name.as_deref(), Some("guard"));
assert_eq!(board.objects[&2].name, None);
assert_eq!(board.objects[&3].name, None);
assert_eq!(board.objects[&1].scripting.name.as_deref(), Some("guard"));
assert_eq!(board.objects[&2].scripting.name, None);
assert_eq!(board.objects[&3].scripting.name, None);
}
#[test]
@@ -1,5 +1,5 @@
use super::{grid, load_board, map};
use crate::archetype::Archetype;
use crate::builtin::Archetype;
/// Palette shorthands shared by these tests.
const EMPTY: (&str, &str) = (".", "kind = \"empty\"");
+7 -7
View File
@@ -2,19 +2,19 @@
//! carrying the embedded `pusher.rhai` plus a `BUILTIN_pusher_<dir>` tag).
use super::{grid, load_board, map};
use crate::archetype::Archetype;
use crate::builtin::Archetype;
use crate::game::GameState;
use crate::map_file::MapFile;
use crate::object_def::ObjectDef;
use std::time::Duration;
use crate::{Board, Builtin};
use crate::Board;
/// Finds the pusher object on a board (by its built-in tag).
fn pusher<'a>(board: &'a crate::board::Board, id: &mut u32) -> &'a ObjectDef {
let (&oid, obj) = board
.objects
.iter()
.find(|(_, o)| o.tags.contains("BUILTIN_pusher_east"))
.find(|(_, o)| o.scripting.tags.contains("BUILTIN_pusher_east"))
.expect("pusher object");
*id = oid;
obj
@@ -34,17 +34,17 @@ fn pusher_loads_as_a_tagged_scripted_solid_object() {
let p = pusher(&board, &mut id);
assert_eq!((p.x, p.y), (0, 0));
assert!(
p.builtin_script.is_some(),
p.scripting.builtin_script.is_some(),
"carries the embedded pusher script"
);
// Each alias gets its own compile-cache key (e.g. "BUILTIN_pusher_east") so the
// script can read direction from Me.has_tag("BUILTIN_pusher_east").
assert_eq!(p.script_name.as_deref(), Some("BUILTIN_pusher_east"));
assert_eq!(p.scripting.script_name.as_deref(), Some("BUILTIN_pusher_east"));
assert!(!board.is_passable(0, 0), "pusher is solid");
}
fn is_tag(board: &Board, x: usize, y: usize, tag: &str) -> bool {
board.object_ids_at(x, y).iter().any(|id| board.objects[id].tags.contains(tag))
board.object_ids_at(x, y).iter().any(|id| board.objects[id].scripting.tags.contains(tag))
}
#[test]
@@ -135,5 +135,5 @@ fn pusher_round_trips_to_its_keyword() {
let mut id = 0;
let p = pusher(&board2, &mut id);
assert_eq!((p.x, p.y), (0, 0));
assert!(p.builtin_script.is_some());
assert!(p.scripting.builtin_script.is_some());
}
+6 -6
View File
@@ -60,8 +60,8 @@ fn object_tags_round_trip_through_toml() {
);
let board = load_board(&toml);
let obj0 = &board.objects[&1];
assert!(obj0.tags.contains("enemy") && obj0.tags.contains("boss"));
assert_eq!(obj0.tags.len(), 2);
assert!(obj0.scripting.tags.contains("enemy") && obj0.scripting.tags.contains("boss"));
assert_eq!(obj0.scripting.tags.len(), 2);
// Saved tags must be sorted alphabetically (boss before enemy).
let toml_out = toml::to_string_pretty(&MapFile::from(&board)).unwrap();
@@ -70,7 +70,7 @@ fn object_tags_round_trip_through_toml() {
assert!(boss < enemy, "tags must be sorted: boss before enemy");
let board2 = load_board(&toml_out);
assert_eq!(board2.objects[&1].tags, obj0.tags);
assert_eq!(board2.objects[&1].scripting.tags, obj0.scripting.tags);
}
#[test]
@@ -95,7 +95,7 @@ fn object_name_round_trips_through_toml() {
),
);
let board = load_board(&toml);
assert_eq!(board.objects[&1].name.as_deref(), Some("beacon"));
assert_eq!(board.objects[&1].scripting.name.as_deref(), Some("beacon"));
let toml_out = toml::to_string_pretty(&MapFile::from(&board)).unwrap();
assert!(
@@ -103,7 +103,7 @@ fn object_name_round_trips_through_toml() {
"name must appear in saved TOML"
);
let board2 = load_board(&toml_out);
assert_eq!(board2.objects[&1].name.as_deref(), Some("beacon"));
assert_eq!(board2.objects[&1].scripting.name.as_deref(), Some("beacon"));
}
#[test]
@@ -111,7 +111,7 @@ fn unnamed_object_name_stays_none_through_toml() {
let toml = map(3, 1, &grid("@O.", &[PLAYER, ("O", &obj("")), EMPTY]));
let board2 = round_trip(&toml);
assert_eq!(
board2.objects[&1].name, None,
board2.objects[&1].scripting.name, None,
"unnamed object must round-trip as None"
);
}
+5 -5
View File
@@ -18,16 +18,16 @@ fn spinner_loads_as_a_tagged_scripted_solid_object() {
let (_, obj) = board
.objects
.iter()
.find(|(_, o)| o.tags.contains("BUILTIN_spinner_cw"))
.find(|(_, o)| o.scripting.tags.contains("BUILTIN_spinner_cw"))
.expect("spinner object");
assert_eq!((obj.x, obj.y), (0, 0));
assert!(
obj.builtin_script.is_some(),
obj.scripting.builtin_script.is_some(),
"carries the embedded spinner script"
);
// Each alias gets its own compile-cache key (e.g. "BUILTIN_spinner_cw") so the
// script can read direction from Me.has_tag("BUILTIN_spinner_cw").
assert_eq!(obj.script_name.as_deref(), Some("BUILTIN_spinner_cw"));
assert_eq!(obj.scripting.script_name.as_deref(), Some("BUILTIN_spinner_cw"));
assert!(!board.is_passable(0, 0), "spinner is solid");
}
@@ -53,8 +53,8 @@ fn spinner_round_trips_to_its_keyword() {
let (_, obj) = board2
.objects
.iter()
.find(|(_, o)| o.tags.contains("BUILTIN_spinner_ccw"))
.find(|(_, o)| o.scripting.tags.contains("BUILTIN_spinner_ccw"))
.expect("spinner object after round-trip");
assert_eq!((obj.x, obj.y), (0, 0));
assert!(obj.builtin_script.is_some());
assert!(obj.scripting.builtin_script.is_some());
}
+3 -3
View File
@@ -22,10 +22,10 @@ fn transporter_loads_as_a_tagged_scripted_object() {
let (_, obj) = board
.objects
.iter()
.find(|(_, o)| o.tags.contains("BUILTIN_transporter_east"))
.find(|(_, o)| o.scripting.tags.contains("BUILTIN_transporter_east"))
.expect("transporter object");
assert_eq!((obj.x, obj.y), (1, 0));
assert!(obj.builtin_script.is_some(), "carries the embedded script");
assert!(obj.scripting.builtin_script.is_some(), "carries the embedded script");
}
#[test]
@@ -145,7 +145,7 @@ fn transporter_round_trips_to_its_keyword() {
board2
.objects
.values()
.any(|o| o.tags.contains("BUILTIN_transporter_east") && o.builtin_script.is_some()),
.any(|o| o.scripting.tags.contains("BUILTIN_transporter_east") && o.scripting.builtin_script.is_some()),
"reloads as a tagged transporter object",
);
}
+3 -3
View File
@@ -5,7 +5,7 @@ mod map_file;
mod movement;
mod scripting;
use crate::archetype::Archetype;
use crate::builtin::Archetype;
use crate::board::Board;
use crate::floor::Floor;
use crate::game::GameState;
@@ -24,7 +24,7 @@ fn board_with_object(
) -> (Board, HashMap<String, String>) {
let mut object = ObjectDef::new(0, 0);
object.id = 1;
object.script_name = object_script.map(str::to_string);
object.scripting.script_name = object_script.map(str::to_string);
let board = Board {
name: "test".into(),
width: 1,
@@ -57,7 +57,7 @@ fn scripts_from(pairs: &[(&str, &str)]) -> HashMap<String, String> {
/// Returns an `ObjectDef` at `(x, y)` bound to the named script.
fn scripted_object(x: usize, y: usize, script: &str) -> ObjectDef {
let mut o = ObjectDef::new(x, y);
o.script_name = Some(script.to_string());
o.scripting.script_name = Some(script.to_string());
o
}
+1 -1
View File
@@ -1,4 +1,4 @@
use crate::archetype::Archetype;
use crate::builtin::Archetype;
use crate::board::tests::{add_floor, crate_at, open_board, stamp, wall_at};
use crate::game::GameState;
use crate::glyph::Glyph;
+6 -5
View File
@@ -131,7 +131,7 @@ fn set_tag_adds_and_removes_via_my_id() {
);
let mut game = GameState::with_scripts(board, scripts);
game.run_init();
assert!(game.board().objects[&1].tags.contains("active"));
assert!(game.board().objects[&1].scripting.tags.contains("active"));
// A script removes a pre-existing tag.
let (mut board2, scripts2) = board_with_object(
@@ -143,11 +143,12 @@ fn set_tag_adds_and_removes_via_my_id() {
.objects
.get_mut(&1)
.unwrap()
.scripting
.tags
.insert("active".to_string());
let mut game2 = GameState::with_scripts(board2, scripts2);
game2.run_init();
assert!(!game2.board().objects[&1].tags.contains("active"));
assert!(!game2.board().objects[&1].scripting.tags.contains("active"));
}
#[test]
@@ -175,7 +176,7 @@ fn objects_with_tag_returns_matching_ids() {
let obj1 = scripted_object(0, 0, "q");
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());
obj2.scripting.tags.insert("enemy".to_string());
let board = open_board(5, 1, (4, 0), vec![obj1, obj2]);
let mut game = GameState::with_scripts(
board,
@@ -202,7 +203,7 @@ 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, scripts) =
board_with_object(Some("n"), &[("n", r#"fn init(m) { log(m.name); }"#)]);
board.objects.get_mut(&1).unwrap().name = Some("beacon".to_string());
board.objects.get_mut(&1).unwrap().scripting.name = Some("beacon".to_string());
let mut game = GameState::with_scripts(board, scripts);
game.run_init();
assert_eq!(log_texts(&game), vec!["beacon"]);
@@ -221,7 +222,7 @@ fn object_id_for_name_finds_by_name() {
// object_id_for_name to find the named one and logs its id.
let obj1 = scripted_object(0, 0, "q");
let mut obj2 = scripted_object(1, 0, "none");
obj2.name = Some("target".to_string());
obj2.scripting.name = Some("target".to_string());
let board = open_board(5, 1, (4, 0), vec![obj1, obj2]);
let mut game = GameState::with_scripts(
board,