diff --git a/kiln-core/src/game.rs b/kiln-core/src/game.rs index 7cec90c..8d508ee 100644 --- a/kiln-core/src/game.rs +++ b/kiln-core/src/game.rs @@ -4,12 +4,56 @@ use crate::log::LogLine; use crate::script::ScriptHost; use crate::utils::{Direction, ObjectId, Player, ScriptArg}; use crate::world::World; +use color::Rgba8; use std::cell::{Ref, RefMut}; use std::time::Duration; /// How long a `say()` speech bubble stays on screen, in seconds. pub const SAY_DURATION: f64 = 3.0; +/// The player's key inventory: one boolean per key color. +/// +/// Each field is `true` when the player holds a key of that color. Use +/// [`colors`](Keyring::colors) to iterate all eight in display order, each +/// paired with its [`Rgba8`] color for rendering. +#[derive(Copy, Clone, Default)] +pub struct Keyring { + /// Blue key. + pub blue: bool, + /// Green key. + pub green: bool, + /// Cyan key. + pub cyan: bool, + /// Red key. + pub red: bool, + /// Purple key. + pub purple: bool, + /// Orange key (custom color, not in the standard EGA palette). + pub orange: bool, + /// Yellow key. + pub yellow: bool, + /// White key. + pub white: bool, +} + +impl Keyring { + /// Returns all eight keys in display order, each paired with its color. + /// + /// Order: blue, green, cyan, red, purple, orange, yellow, white. + pub fn colors(&self) -> [(bool, Rgba8); 8] { + [ + (self.blue, Rgba8 { r: 0x00, g: 0x00, b: 0xAA, a: 255 }), + (self.green, Rgba8 { r: 0x00, g: 0xAA, b: 0x00, a: 255 }), + (self.cyan, Rgba8 { r: 0x00, g: 0xAA, b: 0xAA, a: 255 }), + (self.red, Rgba8 { r: 0xAA, g: 0x00, b: 0x00, a: 255 }), + (self.purple, Rgba8 { r: 0xAA, g: 0x00, b: 0xAA, a: 255 }), + (self.orange, Rgba8 { r: 0xFF, g: 0x88, b: 0x00, a: 255 }), + (self.yellow, Rgba8 { r: 0xFF, g: 0xFF, b: 0x55, a: 255 }), + (self.white, Rgba8 { r: 0xFF, g: 0xFF, b: 0xFF, a: 255 }), + ] + } +} + // Re-export ScrollLine so kiln-tui can pattern-match scroll content without // accessing the private `action` module directly. pub use crate::action::ScrollLine; @@ -74,6 +118,8 @@ pub struct GameState { /// [`player_health`](GameState::player_health); starts at `0` and is /// display-only for now. pub player_gems: u32, + /// The player's key inventory. Game-global; persists across board transitions. + pub player_keys: Keyring, } impl GameState { @@ -102,6 +148,8 @@ impl GameState { board_transition: None, player_health: 5, player_gems: 0, + // Test starting keys: red, green, orange, white. + player_keys: Keyring { red: true, green: true, orange: true, white: true, ..Default::default() }, }; state.drain_errors(); state diff --git a/kiln-tui/src/editor.rs b/kiln-tui/src/editor.rs index 731c075..39367f9 100644 --- a/kiln-tui/src/editor.rs +++ b/kiln-tui/src/editor.rs @@ -100,6 +100,10 @@ impl EditorState { /// Builds an editor session for `world`, starting on its designated start board. pub(crate) fn new(world: World) -> Self { let board_name = world.start.clone(); + let cursor = { + let b = world.boards[&board_name].borrow(); + (b.width as i32 / 2, b.height as i32 / 2) + }; Self { world, board_name, @@ -107,7 +111,7 @@ impl EditorState { dialog: None, code_editor: None, glyph_dialog: None, - cursor: (0, 0), + cursor, current_archetype: Archetype::Wall, current_glyph: Archetype::Wall.default_glyph(), draw_mode: false, diff --git a/kiln-tui/src/main.rs b/kiln-tui/src/main.rs index 2574e66..690396a 100644 --- a/kiln-tui/src/main.rs +++ b/kiln-tui/src/main.rs @@ -342,7 +342,7 @@ fn draw_play(frame: &mut Frame, game: &GameState, ui: &mut Ui) { .spacing(Spacing::Overlap(1)) .areas(frame.area()); frame.render_widget( - StatusSidebarWidget::new(game.player_health - 2, game.player_gems), + StatusSidebarWidget::new(game.player_health - 2, game.player_gems, game.player_keys), sidebar_area, ); rest diff --git a/kiln-tui/src/status.rs b/kiln-tui/src/status.rs index 0667974..b2d0845 100644 --- a/kiln-tui/src/status.rs +++ b/kiln-tui/src/status.rs @@ -5,7 +5,9 @@ //! presentational: it reads `health`/`gems` values handed in at construction and //! never mutates game state. +use std::collections::VecDeque; use crate::utils::rgba8_to_color; +use kiln_core::game::Keyring; use kiln_core::{Archetype, Builtin}; use kiln_core::cp437::tile_to_char; use ratatui::buffer::Buffer; @@ -24,19 +26,22 @@ const HEART_EMPTY: Color = Color::Rgb(90, 0, 0); /// A ratatui widget that renders the play-mode status sidebar. /// -/// Shows a `Health:` label above a row of [`MAX_HEARTS`] heart glyphs (the first -/// `health` filled bright red, the rest dark red) and a `Gems: ♦ N` line. +/// Shows a `Health:` label above a row of [`MAX_HEARTS`] heart glyphs, a +/// `Gems: ♦ N` line, and a `Keys:` row of 8 `♀` glyphs colored when held +/// and dark gray when absent. pub struct StatusSidebarWidget { /// The player's current health, clamped to [`MAX_HEARTS`] when drawn. health: u32, /// The number of gems collected, shown as a count. gems: u32, + /// The player's key inventory. + keys: Keyring, } impl StatusSidebarWidget { /// Builds a sidebar widget for the given player stats. - pub fn new(health: u32, gems: u32) -> Self { - Self { health, gems } + pub fn new(health: u32, gems: u32, keys: Keyring) -> Self { + Self { health, gems, keys } } } @@ -58,6 +63,22 @@ impl Widget for StatusSidebarWidget { let gem_char = tile_to_char(gem_glyph.tile).to_string(); let gem_style = Style::default().fg(rgba8_to_color(gem_glyph.fg)); + // Build the key row: 8 ♀ glyphs, colored when held, near-black when absent. + let mut key_spans: VecDeque<_> = self + .keys + .colors() + .iter() + .filter_map(|(held, color)| { + if *held { + let fg = rgba8_to_color(*color); + Some(Span::styled("♀", Style::default().fg(fg))) + } else { + None + } + }) + .collect(); + key_spans.push_front(Span::raw("Keys: ")); + let lines = vec![ Line::from("Health:"), Line::from(hearts), @@ -65,8 +86,10 @@ impl Widget for StatusSidebarWidget { Line::from(vec![ Span::raw("Gems: "), Span::styled(gem_char, gem_style), - Span::raw(format!(" {}", self.gems)), + Span::raw(format!("{}", self.gems)), ]), + Line::from(""), + Line::from_iter(key_spans.into_iter()), ]; // A bordered block; `merge_borders` joins its right edge with the board