//! World type: a named collection of boards loaded from a single `.toml` file. use crate::board::Board; use crate::map_file::MapFile; use serde::Deserialize; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; /// A named world containing one or more boards, loaded from a `.toml` file. /// /// The file groups all boards under a `[boards]` table. Each board key maps /// to a full board definition (the same structure as a single-board map file, /// but nested under `[boards..*]`). [`World::start`] names the board the /// player enters first. /// /// Scripts live at the world level ([`World::scripts`]) rather than on each /// board, so multiple boards can reference the same script name without /// duplicating source text. pub struct World { /// Human-readable name for this world, from the `[world]` header. pub name: String, /// Key of the starting board; matches a key in [`World::boards`]. pub start: String, /// Named Rhai scripts shared across all boards: script name → source text. /// /// Objects on any board reference a script by name via /// [`ObjectDef::script_name`](crate::object_def::ObjectDef). The script pool is /// passed to [`GameState::from_world`](crate::game::GameState::from_world) at startup. pub scripts: HashMap, /// All boards in this world, keyed by their identifier string. /// /// Every board is always present here as a shared `Rc>` — no board /// is ever "removed" when active. [`GameState`](crate::game::GameState) holds a /// clone of the active board's `Rc` and borrows through it. pub boards: HashMap>>, } /// Serde target for the top-level `.toml` world file. #[derive(Deserialize)] struct WorldFile { world: WorldHeader, /// The `[scripts]` table: script name → Rhai source. Shared across all boards. #[serde(default, skip_serializing_if = "HashMap::is_empty")] scripts: HashMap, /// Each value deserializes as a [`MapFile`], reusing the existing per-board /// serde types from `map_file.rs`. boards: HashMap, } /// The `[world]` header section of a world file. #[derive(Deserialize)] struct WorldHeader { /// Human-readable name for this world. name: String, /// Key of the board to start on; must match a key in `[boards]`. start: String, } /// Load a world from a `.toml` world file at `path`. /// /// Returns an error if the file cannot be read or parsed, if any board fails /// the hard-error conversion (only a grid-dimension mismatch qualifies — all /// other problems are recorded on [`Board::load_errors`]), or if [`World::start`] /// does not match any board key in the file. pub fn load(path: &str) -> Result> { let text = std::fs::read_to_string(path)?; let wf: WorldFile = toml::from_str(&text)?; // Convert each MapFile into a Board. Grid-dimension mismatches are the only // hard errors; everything else is nonfatal and lands on Board::load_errors. let mut boards = HashMap::new(); for (key, map_file) in wf.boards { let board = Board::try_from(map_file).map_err(|e| format!("board '{}': {}", key, e))?; boards.insert(key, Rc::new(RefCell::new(board))); } // Guard: the start key must actually exist in the boards map. if !boards.contains_key(&wf.world.start) { return Err(format!( "start board '{}' not found in world '{}'", wf.world.start, wf.world.name ) .into()); } Ok(World { name: wf.world.name, start: wf.world.start, scripts: wf.scripts, boards, }) }