1use std::ops::Deref;
2
3use crate::engine::engine_error::{EngineError, EngineErrorType};
4use crate::story::Story;
5use crate::story::story_node::{RcStoryNode, StoryNode};
6
7pub mod engine_error;
8
9pub type EngineResult = Result<(String, RcStoryNode), EngineError>;
10
11pub struct Engine {
12 story: Option<Story>,
13}
14
15impl Engine {
16 pub fn new() -> Self {
17 Self { story: None }
18 }
19 pub fn story(&self) -> &Option<Story> {
20 &self.story
21 }
22 pub fn set_story(&mut self, story: Story) {
23 self.story = Some(story);
24 }
25
26 pub fn reset(&mut self) -> Result<(), EngineError> {
27 let story = self.story.as_mut()
28 .ok_or(EngineError::new(EngineErrorType::NoStory))?;
29 story.set_current(story.start().clone());
30 Ok(())
31 }
32
33 pub fn start(&self) -> EngineResult {
34 let story = self.story.as_ref()
35 .ok_or(EngineError::new(EngineErrorType::NoStory))?;
36 let start_node_key = story.start();
37 let start_node = story.story_nodes().get(start_node_key)
38 .ok_or(EngineError::new(EngineErrorType::StoryNodeDne))?;
39 Ok((start_node_key.clone(), start_node.clone()))
40 }
41
42 pub fn next(&mut self, choice: Option<usize>) -> EngineResult {
43 let story = self.story.as_mut()
44 .ok_or(EngineError::new(EngineErrorType::NoStory))?;
45 let current_node_key = match story.current() {
46 Some(current_node) => current_node,
47 _ => story.start()
48 };
49 let story_context = story.story_context();
50 let get_node_from_key = |node_key: String|
51 -> Result<RcStoryNode, EngineError> {
52 Ok(story.story_nodes().get(&node_key)
53 .ok_or(EngineError::new(EngineErrorType::StoryNodeDne))?.clone())
54 };
55
56 let node_dialogue = {
58 let mut search_stack = vec![current_node_key.clone()];
59 let mut result: Option<RcStoryNode> = None;
60 while result.is_none() {
61 let first_query = search_stack.first()
62 .ok_or(EngineError::new(EngineErrorType::CurrentNoDialogue))?;
63 let node = get_node_from_key(first_query.clone())?;
64 let ref_node = node.borrow();
65 match ref_node.deref() {
66 StoryNode::Dialogue(_) => result = Some(node.clone()),
67 StoryNode::Part(part) => {
68 for story_node in part.story_nodes().iter().rev() {
69 search_stack.insert(1, story_node.clone());
70 }
71 }
72 }
73 }
74 result.ok_or(EngineError::new(EngineErrorType::CurrentNoDialogue))?
75 };
76
77 let ref_node_dialogue = node_dialogue.borrow();
78
79 match ref_node_dialogue.deref() {
80 StoryNode::Dialogue(dialogue) => {
82 let quote = if dialogue.has_choices() {
84 let choice_index = choice
85 .ok_or(EngineError::new(EngineErrorType::NoChoiceArg))?;
86 dialogue.quotes().0.get(choice_index)
87 .ok_or(EngineError::new(EngineErrorType::ChoiceDne))?
88 } else {
89 dialogue.first_quote()
90 .ok_or(EngineError::new(EngineErrorType::NoQuotes))?
91 };
92
93 let next_closure = quote.story_link().next()
95 .ok_or(EngineError::new(EngineErrorType::NoNextClosure))?;
96
97 let next_node_key = next_closure(story_context);
99 let next_node = story.story_nodes().get(&next_node_key)
100 .ok_or(EngineError::new(EngineErrorType::StoryNodeDne))?.clone();
101 let result = (next_node_key.clone(), next_node.clone());
102
103 match quote.story_link().change_context() {
105 Some(change_context_closure) => {
106 change_context_closure(story.mut_story_context());
107 }
108 _ => ()
109 }
110
111 story.set_current(next_node_key);
112 Ok(result)
113 }
114 _ => return Err(EngineError::new(EngineErrorType::NoDialogue))
116 }
117 }
118}
119
120#[cfg(test)]
121mod engine_tests {
122 use crate::story::story_builder::StoryBuilder;
123 use crate::story::story_node::context::ContextValue;
124 use crate::story::story_node::dialogue::dialogue_builder::DialogueBuilder;
125 use crate::story::story_node::dialogue::quote::quote_builder::QuoteBuilder;
126
127 use super::*;
128
129 #[test]
130 fn it_constructs() {
131 let engine = Engine::new();
132 assert!(engine.story().is_none());
133 }
134
135 #[test]
136 fn start_works() {
137 let story = StoryBuilder::new(String::from("dialogue-1"))
138 .add_story_node(
139 String::from("dialogue-1"),
140 DialogueBuilder::new(String::from("core"))
141 .build(),
142 )
143 .build();
144
145 let mut engine = Engine::new();
146 engine.set_story(story);
147
148 let (node_key, _) = engine.start().unwrap();
149 assert_eq!(node_key, "dialogue-1");
150 }
151
152 #[test]
153 fn next_works() {
154 let story = StoryBuilder::new(String::from("dialogue-1"))
155 .add_story_node(
156 String::from("dialogue-1"),
157 DialogueBuilder::new(String::from("core"))
158 .add_quote(
159 QuoteBuilder::new(String::from("Hello!"))
160 .next(|_| String::from("dialogue-2"))
161 .build()
162 )
163 .add_quote(
164 QuoteBuilder::new(String::from("Homie!"))
165 .next(|_| String::from("dialogue-3"))
166 .build()
167 )
168 .build(),
169 )
170 .add_story_node(
171 String::from("dialogue-2"),
172 DialogueBuilder::new(String::from("jose"))
173 .add_quote(
174 QuoteBuilder::new(String::from("Hi there!"))
175 .build()
176 )
177 .build(),
178 )
179 .add_story_node(
180 String::from("dialogue-3"),
181 DialogueBuilder::new(String::from("jose"))
182 .add_quote(
183 QuoteBuilder::new(String::from("Who are you?"))
184 .next(|_| String::from("dialogue-4"))
185 .build()
186 )
187 .build(),
188 )
189 .build();
190
191 let mut engine = Engine::new();
192 engine.set_story(story);
193
194 let (node_1_key, _) = engine.next(Some(0)).unwrap();
195 let _ = engine.reset();
196 let (node_2_key, _) = engine.next(Some(1)).unwrap();
197
198 assert_eq!(node_1_key, "dialogue-2");
199 assert_eq!(node_2_key, "dialogue-3");
200 }
201
202 #[test]
203 fn next_with_context_works() {
204 let story = StoryBuilder::new(String::from("dialogue-0"))
205 .add_context(String::from("is-loved"), ContextValue::Bool(false))
206 .add_story_node(
207 String::from("dialogue-0"),
208 DialogueBuilder::new(String::from("girl"))
209 .add_quote(
210 QuoteBuilder::new(String::from("Don't you know?"))
211 .next(|_| String::from("dialogue-1"))
212 .build()
213 )
214 .add_quote(
215 QuoteBuilder::new(String::from("I'm sure of it."))
216 .next(|_| String::from("dialogue-1"))
217 .change_context(|c| {
218 *c.get_mut("is-loved")
219 .expect("Story to have an `is-loved` context") =
220 ContextValue::Bool(true);
221 })
222 .build()
223 )
224 .build(),
225 )
226 .add_story_node(
227 String::from("dialogue-1"),
228 DialogueBuilder::new(String::from("girl"))
229 .add_quote(
230 QuoteBuilder::new(String::from("I love you."))
231 .next(|c| {
232 let is_loved = c.get("is-loved")
233 .expect("Story to have an `is-loved` context");
234 return match is_loved {
235 ContextValue::Bool(is_loved_bool) => {
236 if is_loved_bool.clone() {
237 String::from("dialogue-2")
238 } else {
239 String::from("dialogue-3")
240 }
241 }
242 _ => String::from("dialogue-3")
243 };
244 })
245 .build()
246 )
247 .build(),
248 )
249 .add_story_node(
250 String::from("dialogue-2"),
251 DialogueBuilder::new(String::from("boy"))
252 .add_quote(
253 QuoteBuilder::new(String::from("I love you too!"))
254 .build()
255 )
256 .build(),
257 )
258 .add_story_node(
259 String::from("dialogue-3"),
260 DialogueBuilder::new(String::from("boy"))
261 .add_quote(
262 QuoteBuilder::new(String::from("I'm... sorry."))
263 .build()
264 )
265 .build(),
266 )
267 .build();
268
269
270 let mut engine = Engine::new();
271 engine.set_story(story);
272
273 let _ = engine.next(Some(0)).unwrap();
274 let (node_key, _) = engine.next(Some(0)).unwrap();
275 assert_eq!(node_key, "dialogue-3");
276
277 let _ = engine.reset();
278 let _ = engine.next(Some(1)).unwrap();
279 let (node_key, _) = engine.next(None).unwrap();
280 assert_eq!(node_key, "dialogue-2");
281 }
282}