Replace Element with Behavior + Archetype; update map format
Element is replaced by two types: Behavior (a plain struct of runtime properties: passable, opaque) and Archetype (an enum of named presets: Empty, Wall, Object, ErrorBlock). Board.cells changes from Vec<(Glyph, usize)> to Vec<(Glyph, Archetype)>, eliminating the per-board element palette. Map files now reference archetypes by name (archetype = "wall") instead of property bags (passable = false), so the archetype list can be reordered without breaking saved games. Unknown archetype names produce an ErrorBlock cell (yellow ? on red) so malformed maps are immediately visible in-game. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+4
-3
@@ -5,8 +5,9 @@ height = 25
|
|||||||
player_start = [30, 12]
|
player_start = [30, 12]
|
||||||
|
|
||||||
[palette]
|
[palette]
|
||||||
" " = { passable = true, ch = " ", fg = "#000000", bg = "#000000" }
|
" " = { archetype = "empty", ch = " ", fg = "#000000", bg = "#000000" }
|
||||||
"#" = { passable = false, ch = "#", fg = "#808080", bg = "#606060" }
|
"#" = { archetype = "wall", ch = "#", fg = "#808080", bg = "#606060" }
|
||||||
|
"+" = { archetype = "banana", ch = "#", fg = "#808080", bg = "#606060" }
|
||||||
|
|
||||||
[grid]
|
[grid]
|
||||||
content = """
|
content = """
|
||||||
@@ -18,7 +19,7 @@ content = """
|
|||||||
# #
|
# #
|
||||||
# ###### #
|
# ###### #
|
||||||
# # #
|
# # #
|
||||||
# # #
|
# +++ # #
|
||||||
# # #
|
# # #
|
||||||
# #
|
# #
|
||||||
# #
|
# #
|
||||||
|
|||||||
+127
-47
@@ -4,10 +4,10 @@ use serde::Deserialize;
|
|||||||
/// The visual representation of a single board cell.
|
/// The visual representation of a single board cell.
|
||||||
///
|
///
|
||||||
/// `Glyph` holds everything needed to draw one cell on screen: which character
|
/// `Glyph` holds everything needed to draw one cell on screen: which character
|
||||||
/// to display and what colors to use. It is stored per-cell (not per element
|
/// to display and what colors to use. It is stored per-cell (not per archetype),
|
||||||
/// type), so individual cells can vary their appearance independently — for
|
/// so individual cells can vary their appearance independently — for example,
|
||||||
/// example, a field of "fire" tiles where each flame has a slightly different
|
/// a field of "fire" tiles where each flame has a slightly different color
|
||||||
/// color while all sharing the same [`Element`] behavior.
|
/// while all sharing the same [`Archetype`] behavior.
|
||||||
///
|
///
|
||||||
/// `Glyph` values come from the map file palette and are set at load time.
|
/// `Glyph` values come from the map file palette and are set at load time.
|
||||||
/// The player is the only entity whose glyph is hardcoded at runtime
|
/// The player is the only entity whose glyph is hardcoded at runtime
|
||||||
@@ -33,20 +33,115 @@ impl Glyph {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The behavioral definition of a tile type.
|
/// The behavioral properties of a board cell at runtime.
|
||||||
///
|
///
|
||||||
/// `Element` describes how a tile *behaves*, independent of how it looks.
|
/// `Behavior` is a plain data struct returned by [`Archetype::behavior`]. It
|
||||||
/// Behavior is shared: many cells on the board can reference the same
|
/// contains the properties the engine needs to simulate a cell — currently
|
||||||
/// `Element` by index (see [`Board::elements`]), so changing one `Element`
|
/// passability and opacity. Future properties (pushable, shootable, etc.) can
|
||||||
/// affects all cells that use it.
|
/// be added here without changing call sites.
|
||||||
///
|
///
|
||||||
/// The visual side is handled separately by [`Glyph`], which is stored
|
/// For scripted objects (`Archetype::Object`), the behavior will eventually be
|
||||||
/// per-cell and can vary even among cells of the same element type.
|
/// determined by the Rhai script rather than this struct. See the Object variant
|
||||||
pub struct Element {
|
/// for details.
|
||||||
/// Whether the player (or other entities) can walk onto this tile.
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub struct Behavior {
|
||||||
|
/// Whether an entity can walk through this cell.
|
||||||
pub passable: bool,
|
pub passable: bool,
|
||||||
|
/// Whether this cell blocks line of sight (reserved for future rendering).
|
||||||
|
pub opaque: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A class of board cell, encoding its default behavior and appearance.
|
||||||
|
///
|
||||||
|
/// `Archetype` is an enum of the element types the engine knows about. Each
|
||||||
|
/// variant provides a default [`Behavior`] (via [`Archetype::behavior`]) and a
|
||||||
|
/// default [`Glyph`] (via [`Archetype::default_glyph`]) used when the editor
|
||||||
|
/// stamps a cell.
|
||||||
|
///
|
||||||
|
/// Map files reference archetypes by [`name`](Archetype::name) (e.g. `"wall"`),
|
||||||
|
/// so the list of variants can be reordered without breaking saved games.
|
||||||
|
///
|
||||||
|
/// ## Object special case
|
||||||
|
///
|
||||||
|
/// `Archetype::Object` currently returns a static default `Behavior`.
|
||||||
|
/// TODO: In the future, Object passability and opacity will come from the cell's Rhai script
|
||||||
|
/// rather than from this enum. [`Board::is_passable`] will need a special branch
|
||||||
|
/// at that point. `ErrorBlock` is used as a sentinel for unrecognized archetype
|
||||||
|
/// names in map files — it should never appear in a valid board.
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum Archetype {
|
||||||
|
/// An open cell; the player and other entities can pass through it.
|
||||||
|
Empty,
|
||||||
|
/// A solid wall; impassable and opaque.
|
||||||
|
Wall,
|
||||||
|
/// A scripted object. Behavior defaults to impassable until script-driven.
|
||||||
|
Object,
|
||||||
|
/// Sentinel for map files that reference an unknown archetype name.
|
||||||
|
/// Renders as a yellow `?` on red to make the error visible in-game.
|
||||||
|
ErrorBlock,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Archetype {
|
||||||
|
/// Returns the default [`Behavior`] for this archetype.
|
||||||
|
///
|
||||||
|
/// For `Object`, this is a placeholder until Rhai scripts drive the behavior.
|
||||||
|
pub fn behavior(&self) -> Behavior {
|
||||||
|
match self {
|
||||||
|
Archetype::Empty => Behavior { passable: true, opaque: false },
|
||||||
|
Archetype::Wall => Behavior { passable: false, opaque: true },
|
||||||
|
Archetype::Object => Behavior { passable: false, opaque: false },
|
||||||
|
Archetype::ErrorBlock => Behavior { passable: false, opaque: true },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the canonical name used to reference this archetype in map files.
|
||||||
|
pub fn name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Archetype::Empty => "empty",
|
||||||
|
Archetype::Wall => "wall",
|
||||||
|
Archetype::Object => "object",
|
||||||
|
Archetype::ErrorBlock => "error_block",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the default glyph painted when the editor stamps this archetype.
|
||||||
|
///
|
||||||
|
/// This glyph is used only for new cells created in the editor; existing
|
||||||
|
/// cells retain their own per-cell glyph.
|
||||||
|
pub fn default_glyph(&self) -> Glyph {
|
||||||
|
match self {
|
||||||
|
Archetype::Empty => Glyph { ch: ' ', fg: Color32::BLACK, bg: Color32::BLACK },
|
||||||
|
Archetype::Wall => Glyph { ch: '#', fg: Color32::from_rgb(0x80, 0x80, 0x80), bg: Color32::from_rgb(0x60, 0x60, 0x60) },
|
||||||
|
Archetype::Object => Glyph { ch: '?', fg: Color32::YELLOW, bg: Color32::BLACK },
|
||||||
|
// Visually distinct so malformed map files are immediately obvious.
|
||||||
|
Archetype::ErrorBlock => Glyph { ch: '?', fg: Color32::YELLOW, bg: Color32::RED },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for Archetype {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
/// Parses an archetype by its map-file name.
|
||||||
|
///
|
||||||
|
/// Returns an error for unrecognized names; the caller should substitute
|
||||||
|
/// [`Archetype::ErrorBlock`] and log the error so the problem is visible.
|
||||||
|
fn try_from(name: &str) -> Result<Self, Self::Error> {
|
||||||
|
match name {
|
||||||
|
"empty" => Ok(Archetype::Empty),
|
||||||
|
"wall" => Ok(Archetype::Wall),
|
||||||
|
"object" => Ok(Archetype::Object),
|
||||||
|
_ => Err(format!("unknown archetype: {name}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The archetypes available for placement in the editor, in display order.
|
||||||
|
///
|
||||||
|
/// `ErrorBlock` is excluded — it is a sentinel for load errors, not a valid
|
||||||
|
/// editing choice.
|
||||||
|
pub const ALL_ARCHETYPES: &[Archetype] = &[Archetype::Empty, Archetype::Wall, Archetype::Object];
|
||||||
|
|
||||||
/// A scripted object placed on the board, loaded from a map file.
|
/// A scripted object placed on the board, loaded from a map file.
|
||||||
///
|
///
|
||||||
/// `ObjectDef` represents a tile that has Rhai script attached to it.
|
/// `ObjectDef` represents a tile that has Rhai script attached to it.
|
||||||
@@ -108,32 +203,26 @@ pub struct Player {
|
|||||||
/// "board" in ZZT. It contains everything needed to represent and run one
|
/// "board" in ZZT. It contains everything needed to represent and run one
|
||||||
/// self-contained area of the game world:
|
/// self-contained area of the game world:
|
||||||
///
|
///
|
||||||
/// - A grid of cells, each with a visual ([`Glyph`]) and a behavior index
|
/// - A grid of cells, each with a visual representation ([`Glyph`]) and an [`Archetype`]
|
||||||
/// - A palette of [`Element`] behavior definitions shared across cells
|
|
||||||
/// - The current player position
|
/// - The current player position
|
||||||
/// - Scripted objects and portals (loaded but not yet active)
|
/// - Scripted objects and portals (loaded but not yet active)
|
||||||
///
|
///
|
||||||
/// ## Cell storage
|
/// ## Cell storage
|
||||||
///
|
///
|
||||||
/// Cells are stored as `(Glyph, usize)` tuples in a row-major `Vec`. The
|
/// Cells are stored as `(Glyph, Archetype)` tuples in a row-major `Vec`.
|
||||||
/// `usize` is an index into [`Board::elements`]. It is an anonymous tuple
|
/// Each cell directly owns its visual and behavioral class — there is no
|
||||||
/// field intentionally — the index has no meaning outside the context of
|
/// separate element palette or index indirection. Access cells with
|
||||||
/// a specific `Board`'s element palette, so it cannot be accidentally
|
/// [`Board::get`] and [`Board::get_mut`] using `(x, y)` coordinates.
|
||||||
/// detached and misused.
|
/// Use [`Board::is_passable`] for collision checks.
|
||||||
///
|
|
||||||
/// Access cells with [`Board::get`] and [`Board::get_mut`] using `(x, y)`
|
|
||||||
/// coordinates. Use [`Board::is_passable`] for collision checks.
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct Board {
|
pub struct Board {
|
||||||
/// Width of the board in cells.
|
/// Width of the board in cells.
|
||||||
pub width: usize,
|
pub width: usize,
|
||||||
/// Height of the board in cells.
|
/// Height of the board in cells.
|
||||||
pub height: usize,
|
pub height: usize,
|
||||||
/// Palette of element behavior definitions. Cells reference these by index.
|
/// Row-major grid of `(Glyph, Archetype)` pairs. Use [`Board::get`] to
|
||||||
pub elements: Vec<Element>,
|
/// access by `(x, y)` coordinates.
|
||||||
/// Row-major grid of `(Glyph, element_index)` pairs. Use [`Board::get`]
|
pub(crate) cells: Vec<(Glyph, Archetype)>,
|
||||||
/// to access by coordinates.
|
|
||||||
pub(crate) cells: Vec<(Glyph, usize)>,
|
|
||||||
/// Current player position. See [`Player`] for caveats about its future.
|
/// Current player position. See [`Player`] for caveats about its future.
|
||||||
pub player: Player,
|
pub player: Player,
|
||||||
/// Scripted objects on this board. Parsed from the map file; not yet active.
|
/// Scripted objects on this board. Parsed from the map file; not yet active.
|
||||||
@@ -143,22 +232,11 @@ pub struct Board {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Board {
|
impl Board {
|
||||||
/// Adds an element to this board's palette and returns its index.
|
|
||||||
///
|
|
||||||
/// The returned index can be used as the second field of a cell tuple.
|
|
||||||
/// Indices are stable for the lifetime of the board.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn add_element(&mut self, element: Element) -> usize {
|
|
||||||
let idx = self.elements.len();
|
|
||||||
self.elements.push(element);
|
|
||||||
idx
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a reference to the cell at `(x, y)`.
|
/// Returns a reference to the cell at `(x, y)`.
|
||||||
///
|
///
|
||||||
/// The cell is a `(Glyph, usize)` tuple where the `usize` is an index
|
/// The cell is a `(Glyph, Archetype)` tuple. Panics if `x` or `y` are
|
||||||
/// into [`Board::elements`]. Panics if `x` or `y` are out of bounds.
|
/// out of bounds.
|
||||||
pub fn get(&self, x: usize, y: usize) -> &(Glyph, usize) {
|
pub fn get(&self, x: usize, y: usize) -> &(Glyph, Archetype) {
|
||||||
&self.cells[y * self.width + x]
|
&self.cells[y * self.width + x]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,16 +244,18 @@ impl Board {
|
|||||||
///
|
///
|
||||||
/// Panics if `x` or `y` are out of bounds.
|
/// Panics if `x` or `y` are out of bounds.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn get_mut(&mut self, x: usize, y: usize) -> &mut (Glyph, usize) {
|
pub fn get_mut(&mut self, x: usize, y: usize) -> &mut (Glyph, Archetype) {
|
||||||
&mut self.cells[y * self.width + x]
|
&mut self.cells[y * self.width + x]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if the element at `(x, y)` is passable.
|
/// Returns `true` if the archetype at `(x, y)` allows entities to pass through.
|
||||||
///
|
///
|
||||||
/// Panics if `x` or `y` are out of bounds.
|
/// Panics if `x` or `y` are out of bounds.
|
||||||
pub fn is_passable(&self, x: usize, y: usize) -> bool {
|
pub fn is_passable(&self, x: usize, y: usize) -> bool {
|
||||||
let (_, elem_idx) = self.get(x, y);
|
let (_, arch) = self.get(x, y);
|
||||||
self.elements[*elem_idx].passable
|
// Derive passability from the archetype's behavior.
|
||||||
|
// Object cells will need special handling here once scripts are wired.
|
||||||
|
arch.behavior().passable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +280,7 @@ impl GameState {
|
|||||||
|
|
||||||
/// Attempts to move the player by `(dx, dy)` cells.
|
/// Attempts to move the player by `(dx, dy)` cells.
|
||||||
///
|
///
|
||||||
/// The move is ignored if the target cell is out of bounds or its element
|
/// The move is ignored if the target cell is out of bounds or its behavior
|
||||||
/// is not passable. No-ops silently (the caller does not need to check).
|
/// is not passable. No-ops silently (the caller does not need to check).
|
||||||
pub fn try_move(&mut self, dx: i32, dy: i32) {
|
pub fn try_move(&mut self, dx: i32, dy: i32) {
|
||||||
let nx = self.board.player.x + dx;
|
let nx = self.board.player.x + dx;
|
||||||
|
|||||||
+3
-3
@@ -110,9 +110,9 @@ impl eframe::App for App {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Force a repaint every frame so input is processed continuously.
|
// egui repaints automatically on input events, so no explicit
|
||||||
// Remove this if the game becomes purely event-driven.
|
// request_repaint() is needed while the game is purely event-driven.
|
||||||
ctx.request_repaint();
|
// Add it back (or call it from game logic) when continuous animation is needed.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+34
-29
@@ -1,7 +1,7 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use eframe::egui::Color32;
|
use eframe::egui::Color32;
|
||||||
use crate::game::{Board, Element, Glyph, ObjectDef, Player, PortalDef};
|
use crate::game::{Archetype, Board, Glyph, ObjectDef, Player, PortalDef};
|
||||||
|
|
||||||
/// The top-level deserialization type for a `.toml` map file.
|
/// The top-level deserialization type for a `.toml` map file.
|
||||||
///
|
///
|
||||||
@@ -15,7 +15,7 @@ pub struct MapFile {
|
|||||||
/// The `[map]` section: name, dimensions, player start position.
|
/// The `[map]` section: name, dimensions, player start position.
|
||||||
pub map: MapHeader,
|
pub map: MapHeader,
|
||||||
/// The `[palette]` section: maps single-character keys to tile definitions.
|
/// The `[palette]` section: maps single-character keys to tile definitions.
|
||||||
/// Each entry defines both the visual ([`Glyph`]) and behavior ([`Element`])
|
/// Each entry defines both the visual ([`Glyph`]) and the [`Archetype`]
|
||||||
/// for all cells that use that character in the grid.
|
/// for all cells that use that character in the grid.
|
||||||
pub palette: HashMap<String, PaletteEntry>,
|
pub palette: HashMap<String, PaletteEntry>,
|
||||||
/// The `[grid]` section: the multi-line string that defines cell layout.
|
/// The `[grid]` section: the multi-line string that defines cell layout.
|
||||||
@@ -51,8 +51,8 @@ pub struct MapHeader {
|
|||||||
///
|
///
|
||||||
/// Each palette entry is keyed by a single character (e.g. `"#"` or `" "`).
|
/// Each palette entry is keyed by a single character (e.g. `"#"` or `" "`).
|
||||||
/// That character is used in the `[grid]` content string to place tiles.
|
/// That character is used in the `[grid]` content string to place tiles.
|
||||||
/// The entry defines both how the tile looks (`ch`, `fg`, `bg`) and how it
|
/// The entry defines both how the tile looks (`ch`, `fg`, `bg`) and which
|
||||||
/// behaves (`passable`).
|
/// [`Archetype`] it uses for behavior.
|
||||||
///
|
///
|
||||||
/// Note that `ch` (the display character) does not have to match the palette
|
/// Note that `ch` (the display character) does not have to match the palette
|
||||||
/// key. The key is just a label for the grid; `ch` is what actually gets
|
/// key. The key is just a label for the grid; `ch` is what actually gets
|
||||||
@@ -60,8 +60,10 @@ pub struct MapHeader {
|
|||||||
/// wall tile that displays as `#`.
|
/// wall tile that displays as `#`.
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct PaletteEntry {
|
pub struct PaletteEntry {
|
||||||
/// Whether entities can walk onto this tile.
|
/// The archetype name for this tile, e.g. `"wall"` or `"empty"`.
|
||||||
pub passable: bool,
|
/// Parsed via [`Archetype::try_from`]; unknown names produce an
|
||||||
|
/// [`Archetype::ErrorBlock`] cell and a logged warning.
|
||||||
|
pub archetype: String,
|
||||||
/// The character displayed in this cell.
|
/// The character displayed in this cell.
|
||||||
pub ch: char,
|
pub ch: char,
|
||||||
/// Foreground color as an `"#RRGGBB"` hex string.
|
/// Foreground color as an `"#RRGGBB"` hex string.
|
||||||
@@ -100,42 +102,46 @@ fn parse_color(hex: &str) -> Color32 {
|
|||||||
///
|
///
|
||||||
/// The conversion proceeds in two passes:
|
/// The conversion proceeds in two passes:
|
||||||
///
|
///
|
||||||
/// 1. **Palette pass** — each palette entry becomes an [`Element`] (pushed
|
/// 1. **Palette pass** — each entry is parsed into a `(Glyph, Archetype)` pair
|
||||||
/// into `elements`) and a [`Glyph`] (stored in a temporary `HashMap`
|
/// and stored in a temporary `HashMap` keyed by the palette character.
|
||||||
/// keyed by the palette character). The element index is recorded
|
/// Unknown archetype names produce an [`Archetype::ErrorBlock`] and a
|
||||||
/// alongside the glyph so cells can reference it.
|
/// logged warning so malformed maps are visible in-game.
|
||||||
///
|
///
|
||||||
/// 2. **Grid pass** — `grid.content` is split into lines; each character
|
/// 2. **Grid pass** — `grid.content` is split into lines; each character
|
||||||
/// is looked up in the palette map. Unknown characters are silently
|
/// is looked up in the palette map and pushed directly into `cells`.
|
||||||
/// skipped (they produce no cell), so a malformed grid will result in
|
/// Unknown characters are silently skipped, so a malformed grid will
|
||||||
/// a `cells` vec shorter than `width * height`. No explicit validation
|
/// result in a `cells` vec shorter than `width * height`.
|
||||||
/// is done here yet.
|
|
||||||
impl From<MapFile> for Board {
|
impl From<MapFile> for Board {
|
||||||
fn from(mf: MapFile) -> Self {
|
fn from(mf: MapFile) -> Self {
|
||||||
let w = mf.map.width;
|
let w = mf.map.width;
|
||||||
let h = mf.map.height;
|
let h = mf.map.height;
|
||||||
|
|
||||||
// Pass 1: build the element palette and a char→(Glyph, idx) lookup.
|
// Pass 1: build a char → (Glyph, Archetype) lookup from the palette.
|
||||||
let mut elements: Vec<Element> = Vec::new();
|
let mut palette: HashMap<char, (Glyph, Archetype)> = HashMap::new();
|
||||||
let mut palette: HashMap<char, (Glyph, usize)> = HashMap::new();
|
|
||||||
|
|
||||||
for (key, entry) in &mf.palette {
|
for (key, entry) in &mf.palette {
|
||||||
let key_char = key.chars().next().unwrap_or(' ');
|
let key_char = key.chars().next().unwrap_or(' ');
|
||||||
let idx = elements.len();
|
// Unknown archetype names fall back to ErrorBlock so the error is
|
||||||
elements.push(Element { passable: entry.passable });
|
// visible in-game rather than silently producing empty tiles.
|
||||||
palette.insert(key_char, (Glyph {
|
match Archetype::try_from(entry.archetype.as_str()) {
|
||||||
ch: entry.ch,
|
Ok(archetype) => {
|
||||||
fg: parse_color(&entry.fg),
|
let glyph = Glyph { ch: entry.ch, fg: parse_color(&entry.fg), bg: parse_color(&entry.bg) };
|
||||||
bg: parse_color(&entry.bg),
|
palette.insert(key_char, (glyph, archetype));
|
||||||
}, idx));
|
}
|
||||||
|
Err(e) => {
|
||||||
|
// Be sure to log and ignore the default glyph for an unknown archetype
|
||||||
|
eprintln!("{e}");
|
||||||
|
palette.insert(key_char, (Archetype::ErrorBlock.default_glyph(), Archetype::ErrorBlock));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass 2: walk the grid string and build cells.
|
// Pass 2: walk the grid string and build cells.
|
||||||
let mut cells: Vec<(Glyph, usize)> = Vec::with_capacity(w * h);
|
let mut cells: Vec<(Glyph, Archetype)> = Vec::with_capacity(w * h);
|
||||||
for line in mf.grid.content.lines() {
|
for line in mf.grid.content.lines() {
|
||||||
for ch in line.chars() {
|
for ch in line.chars() {
|
||||||
if let Some(&(glyph, idx)) = palette.get(&ch) {
|
if let Some(&cell) = palette.get(&ch) {
|
||||||
cells.push((glyph, idx));
|
cells.push(cell);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -143,7 +149,6 @@ impl From<MapFile> for Board {
|
|||||||
Board {
|
Board {
|
||||||
width: w,
|
width: w,
|
||||||
height: h,
|
height: h,
|
||||||
elements,
|
|
||||||
cells,
|
cells,
|
||||||
player: Player {
|
player: Player {
|
||||||
x: mf.map.player_start[0],
|
x: mf.map.player_start[0],
|
||||||
@@ -167,4 +172,4 @@ pub fn load(path: &str) -> Result<Board, Box<dyn std::error::Error>> {
|
|||||||
let content = std::fs::read_to_string(path)?;
|
let content = std::fs::read_to_string(path)?;
|
||||||
let map_file: MapFile = toml::from_str(&content)?;
|
let map_file: MapFile = toml::from_str(&content)?;
|
||||||
Ok(Board::from(map_file))
|
Ok(Board::from(map_file))
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user