epkard/
lib.rs

1#![deny(missing_docs)]
2#![deny(unsafe_code)]
3
4/*!
5Epkard is a generalized framework for creating branching narratives.
6
7[Examples](https://github.com/kaikalii/epkard/tree/master/examples)
8*/
9
10mod paragraphs;
11pub use paragraphs::*;
12mod frontend;
13pub use frontend::*;
14
15use std::{
16    collections::HashMap,
17    error::Error,
18    fmt::{self, Display, Formatter},
19    fs, io,
20    ops::{Deref, DerefMut},
21    path::Path,
22    sync::Arc,
23};
24
25use serde::{de::DeserializeOwned, Serialize};
26pub use serde_derive::{Deserialize, Serialize};
27
28/// An item that may be owned mutably borrowed
29enum MayBor<'a, T> {
30    Owned(T),
31    Borrowed(&'a mut T),
32}
33
34impl<'a, T> Deref for MayBor<'a, T> {
35    type Target = T;
36    fn deref(&self) -> &Self::Target {
37        use MayBor::*;
38        match self {
39            Owned(x) => x,
40            Borrowed(x) => x,
41        }
42    }
43}
44
45impl<'a, T> DerefMut for MayBor<'a, T> {
46    fn deref_mut(&mut self) -> &mut Self::Target {
47        use MayBor::*;
48        match self {
49            Owned(x) => x,
50            Borrowed(x) => x,
51        }
52    }
53}
54
55/// An error used by the [`Runtime`](struct.Runtime.html)
56#[derive(Debug)]
57pub enum RuntimeError {
58    /// An IO error
59    IO(io::Error),
60    /// A [`serde_yaml`](https://docs.rs/serde_yaml) error
61    Serialize(serde_yaml::Error),
62}
63
64impl From<io::Error> for RuntimeError {
65    fn from(e: io::Error) -> Self {
66        RuntimeError::IO(e)
67    }
68}
69
70impl From<serde_yaml::Error> for RuntimeError {
71    fn from(e: serde_yaml::Error) -> Self {
72        RuntimeError::Serialize(e)
73    }
74}
75
76impl Display for RuntimeError {
77    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
78        use RuntimeError::*;
79        match self {
80            IO(e) => write!(f, "{}", e),
81            Serialize(e) => write!(f, "{}", e),
82        }
83    }
84}
85
86impl Error for RuntimeError {}
87
88/// A `Result` type used by the [`Runtime`](struct.Runtime.html)
89pub type RuntimeResult<T> = Result<T, RuntimeError>;
90
91/// A type for non-continuous narrative flow.
92///
93/// These normally only need to be explicitly used as the return type
94/// for [`Runtime`](struct.Runtime.html) commands added via
95/// [`RunBuilder::command`](struct.RunBuilder.html#method.command).
96///
97/// This serves as the error type for [`ControlResult`](type.ControlResult.html).
98pub enum Interrupt<N> {
99    /// Exit the program
100    Exit,
101    /// Skip the rest of the current [`Node`](trait.Node.html)
102    ///
103    /// This is normally used after loading a save file.
104    Skip,
105    /// Continue the current [`Node`](trait.Node.html)
106    ///
107    /// This picks up where the node left off.
108    Continue,
109    /// Jump to a [`Node`](trait.Node.html)
110    Jump(N),
111}
112
113pub use Interrupt::*;
114
115/// A type for controlling narrative flow for non-node types
116pub type ControlResult<T, N> = Result<T, Interrupt<N>>;
117
118/// A type for controlling narrative flow of nodes
119pub type Control<N> = ControlResult<N, N>;
120
121/// Get a next [`ControlResult`](type.ControlResult.html) with the given [`Node`](trait.Node.html)
122pub fn next<T, N>(data: T) -> ControlResult<T, N> {
123    Ok(data)
124}
125
126/// Get an exit [`ControlResult`](type.ControlResult.html)
127pub fn exit<T, N>() -> ControlResult<T, N> {
128    Err(Exit)
129}
130
131/**
132A story node in a narrative.
133
134In a branching narrative, a node is a place where the
135story can split into different branches.
136
137While [`Node`](trait.Node.html)s themselves should be transient and not hold
138too much data, they have an associated state that can be
139mutated.
140*/
141pub trait Node: Clone {
142    /// The state that the node transforms
143    type State;
144    /// Handle the node to transforms the state and determine the next node
145    ///
146    /// The state can be access by [`deref`](struct.Runtime.html#impl-Deref)ing the [`Runtime`](struct.Runtime.html).
147    fn next<F>(self, rt: &mut Runtime<Self, F>) -> Control<Self>
148    where
149        F: Frontend;
150}
151
152/// Wraps the basic state with extra information and functionality
153pub struct Runtime<'a, N, F = CliFrontend>
154where
155    N: Node,
156    F: Frontend,
157{
158    curr_node: N,
159    state: MayBor<'a, N::State>,
160    frontend: MayBor<'a, F>,
161    commands: HashMap<String, Arc<Box<CommandFn<N, F>>>>,
162}
163
164impl<'a, N, F> Runtime<'a, N, F>
165where
166    N: Node,
167    F: Frontend,
168{
169    fn borrow_state_frontend<G>(&mut self, f: G)
170    where
171        G: FnOnce(&mut N::State, &mut F),
172    {
173        f(&mut *self.state, &mut self.frontend)
174    }
175    /// Push a [`Node`](trait.Node.html) onto the stack using its default
176    pub fn push<M>(&mut self) -> Interrupt<N>
177    where
178        M: Node<State = N::State> + Default,
179    {
180        self.borrow_state_frontend(|state, frontend| {
181            run(M::default(), state, frontend);
182        });
183        Continue
184    }
185    /// Push a [`Node`](trait.Node.html) onto the stack
186    pub fn push_node<M>(&mut self, node: M) -> Interrupt<N>
187    where
188        M: Node<State = N::State>,
189    {
190        self.borrow_state_frontend(|state, frontend| {
191            run(node, state, frontend);
192        });
193        Continue
194    }
195    /// Save the [`Runtime`](struct.Runtime.html) to the given path
196    pub fn save<P: AsRef<Path>>(&self, path: P) -> RuntimeResult<()>
197    where
198        N: Serialize,
199        N::State: Serialize,
200    {
201        fs::write(
202            path,
203            &serde_yaml::to_vec(&(&self.curr_node, self.state.deref()))?,
204        )?;
205        Ok(())
206    }
207    /// Load the [`Runtime`](struct.Runtime.html) from the given path
208    pub fn load<P: AsRef<Path>>(&mut self, path: P) -> RuntimeResult<()>
209    where
210        N: DeserializeOwned,
211        N::State: DeserializeOwned,
212    {
213        let (curr_node, state): (N, N::State) = serde_yaml::from_slice(&fs::read(path)?)?;
214        self.curr_node = curr_node;
215        self.state = MayBor::Owned(state);
216        Ok(())
217    }
218    #[must_use]
219    fn prompt_intercept(&mut self) -> ControlResult<String, N> {
220        loop {
221            let input = self.input();
222            match input.as_str().trim() {
223                "quit" => break exit(),
224                s => {
225                    if let Some(f) = self.commands.get(s).cloned() {
226                        match f(self) {
227                            Continue => {}
228                            e => break Err(e),
229                        }
230                    } else {
231                        break next(s.to_string());
232                    }
233                }
234            }
235        }
236    }
237    /// Prompt the user to enter text
238    #[must_use]
239    pub fn prompt<S: Display>(&mut self, prompt: S) -> ControlResult<String, N> {
240        loop {
241            self.println(&prompt);
242            let input = self.prompt_intercept()?;
243            if !input.is_empty() {
244                break next(input);
245            }
246        }
247    }
248    /// Wait for the user to continue
249    #[must_use]
250    pub fn wait(&mut self) -> ControlResult<(), N> {
251        self.prompt_intercept()?;
252        next(())
253    }
254    /// Prompt the user with multiple choices
255    #[must_use]
256    pub fn multiple_choice<M, C>(&mut self, message: M, choices: &[C]) -> ControlResult<usize, N>
257    where
258        M: Display,
259        C: Display,
260    {
261        loop {
262            self.println(&message);
263            for (i, choice) in choices.iter().enumerate() {
264                self.println(format!("{}) {}", i + 1, choice));
265            }
266            let resp = self.prompt_intercept()?;
267            match resp.parse::<usize>() {
268                Ok(num) if num > 0 && num <= choices.len() => break next(num - 1),
269                Err(_) => {
270                    if let Some(i) = choices
271                        .iter()
272                        .position(|c| c.to_string().to_lowercase() == resp.to_lowercase())
273                    {
274                        break next(i);
275                    } else {
276                        self.println("Invalid choice")
277                    }
278                }
279                _ => self.println("Invalid choice"),
280            }
281        }
282    }
283    /// Prompt the user with custom-named multiple choices
284    #[must_use]
285    pub fn multiple_choice_named<M, S, C, I>(
286        &mut self,
287        message: M,
288        choices: I,
289    ) -> ControlResult<C, N>
290    where
291        M: Display,
292        S: Display,
293        I: IntoIterator<Item = (S, C)>,
294    {
295        let mut choices: Vec<(S, C)> = choices.into_iter().collect();
296        loop {
297            self.println(&message);
298            for (i, (text, _)) in choices.iter().enumerate() {
299                self.println(format!("{}) {}", i + 1, text));
300            }
301            let resp = self.prompt_intercept()?;
302            match resp.parse::<usize>() {
303                Ok(num) if num > 0 && num <= choices.len() => {
304                    break next(choices.remove(num - 1).1)
305                }
306                _ => self.println("Invalid choice"),
307            }
308        }
309    }
310}
311
312impl<'a, N, F> Deref for Runtime<'a, N, F>
313where
314    N: Node,
315    F: Frontend,
316{
317    type Target = N::State;
318    fn deref(&self) -> &Self::Target {
319        &self.state
320    }
321}
322
323impl<'a, N, F> DerefMut for Runtime<'a, N, F>
324where
325    N: Node,
326    F: Frontend,
327{
328    fn deref_mut(&mut self) -> &mut Self::Target {
329        &mut self.state
330    }
331}
332
333impl<'a, N, F> AsRef<N::State> for Runtime<'a, N, F>
334where
335    N: Node,
336    F: Frontend,
337{
338    fn as_ref(&self) -> &N::State {
339        &self.state
340    }
341}
342
343impl<'a, N, F> AsMut<N::State> for Runtime<'a, N, F>
344where
345    N: Node,
346    F: Frontend,
347{
348    fn as_mut(&mut self) -> &mut N::State {
349        &mut self.state
350    }
351}
352
353/// Trait for listing command names
354pub trait CommandNames {
355    /// Get a command name vector
356    fn commands(&self) -> Vec<String>;
357}
358
359impl CommandNames for &str {
360    fn commands(&self) -> Vec<String> {
361        self.split_whitespace()
362            .flat_map(|s| s.split(','))
363            .flat_map(|s| s.split('|'))
364            .filter(|s| !s.is_empty())
365            .map(Into::into)
366            .collect()
367    }
368}
369
370impl CommandNames for String {
371    fn commands(&self) -> Vec<String> {
372        self.as_str().commands()
373    }
374}
375
376/// The signature of a command function
377type CommandFn<N, F> = dyn Fn(&mut Runtime<N, F>) -> Interrupt<N>;
378
379/// A builder that runs the narrative when it is dropped
380pub struct RunBuilder<'a, N, F = CliFrontend>
381where
382    N: Node,
383    F: Frontend,
384{
385    commands: HashMap<String, Arc<Box<CommandFn<N, F>>>>,
386    state: Option<MayBor<'a, N::State>>,
387    start_node: Option<N>,
388    frontend: Option<MayBor<'a, F>>,
389}
390
391impl<'a, N, F> RunBuilder<'a, N, F>
392where
393    N: Node,
394    F: Frontend,
395{
396    /// Add a global command to the [`Runtime`](struct.Runtime.html)
397    ///
398    /// Global commands can always be entered by the user.
399    ///
400    /// Some examples of global commands might be:
401    /// * save
402    /// * inventory / inv
403    /// * look around
404    pub fn command<C, G>(mut self, names: C, command: G) -> Self
405    where
406        C: CommandNames,
407        G: Fn(&mut Runtime<N, F>) -> Interrupt<N> + 'static,
408    {
409        let names = names.commands();
410        let command: Arc<Box<CommandFn<N, F>>> = Arc::new(Box::new(command));
411        for name in names {
412            self.commands.insert(name.to_string(), Arc::clone(&command));
413        }
414        self
415    }
416}
417
418impl<'a, N, F> Drop for RunBuilder<'a, N, F>
419where
420    N: Node,
421    F: Frontend,
422{
423    fn drop(&mut self) {
424        let mut runtime = Runtime {
425            state: self.state.take().expect("RunBuilder state is empty"),
426            curr_node: self
427                .start_node
428                .take()
429                .expect("RunBuilder start node is empty"),
430            commands: self.commands.drain().collect(),
431            frontend: self.frontend.take().expect("RunBuilder frontend is empty"),
432        };
433        loop {
434            match runtime.curr_node.clone().next(&mut runtime) {
435                Ok(node) | Err(Jump(node)) => runtime.curr_node = node,
436                Err(Exit) => break,
437                Err(Skip) | Err(Continue) => {}
438            }
439        }
440    }
441}
442
443/// Run the narrative starting at the given [`Node`](trait.Node.html) and state
444pub fn run<'a, N, F>(start: N, state: &'a mut N::State, frontend: &'a mut F) -> RunBuilder<'a, N, F>
445where
446    N: Node,
447    F: Frontend,
448{
449    RunBuilder {
450        commands: HashMap::new(),
451        state: Some(MayBor::Borrowed(state)),
452        start_node: Some(start),
453        frontend: Some(MayBor::Borrowed(frontend)),
454    }
455}
456
457/// Run the narrative starting at the default [`Node`](trait.Node.html) and state
458pub fn run_default<'a, N, F>() -> RunBuilder<'a, N, F>
459where
460    N: Node + Default,
461    N::State: Default,
462    F: Frontend + Default,
463{
464    RunBuilder {
465        commands: HashMap::new(),
466        state: Some(MayBor::Owned(N::State::default())),
467        start_node: Some(N::default()),
468        frontend: Some(MayBor::Owned(F::default())),
469    }
470}