This commit is contained in:
2026-06-23 21:51:31 -05:00
parent cbbe522fb1
commit cca56a6153
7 changed files with 171 additions and 52 deletions
+68 -33
View File
@@ -1,15 +1,16 @@
use crate::glyph::Glyph;
use crate::utils::{Behavior, Pushable};
use color::Rgba8;
use crate::game::KeyType;
/// Declares the set of script-backed archetype families.
///
/// Each entry specifies:
/// - A `Variant` name (becomes a [`Builtin`] enum variant).
/// - A `["name" => tile, …]` list: one map-file keyword per alias with the tile
/// index for the editor glyph. All aliases in a family share `behavior`, `fg`,
/// `bg`, and embedded Rhai `script`.
/// - `behavior`, `fg`, `bg`: per-family defaults.
/// - A `["name" => Glyph { … }, …]` list: one map-file keyword per alias with the
/// default [`Glyph`] for the editor. Per-alias glyphs allow aliases in the same
/// family to differ in color (e.g. the eight `Key` variants).
/// - `behavior`: shared across all aliases in the family.
/// - `script`: the embedded Rhai source; `include_str!` paths are relative to this
/// file, so `include_str!("scripts/pusher.rhai")` resolves to
/// `kiln-core/src/scripts/pusher.rhai`.
@@ -21,10 +22,8 @@ use color::Rgba8;
macro_rules! builtins {
(
$(
$variant:ident => [ $( $name:literal => $tile:literal ),+ $(,)? ] {
$variant:ident => [ $( $name:literal => $glyph:expr ),+ $(,)? ] {
behavior: $behavior:expr,
fg: $fg:expr,
bg: $bg:expr,
script: $script:expr $(,)?
}
),+ $(,)?
@@ -66,16 +65,14 @@ macro_rules! builtins {
}
}
/// Returns the glyph for `alias`: the family's fg/bg with the alias's tile.
/// Returns the default glyph for `alias`. Each alias owns its own glyph,
/// so aliases within a family can differ in color (e.g. colored keys).
/// Falls back to a transparent glyph for unrecognized aliases (shouldn't
/// happen in practice since aliases are all from the macro).
pub(crate) fn default_glyph_for(self, alias: &str) -> Glyph {
// The outer repetition brings $fg/$bg into scope; the inner one selects
// the tile for the specific alias. Both are statically resolved at
// compile time.
match alias {
$(
$( $name => Glyph { tile: $tile, fg: $fg, bg: $bg }, )+
$( $name => $glyph, )+
)+
_ => Glyph::transparent(),
}
@@ -91,38 +88,48 @@ macro_rules! builtins {
};
}
builtins! {
Gem => ["gem" => 4] {
behavior: Behavior { solid: true, opaque: false, pushable: Pushable::Any, grab: true },
fg: Rgba8 { r: 0x50, g: 0x50, b: 0xFF, a: 255 },
// Shorthand helpers used only within the builtins! invocation below.
// `g(tile, r, g, b)` builds a Glyph with the given tile and fg on black bg.
const fn g(tile: u32, r: u8, gr: u8, b: u8) -> Glyph {
Glyph {
tile,
fg: Rgba8 { r, g: gr, b, a: 255 },
bg: Rgba8 { r: 0, g: 0, b: 0, a: 255 },
}
}
builtins! {
Gem => ["gem" => g(4, 0x50, 0x50, 0xFF)] {
behavior: Behavior { solid: true, opaque: false, pushable: Pushable::Any, grab: true },
script: include_str!("scripts/gem.rhai"),
},
Pusher => ["pusher_north" => 30, "pusher_south" => 31, "pusher_east" => 16, "pusher_west" => 17] {
Pusher => [
"pusher_north" => g(30, 0xAA, 0xAA, 0xAA),
"pusher_south" => g(31, 0xAA, 0xAA, 0xAA),
"pusher_east" => g(16, 0xAA, 0xAA, 0xAA),
"pusher_west" => g(17, 0xAA, 0xAA, 0xAA),
] {
behavior: Behavior { solid: true, opaque: true, pushable: Pushable::No, grab: false },
fg: Rgba8 { r: 0xAA, g: 0xAA, b: 0xAA, a: 255 },
bg: Rgba8 { r: 0, g: 0, b: 0, a: 255 },
script: include_str!("scripts/pusher.rhai"),
},
Spinner => ["spinner_cw" => 47, "spinner_ccw" => 92] {
Spinner => [
"spinner_cw" => g(47, 0xAA, 0xAA, 0xAA),
"spinner_ccw" => g(92, 0xAA, 0xAA, 0xAA),
] {
behavior: Behavior { solid: true, opaque: true, pushable: Pushable::No, grab: false },
fg: Rgba8 { r: 0xAA, g: 0xAA, b: 0xAA, a: 255 },
bg: Rgba8 { r: 0, g: 0, b: 0, a: 255 },
script: include_str!("scripts/spinner.rhai"),
},
Key => [
"key_red" => 12,
"key_orange" => 12,
"key_yellow" => 12,
"key_green" => 12,
"key_blue" => 12,
"key_cyan" => 12,
"key_purple" => 12,
"key_white" => 12,
Key => [ // TODO these should refer to the key colors in Keyring
"key_blue" => KeyType::Blue.glyph(),
"key_green" => KeyType::Green.glyph(),
"key_cyan" => KeyType::Cyan.glyph(),
"key_red" => KeyType::Red.glyph(),
"key_purple" => KeyType::Purple.glyph(),
"key_orange" => KeyType::Orange.glyph(),
"key_yellow" => KeyType::Yellow.glyph(),
"key_white" => KeyType::White.glyph(),
] {
behavior: Behavior { solid: true, opaque: false, pushable: Pushable::Any, grab: true },
fg: Rgba8 { r: 0xAA, g: 0xAA, b: 0xAA, a: 255 },
bg: Rgba8 { r: 0, g: 0, b: 0, a: 255 },
script: include_str!("scripts/key.rhai"),
}
}
@@ -325,6 +332,9 @@ mod tests {
("pusher_west", 17),
("spinner_cw", 47),
("spinner_ccw", 92),
("key_red", 12),
("key_blue", 12),
("key_white", 12),
] {
let arch = Archetype::try_from(name)
.unwrap_or_else(|_| panic!("'{name}' should parse as a builtin"));
@@ -336,4 +346,29 @@ mod tests {
);
}
}
#[test]
fn key_aliases_have_distinct_fg_colors() {
use crate::game::KeyType;
// Each alias must match the corresponding KeyType glyph — single source of truth.
let cases = [
("key_blue", KeyType::Blue),
("key_green", KeyType::Green),
("key_cyan", KeyType::Cyan),
("key_red", KeyType::Red),
("key_purple", KeyType::Purple),
("key_orange", KeyType::Orange),
("key_yellow", KeyType::Yellow),
("key_white", KeyType::White),
];
for (name, key_type) in cases {
let arch = Archetype::try_from(name)
.unwrap_or_else(|_| panic!("'{name}' should parse"));
assert_eq!(
arch.default_glyph().fg,
key_type.glyph().fg,
"'{name}' fg doesn't match KeyType"
);
}
}
}
+32 -10
View File
@@ -11,6 +11,28 @@ use std::time::Duration;
/// How long a `say()` speech bubble stays on screen, in seconds.
pub const SAY_DURATION: f64 = 3.0;
#[derive(Copy, Clone, Debug)]
pub enum KeyType {
Red, Orange, Yellow, Green, Blue, Cyan, Purple, White
}
impl KeyType {
pub fn glyph(self) -> Glyph {
let fg = match self {
KeyType::Red => Rgba8 { r: 0xFF, g: 0x50, b: 0x50, a: 255 },
KeyType::Orange => Rgba8 { r: 0xFF, g: 0x88, b: 0x00, a: 255 },
KeyType::Yellow => Rgba8 { r: 0xFF, g: 0xFF, b: 0x50, a: 255 },
KeyType::Green => Rgba8 { r: 0x50, g: 0xFF, b: 0x50, a: 255 },
KeyType::Blue => Rgba8 { r: 0x50, g: 0x50, b: 0xFF, a: 255 },
KeyType::Cyan => Rgba8 { r: 0x00, g: 0xAA, b: 0xAA, a: 255 },
KeyType::Purple => Rgba8 { r: 0xAA, g: 0x00, b: 0xAA, a: 255 },
KeyType::White => Rgba8 { r: 0xFF, g: 0xFF, b: 0xFF, a: 255 }
};
Glyph { tile: 12, fg, bg: Rgba8 { r: 0, g: 0, b: 0, a: 255 } }
}
}
/// The player's key inventory: one boolean per key color.
///
/// Each field is `true` when the player holds a key of that color. Use
@@ -60,14 +82,14 @@ impl Keyring {
/// 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 }),
(self.blue, KeyType::Blue.glyph().fg),
(self.green, KeyType::Green.glyph().fg),
(self.cyan, KeyType::Cyan.glyph().fg),
(self.red, KeyType::Red.glyph().fg),
(self.purple, KeyType::Purple.glyph().fg),
(self.orange, KeyType::Orange.glyph().fg),
(self.yellow, KeyType::Yellow.glyph().fg),
(self.white, KeyType::White.glyph().fg),
]
}
}
@@ -75,6 +97,7 @@ impl Keyring {
// Re-export ScrollLine so kiln-tui can pattern-match scroll content without
// accessing the private `action` module directly.
pub use crate::action::ScrollLine;
use crate::glyph::Glyph;
/// An active scroll overlay opened by a scripted object via `scroll()`.
///
@@ -166,8 +189,7 @@ 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() },
player_keys: Keyring::default(),
};
state.drain_errors();
state