summaryrefslogtreecommitdiff
path: root/src/scenes/mod.rs
diff options
context:
space:
mode:
authorskylar <me@casper>2026-11-10 06:50:08 -0800
committerskylar <me@casper>2026-11-10 06:50:08 -0800
commitd36c54d996f3c14ae811312dbbe621e12a39edde (patch)
tree1807134323479ac90bf69d28e410a7f70cb235a1 /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.rs74
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();
+ }
+ }
+ }
+ }
+}