1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
use std::{
  collections::{HashMap, HashSet, VecDeque},
  fmt::Display,
};

#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct KnotName(String);

impl<T> From<T> for KnotName
where
  T: Into<String>,
{
  fn from(name: T) -> Self {
    KnotName(name.into())
  }
}

impl Display for KnotName {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    write!(f, "{}", self.0)
  }
}

pub struct Knot {
  pub text: String,
  pub choices: Vec<(String, KnotName)>,
}

impl Display for Knot {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    if !self.text.is_empty() {
      writeln!(f, "{}", self.text)?;
    }

    if !self.choices.is_empty() {
      self
        .choices
        .iter()
        .map(|(name, text)| writeln!(f, "+ [{}] -> {}", name, text))
        .collect::<Result<Vec<_>, _>>()?;
    } else {
      writeln!(f, "-> END")?;
    }
    Ok(())
  }
}

pub struct Story {
  pub start: KnotName,
  pub knots: HashMap<KnotName, Knot>,
}

impl Display for Story {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    let mut queue = VecDeque::new();
    let mut unreached_knots = self.knots.keys().collect::<HashSet<_>>();

    // Start with an immediate divert to the first knot
    writeln!(f, "-> {}", self.start)?;
    queue.push_back(&self.start);

    // Write the knots in the order they happen in the story
    while let Some(name) = queue.pop_front() {
      // Mark knot as reached.
      // If it has already been reached before, skip.
      if unreached_knots.remove(&name) {
        // Write the knot, add choices to the knot queue
        writeln!(f, "=== {} ===", name)?;
        if let Some(knot) = self.knots.get(&name) {
          writeln!(f, "{}", knot)?;
          for (_, choice) in &knot.choices {
            queue.push_back(choice);
          }
        } else {
          writeln!(f, "// knot not found")?;
        }
      }
    }

    // Write the knots not reachable from the first one, if any
    if !unreached_knots.is_empty() {
      writeln!(f, "// Some knots are unreachable:")?;
      for name in unreached_knots {
        writeln!(f, "=== {} ===", name)?;
        if let Some(knot) = self.knots.get(&name) {
          writeln!(f, "{}", knot)?;
        } else {
          writeln!(f, "// knot not found")?;
        }
      }
    }

    Ok(())
  }
}