summaryrefslogtreecommitdiff
path: root/src/scenes/forge.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/scenes/forge.rs')
-rw-r--r--src/scenes/forge.rs154
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,
+ );
+ }
+}