playtest mode
This commit is contained in:
@@ -24,6 +24,12 @@ use std::collections::{BTreeMap, HashMap};
|
||||
/// separate element palette or index indirection. Access cells with
|
||||
/// [`Board::get`] and [`Board::get_mut`] using `(x, y)` coordinates.
|
||||
/// Use [`Board::is_passable`] for collision checks.
|
||||
///
|
||||
/// `Board` derives [`Clone`] to support deep-copying a whole [`World`](crate::world::World)
|
||||
/// (see [`World::deep_clone`](crate::world::World::deep_clone)) — e.g. the editor's
|
||||
/// playtest runs a game against an isolated copy so play mutations never touch the
|
||||
/// boards being edited.
|
||||
#[derive(Clone)]
|
||||
pub struct Board {
|
||||
/// Human-readable name for this board, loaded from the map file and round-tripped on save.
|
||||
pub name: String,
|
||||
|
||||
@@ -29,6 +29,7 @@ use tinyrand::StdRand;
|
||||
/// it draws nothing and lets the layer beneath show through. Solidity comes from
|
||||
/// the archetype and is independent of transparency. The grid is `width * height`
|
||||
/// long; index it via the owning [`Board`](crate::board::Board)'s dimensions.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Layer {
|
||||
/// Row-major `(Glyph, Archetype)` cells for this layer.
|
||||
pub(crate) cells: Vec<(Glyph, Archetype)>,
|
||||
|
||||
@@ -22,6 +22,7 @@ use std::collections::HashSet;
|
||||
/// Scripts are executed by [`crate::script::ScriptHost`]: an object's optional
|
||||
/// `init()` and `tick(dt)` functions are called via [`GameState::run_init`] and
|
||||
/// [`GameState::tick`]. Other event hooks (touch, shoot, …) are future work.
|
||||
#[derive(Clone)]
|
||||
pub struct ObjectDef {
|
||||
/// Stable identity assigned by [`crate::board::Board::add_object`].
|
||||
/// `0` is the sentinel meaning "not yet inserted into a board".
|
||||
|
||||
@@ -36,6 +36,29 @@ pub struct World {
|
||||
pub boards: HashMap<String, Rc<RefCell<Board>>>,
|
||||
}
|
||||
|
||||
impl World {
|
||||
/// Returns a fully independent copy of this world.
|
||||
///
|
||||
/// Unlike a shallow clone (which would copy each board's `Rc` and so keep
|
||||
/// *sharing* the underlying [`Board`]s), this rebuilds every board into a brand
|
||||
/// new `Rc<RefCell<Board>>`, so mutating the copy never affects the original.
|
||||
/// The editor uses it to playtest a board against an isolated game state, leaving
|
||||
/// the boards being edited untouched. (An explicit method rather than a `Clone`
|
||||
/// impl, precisely because the shallow, `Rc`-sharing behavior would be surprising.)
|
||||
pub fn deep_clone(&self) -> World {
|
||||
World {
|
||||
name: self.name.clone(),
|
||||
start: self.start.clone(),
|
||||
scripts: self.scripts.clone(),
|
||||
boards: self
|
||||
.boards
|
||||
.iter()
|
||||
.map(|(key, board)| (key.clone(), Rc::new(RefCell::new(board.borrow().clone()))))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Serde target for the top-level `.toml` world file.
|
||||
#[derive(Deserialize)]
|
||||
struct WorldFile {
|
||||
@@ -91,3 +114,46 @@ pub fn load(path: &str) -> Result<World, Box<dyn std::error::Error>> {
|
||||
boards,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::World;
|
||||
use crate::archetype::Archetype;
|
||||
use crate::board::tests::open_board;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// A `deep_clone`d world owns independent boards: mutating the copy must leave the
|
||||
/// original board untouched (the property the editor's playtest relies on).
|
||||
#[test]
|
||||
fn deep_clone_isolates_boards() {
|
||||
let board = open_board(3, 1, (0, 0), vec![]);
|
||||
let mut boards = HashMap::new();
|
||||
boards.insert("start".to_string(), Rc::new(RefCell::new(board)));
|
||||
let world = World {
|
||||
name: "w".into(),
|
||||
start: "start".into(),
|
||||
scripts: HashMap::new(),
|
||||
boards,
|
||||
};
|
||||
|
||||
let copy = world.deep_clone();
|
||||
// Stamp a wall into the copy's board.
|
||||
copy.boards["start"].borrow_mut().place_archetype(
|
||||
1,
|
||||
0,
|
||||
Archetype::Wall,
|
||||
Archetype::Wall.default_glyph(),
|
||||
);
|
||||
|
||||
// The copy changed; the original is still empty at that cell.
|
||||
assert_eq!(copy.boards["start"].borrow().get(0, 1, 0).1, Archetype::Wall);
|
||||
assert_eq!(
|
||||
world.boards["start"].borrow().get(0, 1, 0).1,
|
||||
Archetype::Empty
|
||||
);
|
||||
// And they are genuinely different allocations.
|
||||
assert!(!Rc::ptr_eq(&world.boards["start"], ©.boards["start"]));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user