multi board

This commit is contained in:
2026-06-13 01:25:58 -05:00
parent e5c6affc41
commit 73c8a9bd3e
13 changed files with 456 additions and 445 deletions
+58 -24
View File
@@ -1,10 +1,10 @@
use crate::action::Action;
use crate::board::Board;
use crate::log::LogLine;
use crate::script::ScriptHost;
use std::cell::{Ref, RefCell, RefMut};
use std::rc::Rc;
use crate::world::World;
use std::cell::{Ref, RefMut};
use std::time::Duration;
use crate::board::Board;
use crate::utils::{Direction, ObjectId, ScriptArg, Solid};
/// How long a `say()` speech bubble stays on screen, in seconds.
@@ -38,18 +38,19 @@ pub struct SpeechBubble {
/// Holds the active game world and provides game-logic operations.
///
/// `GameState` is the boundary between the engine (rendering, input) and the
/// game data. It owns the board (behind a shared `Rc<RefCell<Board>>`),
/// the message log, and the [`ScriptHost`] driving object scripts. Front-ends and
/// internal logic reach the board through [`board`](GameState::board) /
/// [`board_mut`](GameState::board_mut). Scripts read the board directly and
/// request mutations as commands, applied by [`apply_commands`](GameState::apply_commands)
/// after each script batch.
/// game data. It owns a [`World`] (all boards + world scripts) and tracks which
/// board is currently active. Front-ends reach the active board through
/// [`board`](GameState::board) / [`board_mut`](GameState::board_mut). Scripts
/// read the board and queue mutations via commands applied by `resolve` after
/// each script batch.
pub struct GameState {
/// The scriptable world, shared with the script host's read getters.
board: Rc<RefCell<Board>>,
/// All boards and world-level scripts.
world: World,
/// Key of the currently active board in [`world.boards`](World::boards).
current_board_name: String,
/// The in-game message log, oldest first (newest pushed at the end).
pub log: Vec<LogLine>,
/// The Rhai scripting runtime driving this board's scripted objects.
/// The Rhai scripting runtime for the active board's objects.
scripts: ScriptHost,
/// Active speech bubbles from `say()` calls, displayed by the front-end over the board.
pub speech_bubbles: Vec<SpeechBubble>,
@@ -59,34 +60,67 @@ pub struct GameState {
}
impl GameState {
/// Creates a `GameState` from a pre-loaded [`Board`].
/// Creates a [`GameState`] from a loaded [`World`], starting on `world.start`.
///
/// Compiles the board's object scripts but does **not** run any of them;
/// call [`GameState::run_init`] once the game is ready to start. Any script
/// compile errors are surfaced into the log here.
pub fn new(board: Board) -> Self {
let board = Rc::new(RefCell::new(board));
let scripts = ScriptHost::new(&board);
/// The starting board's objects are compiled and registered with the
/// [`ScriptHost`] using `world.scripts`. No lifecycle hooks are run here;
/// call [`run_init`](GameState::run_init) once the game is ready to start.
/// Compile errors are surfaced into the log immediately.
pub fn from_world(world: World) -> Self {
let name = world.start.clone();
let host = ScriptHost::new(
world.boards.get(&name).expect("world::load guarantees start board exists"),
&world.scripts,
);
let mut state = Self {
board,
world,
current_board_name: name,
log: Vec::new(),
scripts,
scripts: host,
speech_bubbles: Vec::new(),
active_scroll: None,
};
// Surface any compile-time script errors collected during ScriptHost::new.
state.drain_errors();
state
}
/// Creates a `GameState` from a single [`Board`] with no scripts.
///
/// Test-only convenience. Use [`from_world`](GameState::from_world) in production.
#[cfg(test)]
pub fn new(board: Board) -> Self {
Self::with_scripts(board, std::collections::HashMap::new())
}
/// Creates a `GameState` from a single [`Board`] and an explicit script pool.
///
/// Test-only convenience. Use [`from_world`](GameState::from_world) in production.
#[cfg(test)]
pub fn with_scripts(board: Board, scripts: std::collections::HashMap<String, String>) -> Self {
// Wrap the bare board in Rc<RefCell<Board>> to match World::boards storage.
let board_ref = std::rc::Rc::new(std::cell::RefCell::new(board));
let world = World {
name: String::new(),
start: "board".to_string(),
scripts,
boards: std::collections::HashMap::from([("board".to_string(), board_ref)]),
};
Self::from_world(world)
}
/// Borrows the active board for reading (e.g. by a front-end renderer).
pub fn board(&self) -> Ref<'_, Board> {
self.board.borrow()
self.world.boards[&self.current_board_name].borrow()
}
/// Borrows the active board for mutation.
pub fn board_mut(&self) -> RefMut<'_, Board> {
self.board.borrow_mut()
self.world.boards[&self.current_board_name].borrow_mut()
}
/// The name (key) of the currently active board within the world.
pub fn current_board_name(&self) -> &str {
&self.current_board_name
}
/// Appends a styled message to the log.