multi board
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
//! World type: a named collection of boards loaded from a single `.toml` file.
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
use crate::board::Board;
|
||||
use crate::map_file::MapFile;
|
||||
use serde::Deserialize;
|
||||
|
||||
/// 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.<key>.*]`). [`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<String, String>,
|
||||
/// All boards in this world, keyed by their identifier string.
|
||||
///
|
||||
/// Every board is always present here as a shared `Rc<RefCell<Board>>` — 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<String, Rc<RefCell<Board>>>,
|
||||
}
|
||||
|
||||
/// 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<String, String>,
|
||||
/// Each value deserializes as a [`MapFile`], reusing the existing per-board
|
||||
/// serde types from `map_file.rs`.
|
||||
boards: HashMap<String, MapFile>,
|
||||
}
|
||||
|
||||
/// 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<World, Box<dyn std::error::Error>> {
|
||||
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,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user