Skip to main content

parade_rs/action/
interface.rs

1use capitalize::Capitalize;
2use std::{collections::BTreeMap, iter};
3
4use crate::{
5    tree::{self, Node, Tree},
6    vessel::{Article, Vessel, vessel_text},
7};
8
9use super::parse::{Action, Verb, VesselRequest};
10
11/// Generates a [String] listing the children of a given [Vessel].
12pub fn list_children(children: BTreeMap<String, Vessel>) -> String {
13    children
14        .iter()
15        .fold("You see:".to_string(), |acc, (key, vessel)| {
16            format!("{acc}\n\t> {}", vessel_text(key, &vessel.article))
17        })
18}
19/// Generates a [String] containing a given [Vessel]'s note, followed by a newline, if such a note is present.
20/// Otherwise, returns an empty [String].
21pub fn note_text(vessel: &Vessel) -> String {
22    if let Some(note) = &vessel.note {
23        format!("{note}\n")
24    } else {
25        String::new()
26    }
27}
28/// Generates a [String] informing a user of their [Vessel]'s location, the other contents of that location, and the contents of their [Vessel].
29///
30/// # Errors
31///
32/// This method fails if the specified [Vessel] is not present in the provided [Tree], if that [Vessel]'s parent is not present in the [Tree], or if any of the [Vessel]'s siblings or children cannot be located.
33pub fn status_info(
34    tree: &Tree<String, Vessel>,
35    user_key: &String,
36) -> Result<String, tree::Error<String>> {
37    let user = tree.get(user_key)?;
38    let parent = tree.get(&user.parent)?;
39    let mut out = String::new();
40
41    out += &format!(
42        "You are {} in {}.\n",
43        vessel_text(user_key, &user.value.article),
44        vessel_text(&user.parent, &parent.value.article)
45    );
46    out += &note_text(&parent.value);
47
48    for sibling in tree
49        .get_all(tree.siblings(user_key, false)?)?
50        .iter()
51        .map(|(key, node)| vessel_text(key, &node.value.article))
52    {
53        out += &format!("You see {}.\n", sibling);
54    }
55    for child in tree
56        .get_all(tree.children(user_key))?
57        .iter()
58        .map(|(key, node)| vessel_text(key, &node.value.article))
59    {
60        out += &format!("You are carrying {}.\n", child);
61    }
62
63    Ok(out)
64}
65
66/// The result of an [Action], after it has been run.
67/// Instructs the client on what to tell the user.
68pub enum ActionResult {
69    /// The client should display the user's [Vessel]'s status, likely via [status_info].
70    ShowStatus,
71    /// The interface has begun programming a [Vessel], with the provided name and [Article].
72    Programming(String, Option<Article>),
73    /// The interface is programming a [Vessel], and has accepted the provided input.
74    StillProgramming,
75    /// The interface is programming a [Vessel], and has accepted an [Action::Exit], but is programming the [Vessel] to program another [Vessel], so we are not exiting the [Action::Program] state.
76    StillProgrammingButExit,
77    /// The interface has stopped programming a [Vessel].
78    DoneProgramming,
79    /// The interface has received an [Action::Exit] from the user, and the client should now exit.
80    Exit,
81    /// There was an issue with the user's input, and the client should inform them of it.
82    /// Contains the error message to be displayed.
83    UserError(String),
84    /// The client has requested debug information, and the client should display it.
85    /// Contains the debug informtion to be displayed.
86    DebugText(String),
87    /// The action has triggered a custom response message, which the client should display.
88    /// Contains the message to be displayed.
89    ResponseText(String),
90    /// The action has triggered a "help" message, which the client should display.
91    /// Contains the message to be displayed.
92    HelpText(String),
93    /// The interface has saved the current state, and the client should inform the user.
94    Saved,
95    /// The interface has loaded a previous state, and the client should inform the user.
96    Loaded,
97    /// There was an error saving the current state, and the client should inform the user.
98    /// Contains the error to be displayed.
99    SaveError(std::io::Error),
100    /// There was an error loading a previous state, and the client should inform the user.
101    /// Contains the error to be displayed.
102    LoadError(std::io::Error),
103}
104
105/// Runs the provided program on the provided world-state, possibly modifying the provided user key.
106/// - `user_key`: The key of the user's active [Vessel].
107/// - `world`: The mutable world-state.
108/// - `program`: The set of [Action]s to be run.
109///
110/// Returns an [ActionResult] informing the client of what to tell the user, if successful.
111///
112/// # Errors:
113///
114/// This method fails if any of the [Action]s fails.
115pub fn run_program(
116    user_key: &mut String,
117    world: &mut Tree<String, Vessel>,
118    program: Vec<Action>,
119) -> Result<ActionResult, Box<dyn std::error::Error>> {
120    let mut local_editor_stack: Vec<String> = Vec::new();
121    for action in program {
122        if matches!(action, Action::Save { filename: _ })
123            || matches!(action, Action::Load { filename: _ })
124        {
125            return Ok(ActionResult::UserError(
126                "Programs are not allowed to use the debug, save, or load actions.".to_string(),
127            ));
128        }
129        match run_action(
130            user_key,
131            world,
132            Some(action.clone()),
133            &mut local_editor_stack,
134        ) {
135            Ok(ActionResult::StillProgrammingButExit)
136            | Ok(ActionResult::DoneProgramming)
137            | Ok(ActionResult::ResponseText(_))
138            | Ok(ActionResult::HelpText(_))
139            | Ok(ActionResult::StillProgramming)
140            | Ok(ActionResult::ShowStatus)
141            | Ok(ActionResult::Programming(_, _)) => {}
142            Ok(ActionResult::Exit) => {
143                if local_editor_stack.pop().is_none() {
144                    return Ok(ActionResult::Exit);
145                }
146            }
147            Ok(ActionResult::UserError(e)) => {
148                return Ok(ActionResult::UserError(e));
149            }
150            Ok(ActionResult::DebugText(_))
151            | Ok(ActionResult::Saved)
152            | Ok(ActionResult::Loaded)
153            | Ok(ActionResult::SaveError(_))
154            | Ok(ActionResult::LoadError(_)) => {
155                return Ok(ActionResult::UserError(
156                    "Programs are not allowed to use the debug, save, or load actions.".to_string(),
157                ));
158            }
159            Err(e) => return Err(e),
160        }
161    }
162
163    Ok(ActionResult::ShowStatus)
164}
165
166/// Runs the provided action on the provided world-state, possibly modifying the provided user key and/or editing stack.
167/// - `user_key`: The key of the user's active [Vessel].
168/// - `world`: The mutable world-state.
169/// - `action`: The action to be run, if any.
170///
171/// Returns an [ActionResult] informing the client of what to tell the user, if successful.
172///
173/// # Errors:
174///
175/// This method fails if:
176/// - The [Vessel] at the provided `user_key` or its parent [Node] cannot be found
177/// - Another [Node] which should exist cannot be found (such as a sibling or child of the user's [Vessel], or a child thereof)
178/// - There is an issue serializing and saving or loading and deserializing the world-state.
179pub fn run_action(
180    user_key: &mut String,
181    world: &mut Tree<String, Vessel>,
182    action: Option<Action>,
183    editor_stack: &mut Vec<String>,
184) -> Result<ActionResult, Box<dyn std::error::Error>> {
185    let parent_key = world.get_parent(user_key)?.clone();
186
187    match editor_stack.first().map(|editing| world.get_mut(editing)) {
188        Some(Ok(editing_node)) => match action {
189            Some(Action::Exit) => {
190                editor_stack.pop();
191                if editor_stack.is_empty() {
192                    Ok(ActionResult::DoneProgramming)
193                } else {
194                    editing_node.value.program = match &editing_node.value.program {
195                        Some(program) => Some(
196                            program
197                                .clone()
198                                .into_iter()
199                                .chain(iter::once(Action::Exit))
200                                .collect(),
201                        ),
202                        None => Some(vec![Action::Exit]),
203                    };
204
205                    Ok(ActionResult::StillProgrammingButExit)
206                }
207            }
208            Some(Action::Program(vessel_request)) => {
209                let action_copy = Action::Program(vessel_request.clone());
210                let name = vessel_request
211                    .map(|vr| vr.name)
212                    .unwrap_or(parent_key.clone());
213                editor_stack.push(name);
214                editing_node.value.program = match &editing_node.value.program {
215                    Some(program) => Some(
216                        program
217                            .clone()
218                            .into_iter()
219                            .chain(iter::once(action_copy))
220                            .collect(),
221                    ),
222                    None => Some(vec![action_copy]),
223                };
224
225                Ok(ActionResult::StillProgramming)
226            }
227            Some(action) => {
228                editing_node.value.program = match &editing_node.value.program {
229                    Some(program) => Some(
230                        program
231                            .clone()
232                            .into_iter()
233                            .chain(iter::once(action))
234                            .collect(),
235                    ),
236                    None => Some(vec![action]),
237                };
238
239                Ok(ActionResult::StillProgramming)
240            }
241            None => Ok(ActionResult::StillProgramming),
242        },
243        Some(Err(e)) => Err(Box::new(e)),
244        None => match action {
245            Some(action) => match action {
246                Action::Create(VesselRequest { article, name }) => {
247                    if let Err(e) = world.add(
248                        name.clone(),
249                        Node::new(Vessel::with_article(article), parent_key),
250                    ) {
251                        match e {
252                            tree::Error::NonUniqueKey(key) => {
253                                Ok(ActionResult::UserError(format!("{key} already exists")))
254                            }
255                            _ => Err(Box::new(e)),
256                        }
257                    } else {
258                        Ok(ActionResult::ShowStatus)
259                    }
260                }
261                Action::Become(VesselRequest { article, name }) => {
262                    if world
263                        .get_parent(&name)
264                        .is_ok_and(|parent| parent == &parent_key)
265                    {
266                        *user_key = name;
267
268                        Ok(ActionResult::ShowStatus)
269                    } else {
270                        Ok(ActionResult::UserError(format!(
271                            "You don't see {}.",
272                            vessel_text(&name, &article)
273                        )))
274                    }
275                }
276                Action::Enter(VesselRequest { article, name }) => {
277                    if let Err(e) = world.set_parent(user_key, name.clone()) {
278                        match e {
279                            tree::Error::NonexistentNode(_) => Ok(ActionResult::UserError(
280                                format!("You don't see {}.", vessel_text(&name, &article)),
281                            )),
282                            _ => Err(Box::new(e)),
283                        }
284                    } else {
285                        Ok(ActionResult::ShowStatus)
286                    }
287                }
288                Action::Leave => {
289                    world.set_parent(user_key, world.get_parent(&parent_key)?.clone())?;
290
291                    Ok(ActionResult::ShowStatus)
292                }
293                Action::Take(VesselRequest { article, name }) => {
294                    if world
295                        .get_parent(&name)
296                        .is_ok_and(|selected_parent| selected_parent == &parent_key)
297                    {
298                        world.set_parent(&name, user_key.clone())?;
299
300                        Ok(ActionResult::ShowStatus)
301                    } else {
302                        Ok(ActionResult::UserError(format!(
303                            "You don't see {}.",
304                            vessel_text(&name, &article)
305                        )))
306                    }
307                }
308                Action::Drop(VesselRequest { article, name }) => {
309                    if world
310                        .get_parent(&name)
311                        .is_ok_and(|selected_parent| selected_parent == user_key)
312                    {
313                        world.set_parent(&name, parent_key.clone())?;
314
315                        Ok(ActionResult::ShowStatus)
316                    } else {
317                        Ok(ActionResult::UserError(format!(
318                            "You aren't carrying {}.",
319                            vessel_text(&name, &article)
320                        )))
321                    }
322                }
323                Action::Move {
324                    object_article,
325                    object_name,
326                    preposition: _,
327                    target_article,
328                    target_name,
329                } => {
330                    if world
331                        .get_parent(&object_name)
332                        .is_ok_and(|object_parent| object_parent == &parent_key)
333                    {
334                        if world
335                            .get_parent(&target_name)
336                            .is_ok_and(|target_parent| target_parent == &parent_key)
337                        {
338                            world.set_parent(&object_name, target_name)?;
339                            Ok(ActionResult::ShowStatus)
340                        } else {
341                            Ok(ActionResult::UserError(format!(
342                                "You don't see {}",
343                                vessel_text(&target_name, &target_article)
344                            )))
345                        }
346                    } else {
347                        Ok(ActionResult::UserError(format!(
348                            "You don't see {}",
349                            vessel_text(&object_name, &object_article)
350                        )))
351                    }
352                }
353                Action::Look {
354                    preposition: _,
355                    basic,
356                } => match basic {
357                    Some(VesselRequest { article, name }) => {
358                        match world
359                            .get_from_parent(&name, &parent_key)
360                            .ok()
361                            .flatten()
362                            .or_else(|| world.get_from_parent(&name, user_key).ok().flatten())
363                        {
364                            Some(node) => Ok(ActionResult::ResponseText(format!(
365                                "You look at {}.\n{}{}",
366                                vessel_text(&name, &article),
367                                note_text(&node.value),
368                                list_children(world.get_all_values(world.children(&name))?)
369                            ))),
370                            None => Ok(ActionResult::UserError(format!(
371                                "You don't see {}.",
372                                vessel_text(&name, &article)
373                            ))),
374                        }
375                    }
376                    None => Ok(ActionResult::ResponseText(format!(
377                        "{}{}",
378                        note_text(&world.get(&parent_key)?.value),
379                        list_children(world.get_all_values(world.siblings(user_key, false)?)?)
380                    ))),
381                },
382                Action::Transform {
383                    preposition: _,
384                    basic: VesselRequest { article: _, name },
385                } => {
386                    if let Err(e) = world.rename(user_key, name.clone()) {
387                        match e {
388                            tree::Error::NonUniqueKey(key) => {
389                                Ok(ActionResult::UserError(format!("{key} already exists.")))
390                            }
391                            e => Err(Box::new(e)),
392                        }
393                    } else {
394                        *user_key = name;
395                        Ok(ActionResult::ShowStatus)
396                    }
397                }
398                Action::Note { text: note } => {
399                    world.get_mut(&parent_key)?.value.note = note;
400
401                    Ok(ActionResult::ShowStatus)
402                }
403                Action::Warp {
404                    preposition: _,
405                    basic: VesselRequest { article, name },
406                } => {
407                    if let Err(e) = world.set_parent(user_key, name) {
408                        match e {
409                            tree::Error::NonexistentNode(node) => Ok(ActionResult::UserError(
410                                format!("You can't find {}.", vessel_text(&node, &article)),
411                            )),
412                            e => Err(Box::new(e)),
413                        }
414                    } else {
415                        Ok(ActionResult::ShowStatus)
416                    }
417                }
418                Action::Program(vessel_request) => match vessel_request {
419                    Some(VesselRequest { article, name }) => {
420                        if world
421                            .get_parent(&name)
422                            .is_ok_and(|parent| parent == &parent_key)
423                        {
424                            editor_stack.push(name.clone());
425                            Ok(ActionResult::Programming(name, article))
426                        } else {
427                            Ok(ActionResult::UserError(format!(
428                                "You can't find {}.",
429                                vessel_text(&name, &article)
430                            )))
431                        }
432                    }
433                    None => {
434                        editor_stack.push(parent_key.clone());
435                        let article = world.get(&parent_key)?.value.article;
436                        Ok(ActionResult::Programming(parent_key, article))
437                    }
438                },
439                Action::Use(VesselRequest { article, name }) => {
440                    if let Some(program) = world
441                        .get_from_parent(&name, &parent_key)
442                        .ok()
443                        .flatten()
444                        .map(|node| node.value.program.clone())
445                    {
446                        match program {
447                            Some(program) => run_program(user_key, world, program),
448                            None => Ok(ActionResult::UserError(format!(
449                                "{} has no program.",
450                                vessel_text(&name, &article).capitalize_first_only()
451                            ))),
452                        }
453                    } else {
454                        Ok(ActionResult::UserError(format!(
455                            "You can't find {}.",
456                            vessel_text(&name, &article)
457                        )))
458                    }
459                }
460                Action::Cast {
461                    spell,
462                    preposition: _,
463                    target,
464                } => {
465                    if let Some(program) = world
466                        .get(&spell.name)
467                        .ok()
468                        .map(|node| node.value.program.clone())
469                    {
470                        match program {
471                            Some(program) => {
472                                if let Ok(Some(_)) =
473                                    world.get_from_parent(&target.name, &parent_key)
474                                {
475                                    let mut name = target.name;
476                                    run_program(&mut name, world, program)
477                                } else {
478                                    Ok(ActionResult::UserError(format!(
479                                        "You can't find {}.",
480                                        vessel_text(&target.name, &target.article)
481                                    )))
482                                }
483                            }
484                            None => Ok(ActionResult::UserError(format!(
485                                "{} has no program.",
486                                vessel_text(&spell.name, &spell.article).capitalize_first_only()
487                            ))),
488                        }
489                    } else {
490                        Ok(ActionResult::UserError(format!(
491                            "You can't find {}.",
492                            vessel_text(&spell.name, &spell.article)
493                        )))
494                    }
495                }
496                Action::Save { filename } => {
497                    let filename = filename.unwrap_or("parade_save".to_string()) + ".json";
498                    let save_data = serde_json::to_string(&world)?;
499                    match std::fs::write(filename, save_data) {
500                        Ok(_) => Ok(ActionResult::Saved),
501                        Err(e) => Ok(ActionResult::SaveError(e)),
502                    }
503                }
504                Action::Load { filename } => {
505                    let filename = filename.unwrap_or("parade_save".to_string()) + ".json";
506                    let save_data_text = match std::fs::read_to_string(filename) {
507                        Ok(data) => data,
508                        Err(e) => return Ok(ActionResult::LoadError(e)),
509                    };
510                    let save_data: Tree<String, Vessel> = serde_json::from_str(&save_data_text)?;
511                    *world = save_data;
512                    Ok(ActionResult::Loaded)
513                }
514                Action::Exit => Ok(ActionResult::Exit),
515                Action::Debug => Ok(ActionResult::DebugText(format!("{:?}", world))),
516                Action::Learn {
517                    preposition: _,
518                    topic: Some(topic),
519                } => Ok(ActionResult::HelpText(topic.help_text())),
520                Action::Learn {
521                    preposition: _,
522                    topic: None,
523                } => Ok(ActionResult::HelpText(Verb::help_list_text())),
524            },
525            None => Ok(ActionResult::ShowStatus),
526        },
527    }
528}