diff options
Diffstat (limited to 'src/scenes/forge.rs')
| -rw-r--r-- | src/scenes/forge.rs | 154 |
1 files changed, 154 insertions, 0 deletions
diff --git a/src/scenes/forge.rs b/src/scenes/forge.rs new file mode 100644 index 0000000..f856c8f --- /dev/null +++ b/src/scenes/forge.rs @@ -0,0 +1,154 @@ +//! The name forge: browse what the name generator makes for each culture. +//! +//! Each forging is `derive(universe, "forge", n)`, so stepping back with ← +//! regenerates exactly what you saw before. + +use ratatui::Frame; +use ratatui::crossterm::event::{KeyCode, KeyEvent}; +use ratatui::layout::{Constraint, Layout, Rect}; +use ratatui::style::{Color, Style, Stylize}; +use ratatui::text::{Line, Span}; +use ratatui::widgets::{Block, Paragraph, Wrap}; + +use super::{Scene, Transition}; +use crate::app::Ctx; +use crate::names::{self, Artifact, Culture}; +use crate::seed; + +const PER_LIST: usize = 4; +const ARTIFACTS: usize = 5; + +struct Column { + culture: Culture, + people: Vec<String>, + places: Vec<String>, +} + +pub struct Forge { + forging: u64, + columns: Vec<Column>, + artifacts: Vec<Artifact>, +} + +impl Forge { + pub fn new(ctx: &Ctx) -> Self { + let mut forge = Forge { forging: 0, columns: Vec::new(), artifacts: Vec::new() }; + forge.forge(ctx.seed); + forge + } + + fn forge(&mut self, universe: u64) { + let mut rng = seed::rng(seed::derive(universe, "forge", self.forging)); + self.columns = Culture::ALL + .iter() + .map(|&culture| Column { + culture, + people: (0..PER_LIST).map(|_| culture.person(&mut rng)).collect(), + places: (0..PER_LIST).map(|_| culture.place(&mut rng)).collect(), + }) + .collect(); + self.artifacts = (0..ARTIFACTS).map(|_| names::artifact(&mut rng)).collect(); + } +} + +fn colour(culture: Culture) -> Color { + match culture { + Culture::Qaari => Color::Yellow, + Culture::Cant => Color::Red, + Culture::Houses => Color::Magenta, + Culture::Drift => Color::Cyan, + } +} + +fn render_column(frame: &mut Frame, column: &Column, area: Rect) { + let tint = colour(column.culture); + let heading = |text: &'static str| Line::from(text).fg(tint).dim(); + let mut lines = vec![ + Line::from(column.culture.blurb()).dark_gray().italic(), + Line::default(), + heading("PEOPLE"), + ]; + lines.extend(column.people.iter().map(|n| Line::from(n.as_str()))); + lines.push(Line::default()); + lines.push(heading("PLACES")); + lines.extend(column.places.iter().map(|n| Line::from(n.as_str()))); + + let block = Block::bordered() + .border_style(Style::new().fg(tint).dim()) + .title(Line::from(format!(" {} ", column.culture.name())).fg(tint).bold()); + frame.render_widget(Paragraph::new(lines).wrap(Wrap { trim: true }).block(block), area); +} + +impl Scene for Forge { + fn key(&mut self, key: KeyEvent, ctx: &mut Ctx) -> Transition { + match key.code { + KeyCode::Char(' ') | KeyCode::Right | KeyCode::Enter => { + self.forging += 1; + self.forge(ctx.seed); + } + KeyCode::Left | KeyCode::Backspace if self.forging > 0 => { + self.forging -= 1; + self.forge(ctx.seed); + } + KeyCode::Esc => return Transition::Pop, + KeyCode::Char(c) if c.eq_ignore_ascii_case(&'q') => return Transition::Pop, + _ => {} + } + Transition::None + } + + fn render(&self, frame: &mut Frame, ctx: &Ctx) { + let [header, cultures, artifacts, footer] = Layout::vertical([ + Constraint::Length(2), + Constraint::Fill(1), + Constraint::Length(ARTIFACTS as u16 + 2), + Constraint::Length(1), + ]) + .areas(frame.area()); + + frame.render_widget( + Paragraph::new(Line::from(vec![ + Span::from(" THE NAME FORGE").bold(), + Span::from(format!(" forging {} of universe {}", self.forging, ctx.seed)) + .dark_gray(), + ])), + header, + ); + + // Four across on wide terminals, two by two otherwise. + let cells: Vec<Rect> = if cultures.width >= 120 { + Layout::horizontal([Constraint::Fill(1); 4]).split(cultures).to_vec() + } else { + Layout::vertical([Constraint::Fill(1); 2]) + .split(cultures) + .iter() + .flat_map(|row| Layout::horizontal([Constraint::Fill(1); 2]).split(*row).to_vec()) + .collect() + }; + for (column, cell) in self.columns.iter().zip(cells) { + render_column(frame, column, cell); + } + + let width = self.artifacts.iter().map(|a| a.unidentified.len()).max().unwrap_or(0); + let lines: Vec<Line> = self + .artifacts + .iter() + .map(|a| { + Line::from(vec![ + Span::from(format!("{:<width$}", a.unidentified)).italic().gray(), + Span::from(" → ").dark_gray(), + Span::from(a.true_name.as_str()).light_yellow(), + ]) + }) + .collect(); + let block = Block::bordered() + .border_style(Style::new().dark_gray()) + .title(Line::from(" ARTIFACTS: as found → once identified ").bold()); + frame.render_widget(Paragraph::new(lines).block(block), artifacts); + + frame.render_widget( + Paragraph::new(" [space/→] forge again [←] previous [esc] back").dark_gray(), + footer, + ); + } +} |