diff options
| author | skylar <me@casper> | 2026-11-10 06:50:08 -0800 |
|---|---|---|
| committer | skylar <me@casper> | 2026-11-10 06:50:08 -0800 |
| commit | d36c54d996f3c14ae811312dbbe621e12a39edde (patch) | |
| tree | 1807134323479ac90bf69d28e410a7f70cb235a1 /src/scenes/mod.rs | |
Orrery: game loop, name generator and derelict dungeons
An ASCII sci-fi-fantasy game for the terminal, built on ratatui.
- Game loop with a fixed 30 Hz tick and a stack of scenes; the kitty
keyboard protocol is turned on where the terminal supports it, so later
real-time scenes can tell when a key is held.
- Stable hierarchical seeds: the whole universe derives from one u64,
with tests pinning the exact outputs so old seeds keep working.
- Name generator with four cultures, each with its own syllables and
templates, plus unidentified/true names for artifacts.
- Title screen with an animated orrery, and a name forge for browsing
generated names.
- Derelict ships to board: ship-shaped maps with an engine room, spine
corridor, side rooms and bridge; symmetric shadowcasting sight;
monsters that wake on sight and hunt; loot carried out through the
airlock goes into the hold.
- `orrery names` and `orrery ship` print the generators' output without
the TUI, for tuning.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'src/scenes/mod.rs')
| -rw-r--r-- | src/scenes/mod.rs | 74 |
1 files changed, 74 insertions, 0 deletions
diff --git a/src/scenes/mod.rs b/src/scenes/mod.rs new file mode 100644 index 0000000..60db647 --- /dev/null +++ b/src/scenes/mod.rs @@ -0,0 +1,74 @@ +//! Scenes: the title screen, the name forge, and derelicts to board; +//! flight and ports later. + +pub mod derelict; +pub mod forge; +pub mod title; + +use ratatui::Frame; +use ratatui::crossterm::event::KeyEvent; + +use crate::app::Ctx; + +/// What a scene wants to happen after it handles a key or a tick. +pub enum Transition { + None, + /// Open a scene on top of this one. Popping it comes back here. + Push(Box<dyn Scene>), + /// Close this scene and return to the one beneath it. + Pop, + Quit, +} + +/// One screen of the game. The game keeps a stack of them; only the top one +/// gets keys and ticks and is drawn. +pub trait Scene { + /// A key was pressed, or auto-repeated while held. + fn key(&mut self, key: KeyEvent, ctx: &mut Ctx) -> Transition; + + /// One fixed step of game time; there are `app::TICKS_PER_SECOND` of + /// these a second, whatever the frame rate. Turn-based scenes can ignore it. + fn tick(&mut self, _ctx: &mut Ctx) -> Transition { + Transition::None + } + + fn render(&self, frame: &mut Frame, ctx: &Ctx); +} + +#[cfg(test)] +mod tests { + use ratatui::Terminal; + use ratatui::backend::TestBackend; + use ratatui::crossterm::event::{KeyCode, KeyModifiers}; + + use super::*; + use crate::term::KeyboardCaps; + + pub fn test_ctx() -> Ctx { + Ctx::new(1, KeyboardCaps { key_release: false }) + } + + /// Draws every scene at several sizes, down to absurdly small ones, to + /// catch layout arithmetic that panics. + #[test] + fn scenes_render_at_any_size() { + let mut ctx = test_ctx(); + let mut abandoning = derelict::Derelict::new(2); + abandoning.key(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE), &mut ctx); + let scenes: [Box<dyn Scene>; 4] = [ + Box::new(title::Title::new(&ctx)), + Box::new(forge::Forge::new(&ctx)), + Box::new(derelict::Derelict::new(1)), + Box::new(abandoning), + ]; + for (width, height) in [(160, 50), (80, 24), (40, 12), (5, 3), (1, 1)] { + let mut terminal = Terminal::new(TestBackend::new(width, height)).unwrap(); + for scene in &scenes { + for ticks in [0, 12_345] { + ctx.ticks = ticks; + terminal.draw(|frame| scene.render(frame, &ctx)).unwrap(); + } + } + } + } +} |