basic drawing
This commit is contained in:
@@ -329,6 +329,50 @@ impl Board {
|
||||
self.objects.insert(id, object);
|
||||
id
|
||||
}
|
||||
|
||||
/// Editor primitive: stamps `arch` (with visual `glyph`) into the cell at
|
||||
/// `(x, y)`, applying the editor's placement/removal rules.
|
||||
///
|
||||
/// Two cases, keyed only on the archetype (the floor is **never** touched — the
|
||||
/// drawing tools cannot place, remove, or alter a floor):
|
||||
///
|
||||
/// - **Terrain** (`arch != Empty`, always solid today): removes any solid object
|
||||
/// already in the cell, then writes `(glyph, arch)` into the cell's terrain.
|
||||
/// - **Erase** (`arch == Empty`): removes the cell's terrain *and* every object in
|
||||
/// it, leaving the floor (a visible `Empty` cell on a lower layer) in place.
|
||||
///
|
||||
/// Terrain is written to the cell's existing terrain layer (the single non-`Empty`
|
||||
/// archetype across layers, if any) or else the top layer; a vacated terrain cell
|
||||
/// becomes a transparent `Empty` so a lower floor shows through. Panics if `(x, y)`
|
||||
/// is out of bounds.
|
||||
pub fn place_archetype(&mut self, x: usize, y: usize, arch: Archetype, glyph: Glyph) {
|
||||
if arch == Archetype::Empty {
|
||||
// Erase: drop every object in the cell and clear its terrain (keep floor).
|
||||
for id in self.object_ids_at(x, y) {
|
||||
self.objects.remove(&id);
|
||||
}
|
||||
if let Some(z) = self.terrain_layer_at(x, y) {
|
||||
*self.get_mut(z, x, y) = (Glyph::transparent(), Archetype::Empty);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Placing solid terrain: a solid object can't share the cell, so drop it.
|
||||
if let Some(id) = self.solid_object_id_at(x, y) {
|
||||
self.objects.remove(&id);
|
||||
}
|
||||
// Reuse the existing terrain layer if the cell already has terrain, else the
|
||||
// top layer (so the new wall draws above any floor on a lower layer).
|
||||
let z = self.terrain_layer_at(x, y).unwrap_or(self.layers.len() - 1);
|
||||
*self.get_mut(z, x, y) = (glyph, arch);
|
||||
}
|
||||
|
||||
/// Returns the index of the layer whose terrain cell at `(x, y)` is non-`Empty`
|
||||
/// (the cell's single terrain archetype, if any). By the one-solid-per-cell
|
||||
/// invariant there is at most one such layer.
|
||||
fn terrain_layer_at(&self, x: usize, y: usize) -> Option<usize> {
|
||||
(0..self.layers.len()).find(|&z| self.get(z, x, y).1 != Archetype::Empty)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -559,6 +603,62 @@ pub(crate) mod tests {
|
||||
assert_eq!(board.glyph_at(0, 0).tile, '*' as u32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn place_wall_keeps_floor_and_removes_solid_object() {
|
||||
// Floor on layer 0, terrain layer on top; a solid object sits at (1,0).
|
||||
let mut board = open_board(3, 1, (2, 0), vec![ObjectDef::new(1, 0)]);
|
||||
let floor = Glyph {
|
||||
tile: '.' as u32,
|
||||
..Glyph::transparent()
|
||||
};
|
||||
add_floor(&mut board, floor);
|
||||
|
||||
let wall = Archetype::Wall.default_glyph();
|
||||
board.place_archetype(1, 0, Archetype::Wall, wall);
|
||||
|
||||
// The wall landed on the terrain (top) layer; the floor below is untouched.
|
||||
assert_eq!(board.get(1, 1, 0), &(wall, Archetype::Wall));
|
||||
assert_eq!(board.get(0, 1, 0).0, floor);
|
||||
// The solid object that was there is gone.
|
||||
assert!(board.object_ids_at(1, 0).is_empty());
|
||||
assert_eq!(board.glyph_at(1, 0), wall);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn place_wall_overwrites_existing_terrain_in_place() {
|
||||
// A crate already occupies the top layer at (1,0).
|
||||
let mut board = open_board(3, 1, (2, 0), vec![]);
|
||||
crate_at(&mut board, 1, 0);
|
||||
let wall = Archetype::Wall.default_glyph();
|
||||
board.place_archetype(1, 0, Archetype::Wall, wall);
|
||||
assert_eq!(board.get(0, 1, 0), &(wall, Archetype::Wall));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn erase_removes_terrain_and_objects_but_keeps_floor() {
|
||||
// Floor, a wall on the terrain layer, and a (non-solid) object all at (1,0).
|
||||
let mut obj = ObjectDef::new(1, 0);
|
||||
obj.solid = false;
|
||||
let mut board = open_board(3, 1, (2, 0), vec![obj]);
|
||||
let floor = Glyph {
|
||||
tile: '.' as u32,
|
||||
..Glyph::transparent()
|
||||
};
|
||||
add_floor(&mut board, floor);
|
||||
wall_at(&mut board, 1, 0);
|
||||
|
||||
board.place_archetype(1, 0, Archetype::Empty, Glyph::transparent());
|
||||
|
||||
// Terrain cleared to transparent Empty; object removed; floor still there.
|
||||
assert_eq!(
|
||||
board.get(1, 1, 0),
|
||||
&(Glyph::transparent(), Archetype::Empty)
|
||||
);
|
||||
assert!(board.object_ids_at(1, 0).is_empty());
|
||||
assert_eq!(board.get(0, 1, 0).0, floor);
|
||||
assert_eq!(board.glyph_at(1, 0), floor);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fresh_board_is_valid_and_reports_errors() {
|
||||
let mut board = open_board(1, 1, (0, 0), vec![]);
|
||||
|
||||
@@ -2,10 +2,10 @@ mod action;
|
||||
mod archetype;
|
||||
mod board;
|
||||
mod builtin_scripts;
|
||||
/// CP437 tile-index → character mapping ([`cp437::tile_to_char`]) for the default font.
|
||||
pub mod cp437;
|
||||
/// The 16 EGA/VGA named colors ([`colors::NAMED_COLORS`]), shared by scripts and the editor.
|
||||
pub mod colors;
|
||||
/// CP437 tile-index → character mapping ([`cp437::tile_to_char`]) for the default font.
|
||||
pub mod cp437;
|
||||
/// Procedural floor generators ([`floor::FloorGenerator`]).
|
||||
pub mod floor;
|
||||
/// Core game types: [`board::Board`], [`glyph::Glyph`], [`archetype::Archetype`], etc.
|
||||
@@ -23,6 +23,7 @@ mod utils;
|
||||
/// World type: a named collection of boards in a single `.toml` file ([`world::World`], [`world::load`]).
|
||||
pub mod world;
|
||||
|
||||
pub use archetype::Archetype;
|
||||
pub use board::Board;
|
||||
pub use utils::Direction;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user