use capitalize::Capitalize;
use std::{collections::BTreeMap, iter};
use crate::{
tree::{self, Node, Tree},
vessel::{Article, Vessel, vessel_text},
};
use super::parse::{Action, Verb, VesselRequest};
pub fn list_children(children: BTreeMap<String, Vessel>) -> String {
children
.iter()
.fold("You see:".to_string(), |acc, (key, vessel)| {
format!("{acc}\n\t> {}", vessel_text(key, &vessel.article))
})
}
pub fn note_text(vessel: &Vessel) -> String {
if let Some(note) = &vessel.note {
format!("{note}\n")
} else {
String::new()
}
}
pub fn status_info(
tree: &Tree<String, Vessel>,
user_key: &String,
) -> Result<String, tree::Error<String>> {
let user = tree.get(user_key)?;
let parent = tree.get(&user.parent)?;
let mut out = String::new();
out += &format!(
"You are {} in {}.\n",
vessel_text(user_key, &user.value.article),
vessel_text(&user.parent, &parent.value.article)
);
out += ¬e_text(&parent.value);
for sibling in tree
.get_all(tree.siblings(user_key, false)?)?
.iter()
.map(|(key, node)| vessel_text(key, &node.value.article))
{
out += &format!("You see {}.\n", sibling);
}
for child in tree
.get_all(tree.children(user_key))?
.iter()
.map(|(key, node)| vessel_text(key, &node.value.article))
{
out += &format!("You are carrying {}.\n", child);
}
Ok(out)
}
pub enum ActionResult {
ShowStatus,
Programming(String, Option<Article>),
StillProgramming,
StillProgrammingButExit,
DoneProgramming,
Exit,
UserError(String),
DebugText(String),
ResponseText(String),
HelpText(String),
Saved,
Loaded,
SaveError(std::io::Error),
LoadError(std::io::Error),
}
pub fn run_program(
mut user_key: String,
world: &mut Tree<String, Vessel>,
program: Vec<Action>,
) -> Result<ActionResult, Box<dyn std::error::Error>> {
let mut local_editor_stack: Vec<String> = Vec::new();
for action in program {
if matches!(action, Action::Save { filename: _ })
|| matches!(action, Action::Load { filename: _ })
{
return Ok(ActionResult::UserError(
"Programs are not allowed to use the debug, save, or load actions.".to_string(),
));
}
match run_action(
&mut user_key,
world,
Some(action.clone()),
&mut local_editor_stack,
) {
Ok(ActionResult::StillProgrammingButExit)
| Ok(ActionResult::DoneProgramming)
| Ok(ActionResult::ResponseText(_))
| Ok(ActionResult::HelpText(_))
| Ok(ActionResult::StillProgramming)
| Ok(ActionResult::ShowStatus)
| Ok(ActionResult::Programming(_, _)) => {}
Ok(ActionResult::Exit) => {
if local_editor_stack.pop().is_none() {
return Ok(ActionResult::Exit);
}
}
Ok(ActionResult::UserError(e)) => {
return Ok(ActionResult::UserError(e));
}
Ok(ActionResult::DebugText(_))
| Ok(ActionResult::Saved)
| Ok(ActionResult::Loaded)
| Ok(ActionResult::SaveError(_))
| Ok(ActionResult::LoadError(_)) => {
return Ok(ActionResult::UserError(
"Programs are not allowed to use the debug, save, or load actions.".to_string(),
));
}
Err(e) => return Err(e),
}
}
Ok(ActionResult::ShowStatus)
}
pub fn run_action(
user_key: &mut String,
world: &mut Tree<String, Vessel>,
action: Option<Action>,
editor_stack: &mut Vec<String>,
) -> Result<ActionResult, Box<dyn std::error::Error>> {
let parent_key = world.get_parent(user_key)?.clone();
match editor_stack.first().map(|editing| world.get_mut(editing)) {
Some(Ok(editing_node)) => match action {
Some(Action::Exit) => {
editor_stack.pop();
if editor_stack.is_empty() {
Ok(ActionResult::DoneProgramming)
} else {
editing_node.value.program = match &editing_node.value.program {
Some(program) => Some(
program
.clone()
.into_iter()
.chain(iter::once(Action::Exit))
.collect(),
),
None => Some(vec![Action::Exit]),
};
Ok(ActionResult::StillProgrammingButExit)
}
}
Some(Action::Program(vessel_request)) => {
let action_copy = Action::Program(vessel_request.clone());
let name = vessel_request
.map(|vr| vr.name)
.unwrap_or(parent_key.clone());
editor_stack.push(name);
editing_node.value.program = match &editing_node.value.program {
Some(program) => Some(
program
.clone()
.into_iter()
.chain(iter::once(action_copy))
.collect(),
),
None => Some(vec![action_copy]),
};
Ok(ActionResult::StillProgramming)
}
Some(action) => {
editing_node.value.program = match &editing_node.value.program {
Some(program) => Some(
program
.clone()
.into_iter()
.chain(iter::once(action))
.collect(),
),
None => Some(vec![action]),
};
Ok(ActionResult::StillProgramming)
}
None => Ok(ActionResult::StillProgramming),
},
Some(Err(e)) => Err(Box::new(e)),
None => match action {
Some(action) => match action {
Action::Create(VesselRequest { article, name }) => {
if let Err(e) = world.add(
name.clone(),
Node::new(Vessel::with_article(article), parent_key),
) {
match e {
tree::Error::NonUniqueKey(key) => {
Ok(ActionResult::UserError(format!("{key} already exists")))
}
_ => Err(Box::new(e)),
}
} else {
Ok(ActionResult::ShowStatus)
}
}
Action::Become(VesselRequest { article, name }) => {
if world
.get_parent(&name)
.is_ok_and(|parent| parent == &parent_key)
{
*user_key = name;
Ok(ActionResult::ShowStatus)
} else {
Ok(ActionResult::UserError(format!(
"You don't see {}.",
vessel_text(&name, &article)
)))
}
}
Action::Enter(VesselRequest { article, name }) => {
if let Err(e) = world.set_parent(user_key, name.clone()) {
match e {
tree::Error::NonexistentNode(_) => Ok(ActionResult::UserError(
format!("You don't see {}.", vessel_text(&name, &article)),
)),
_ => Err(Box::new(e)),
}
} else {
Ok(ActionResult::ShowStatus)
}
}
Action::Leave => {
world.set_parent(user_key, world.get_parent(&parent_key)?.clone())?;
Ok(ActionResult::ShowStatus)
}
Action::Take(VesselRequest { article, name }) => {
if world
.get_parent(&name)
.is_ok_and(|selected_parent| selected_parent == &parent_key)
{
world.set_parent(&name, user_key.clone())?;
Ok(ActionResult::ShowStatus)
} else {
Ok(ActionResult::UserError(format!(
"You don't see {}.",
vessel_text(&name, &article)
)))
}
}
Action::Drop(VesselRequest { article, name }) => {
if world
.get_parent(&name)
.is_ok_and(|selected_parent| selected_parent == user_key)
{
world.set_parent(&name, parent_key.clone())?;
Ok(ActionResult::ShowStatus)
} else {
Ok(ActionResult::UserError(format!(
"You aren't carrying {}.",
vessel_text(&name, &article)
)))
}
}
Action::Move {
object_article,
object_name,
preposition: _,
target_article,
target_name,
} => {
if world
.get_parent(&object_name)
.is_ok_and(|object_parent| object_parent == &parent_key)
{
if world
.get_parent(&target_name)
.is_ok_and(|target_parent| target_parent == &parent_key)
{
world.set_parent(&object_name, target_name)?;
Ok(ActionResult::ShowStatus)
} else {
Ok(ActionResult::UserError(format!(
"You don't see {}",
vessel_text(&target_name, &target_article)
)))
}
} else {
Ok(ActionResult::UserError(format!(
"You don't see {}",
vessel_text(&object_name, &object_article)
)))
}
}
Action::Look {
preposition: _,
basic,
} => match basic {
Some(VesselRequest { article, name }) => {
match world
.get_from_parent(&name, &parent_key)
.ok()
.flatten()
.or_else(|| world.get_from_parent(&name, user_key).ok().flatten())
{
Some(node) => Ok(ActionResult::ResponseText(format!(
"You look at {}.\n{}{}",
vessel_text(&name, &article),
note_text(&node.value),
list_children(world.get_all_values(world.children(&name))?)
))),
None => Ok(ActionResult::UserError(format!(
"You don't see {}.",
vessel_text(&name, &article)
))),
}
}
None => Ok(ActionResult::ResponseText(format!(
"{}{}",
note_text(&world.get(&parent_key)?.value),
list_children(world.get_all_values(world.siblings(user_key, false)?)?)
))),
},
Action::Transform {
preposition: _,
basic: VesselRequest { article: _, name },
} => {
if let Err(e) = world.rename(user_key, name.clone()) {
match e {
tree::Error::NonUniqueKey(key) => {
Ok(ActionResult::UserError(format!("{key} already exists.")))
}
e => Err(Box::new(e)),
}
} else {
*user_key = name;
Ok(ActionResult::ShowStatus)
}
}
Action::Note { text: note } => {
world.get_mut(&parent_key)?.value.note = note;
Ok(ActionResult::ShowStatus)
}
Action::Warp {
preposition: _,
basic: VesselRequest { article, name },
} => {
if let Err(e) = world.set_parent(user_key, name) {
match e {
tree::Error::NonexistentNode(node) => Ok(ActionResult::UserError(
format!("You can't find {}.", vessel_text(&node, &article)),
)),
e => Err(Box::new(e)),
}
} else {
Ok(ActionResult::ShowStatus)
}
}
Action::Program(vessel_request) => match vessel_request {
Some(VesselRequest { article, name }) => {
if world
.get_parent(&name)
.is_ok_and(|parent| parent == &parent_key)
{
editor_stack.push(name.clone());
Ok(ActionResult::Programming(name, article))
} else {
Ok(ActionResult::UserError(format!(
"You can't find {}.",
vessel_text(&name, &article)
)))
}
}
None => {
editor_stack.push(parent_key.clone());
let article = world.get(&parent_key)?.value.article;
Ok(ActionResult::Programming(parent_key, article))
}
},
Action::Use(VesselRequest { article, name }) => {
if let Some(program) = world
.get_from_parent(&name, &parent_key)
.ok()
.flatten()
.map(|node| node.value.program.clone())
{
match program {
Some(program) => run_program(name, world, program),
None => Ok(ActionResult::UserError(format!(
"{} has no program.",
vessel_text(&name, &article).capitalize_first_only()
))),
}
} else {
Ok(ActionResult::UserError(format!(
"You can't find {}.",
vessel_text(&name, &article)
)))
}
}
Action::Cast {
spell,
preposition: _,
target,
} => {
if let Some(program) = world
.get(&spell.name)
.ok()
.map(|node| node.value.program.clone())
{
match program {
Some(program) => {
if let Ok(Some(_)) =
world.get_from_parent(&target.name, &parent_key)
{
run_program(target.name, world, program)
} else {
Ok(ActionResult::UserError(format!(
"You can't find {}.",
vessel_text(&target.name, &target.article)
)))
}
}
None => Ok(ActionResult::UserError(format!(
"{} has no program.",
vessel_text(&spell.name, &spell.article).capitalize_first_only()
))),
}
} else {
Ok(ActionResult::UserError(format!(
"You can't find {}.",
vessel_text(&spell.name, &spell.article)
)))
}
}
Action::Save { filename } => {
let filename = filename.unwrap_or("parade_save".to_string()) + ".json";
let save_data = serde_json::to_string(&world)?;
match std::fs::write(filename, save_data) {
Ok(_) => Ok(ActionResult::Saved),
Err(e) => Ok(ActionResult::SaveError(e)),
}
}
Action::Load { filename } => {
let filename = filename.unwrap_or("parade_save".to_string()) + ".json";
let save_data_text = match std::fs::read_to_string(filename) {
Ok(data) => data,
Err(e) => return Ok(ActionResult::LoadError(e)),
};
let save_data: Tree<String, Vessel> = serde_json::from_str(&save_data_text)?;
*world = save_data;
Ok(ActionResult::Loaded)
}
Action::Exit => Ok(ActionResult::Exit),
Action::Debug => Ok(ActionResult::DebugText(format!("{:?}", world))),
Action::Help { topic: Some(topic) } => {
Ok(ActionResult::HelpText(topic.help_text()))
}
Action::Help { topic: None } => Ok(ActionResult::HelpText(Verb::help_list_text())),
},
None => Ok(ActionResult::ShowStatus),
},
}
}