use std::collections::HashMap;
use crate::config::directions::Directions;
use crate::config::rooms::Room;
use crate::config::{Event, Item, State, Subject, VerbFunction};
use crate::parser::action::{Action, ActionType};
use crate::parser::errors::*;
use crate::util::{
display_help, move_to_direction, parse_room_text, player_get_item, player_receive_item,
player_remove_item, MoveSuccess,
};
use crate::NRResult;
use crate::ParsingResult;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum MessageParts {
RoomText,
EventText,
Exits,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct EventMessage {
pub message: String,
pub message_parts: HashMap<MessageParts, String>,
pub templated_words: Vec<String>,
}
pub(super) fn process_action(state: &State, action: Action) -> NRResult<(State, ParsingResult)> {
match action.action_type() {
ActionType::VerbItemSubject => handle_event(state, action),
ActionType::VerbSubject => handle_verb_subject(state, action),
ActionType::VerbItem => handle_verb_item(state, action),
ActionType::Verb => handle_verb(state, action),
ActionType::Movement => handle_movement(state, action.movement),
ActionType::Invalid => Err(InvalidEvent.into()),
}
}
fn handle_verb(state: &State, action: Action) -> NRResult<(State, ParsingResult)> {
let new_state = state.clone();
let allowed_verbs = state.config.allowed_verbs.clone();
let verb = match action.verb.clone() {
Some(verb) => verb,
None => return Err(InvalidVerb.into()),
};
if allowed_verbs.contains(&verb) {
match &verb.verb_function {
VerbFunction::Quit => Ok((new_state, ParsingResult::Quit)),
VerbFunction::Help => match display_help(&new_state) {
Ok(help_text) => Ok((new_state, help_text)),
Err(error) => Err(error),
},
VerbFunction::Look => match look_room(&new_state) {
Ok(parsing_result) => Ok((new_state, parsing_result)),
Err(error) => Err(error),
},
VerbFunction::Inventory => match show_inventory(&new_state) {
Ok(parsing_result) => Ok((new_state, parsing_result)),
Err(error) => Err(error),
},
_ => match &verb.verb_function {
VerbFunction::Take => handle_verb_item(state, action),
VerbFunction::Drop => handle_verb_item(state, action),
VerbFunction::Talk => handle_verb_subject(state, action),
VerbFunction::Normal => handle_event(state, action),
_ => Err(InvalidVerb.into()),
},
}
} else {
Err(InvalidVerb.into())
}
}
fn handle_verb_subject(state: &State, action: Action) -> NRResult<(State, ParsingResult)> {
let allowed_verbs = state.config.allowed_verbs.clone();
let verb = match action.verb.clone() {
Some(verb) => verb,
None => return Err(InvalidVerb.into()),
};
let subject = match action.subject.clone() {
Some(subject) => subject,
None => return Err(InvalidSubject.into()),
};
if allowed_verbs.contains(&verb) {
if verb.verb_function == VerbFunction::Look {
match look_subject(state, subject) {
Ok(parsing_result) => Ok((state.clone(), parsing_result)),
Err(error) => Err(error),
}
} else {
handle_event(state, action)
}
} else {
Err(InvalidVerb.into())
}
}
fn handle_verb_item(state: &State, action: Action) -> NRResult<(State, ParsingResult)> {
let allowed_verbs = state.config.allowed_verbs.clone();
let verb = match action.verb.clone() {
Some(verb) => verb,
None => return Err(InvalidVerb.into()),
};
if allowed_verbs.contains(&verb) {
match action.item.clone() {
Some(item) => match &verb.verb_function {
VerbFunction::Take => pick_item(state, item),
VerbFunction::Drop => drop_item(state, item),
VerbFunction::Look => match look_item(state, item) {
Ok(parsing_result) => Ok((state.clone(), parsing_result)),
Err(error) => Err(error),
},
VerbFunction::Normal => handle_event(state, action),
_ => Err(InvalidVerb.into()),
},
None => Err(NoItem.into()),
}
} else {
Err(InvalidVerb.into())
}
}
fn handle_movement(
state: &State,
movement: Option<Directions>,
) -> NRResult<(State, ParsingResult)> {
if let Some(direction) = movement {
match move_to_direction(state, direction) {
Ok((new_state, MoveSuccess)) => {
let state_rooms = new_state.rooms.clone();
let current_room = match state_rooms
.iter()
.find(|room| room.id == new_state.current_room)
{
Some(room) => room,
None => return Err(InvalidRoom.into()),
};
let narrative = match new_state
.config
.narratives
.iter()
.find(|n| n.id == current_room.narrative)
{
Some(narrative) => narrative,
None => return Err(InvalidNarrative.into()),
};
let new_room_text =
parse_room_text(&new_state, narrative.text.clone(), "".to_string(), None)?;
Ok((new_state, ParsingResult::EventSuccess(new_room_text)))
}
Err(error) => Err(error),
}
} else {
Err(InvalidDirection.into())
}
}
fn handle_event(state: &State, action: Action) -> NRResult<(State, ParsingResult)> {
let current_room_id = state.current_room;
let current_room = state
.rooms
.iter()
.find(|room| room.id == current_room_id)
.ok_or(InvalidRoom)?;
let (inventory_item, subject) = extract_item_subject(state, &action);
let events = filter_events(current_room, &action, &inventory_item, &subject);
if events.is_empty() {
if let Some(subject) = subject {
return Ok((
state.clone(),
ParsingResult::SubjectNoEvent(subject.default_text.clone()),
));
} else {
return Err(InvalidEvent.into());
}
}
let event = events
.iter()
.find(|event| !event.is_completed())
.ok_or(InvalidEvent)?;
if !are_required_events_completed(state, event)? {
return Err(RequiredEventNotCompleted.into());
}
let mut new_state = state.clone();
let (state, event_messages) = process_event(&new_state, event, &subject)?;
new_state = state;
let event_message = format_event_message(event, &new_state, &event_messages)?;
new_state.complete_event(event.id);
if let Some(destination) = event.destination {
new_state.current_room = destination;
}
Ok((new_state, event_message))
}
fn filter_events<'a>(
room: &'a Room,
action: &Action,
inventory_item: &Option<Item>,
subject: &Option<Subject>,
) -> Vec<&'a Event> {
room.events
.iter()
.filter(
|event| match (action.verb.as_ref(), subject, inventory_item) {
(Some(verb), Some(subject), Some(item)) => {
event.required_verb == Some(verb.id)
&& event.required_subject == Some(subject.id)
&& event.required_item == Some(item.id)
}
(Some(verb), Some(subject), None) => {
event.required_verb == Some(verb.id)
&& event.required_subject == Some(subject.id)
&& event.required_item.is_none()
}
(Some(verb), None, Some(item)) => {
event.required_verb == Some(verb.id)
&& event.required_item == Some(item.id)
&& event.required_subject.is_none()
}
_ => false,
},
)
.collect()
}
fn are_required_events_completed(state: &State, event: &Event) -> NRResult<bool> {
let required_events_completed = event
.required_events
.iter()
.all(|event_id| state.is_event_completed(*event_id));
Ok(required_events_completed)
}
fn process_event(
state: &State,
event: &Event,
subject: &Option<Subject>,
) -> NRResult<(State, Vec<String>)> {
let mut new_state = state.clone();
let mut event_messages = Vec::new();
if let Some(item_id) = event.add_item {
if let Some(item) = new_state.config.items.iter().find(|i| i.id == item_id) {
let (state, message) = player_receive_item(&new_state, item.clone())?;
new_state = state;
event_messages.push(message);
}
}
if let Some(item_id) = event.remove_item {
if let Some(item) = new_state.config.items.iter().find(|i| i.id == item_id) {
let (state, message) = player_remove_item(&new_state, item.clone())?;
new_state = state;
event_messages.push(message);
}
}
let (state, _) = process_subject_movement(&new_state, event, subject)?;
new_state = state;
let (state, _) = process_subject_addition(&new_state, event)?;
new_state = state;
if event.remove_old_narrative {
if let Some(narrative_after) = event.narrative_after {
new_state.set_narrative(narrative_after)?;
}
}
Ok((new_state, event_messages))
}
fn process_subject_movement(
state: &State,
event: &Event,
subject: &Option<Subject>,
) -> NRResult<(State, ())> {
let mut new_state = state.clone();
if event.remove_subject {
let event_subject = subject.as_ref().ok_or(InvalidEvent)?;
if let Some(location) = event.move_subject_to_location {
new_state.move_subject(event_subject.id, location)?;
} else {
new_state.remove_subject(event_subject.id)?;
}
}
Ok((new_state, ()))
}
fn process_subject_addition(state: &State, event: &Event) -> NRResult<(State, ())> {
let mut new_state = state.clone();
if let Some(new_subject_id) = event.add_subject {
let new_subject = new_state
.config
.subjects
.iter()
.find(|s| s.id == new_subject_id)
.ok_or(InvalidEvent)?;
new_state.add_subject(new_subject.clone())?;
}
Ok((new_state, ()))
}
fn format_event_message(
event: &Event,
state: &State,
event_messages: &[String],
) -> NRResult<ParsingResult> {
let event_message = event_messages
.iter()
.filter(|s| !s.is_empty())
.cloned()
.collect::<Vec<String>>()
.join("");
return_formated_message(event, state, event_message)
}
fn return_formated_message(
event: &crate::config::Event,
state: &State,
event_message: String,
) -> NRResult<ParsingResult> {
let event_narrative = state
.config
.narratives
.iter()
.find(|narrative| event.narrative == Some(narrative.id))
.ok_or(InvalidNarrative)?;
let room_text = if event.remove_old_narrative {
event_narrative.text.clone()
} else {
let room_narrative_id = state
.rooms
.iter()
.find(|room| room.id == state.current_room)
.ok_or(NoRoom)?
.narrative;
let room_narrative = state
.config
.narratives
.iter()
.find(|r_narrative| r_narrative.id == room_narrative_id)
.ok_or(InvalidNarrative)?;
room_narrative.text.clone() + "\n\n" + event_narrative.text.as_str()
};
let new_room_text = parse_room_text(state, room_text, event_message, Some(event.id))?;
Ok(ParsingResult::EventSuccess(new_room_text))
}
fn extract_item_subject(state: &State, action: &Action) -> (Option<Item>, Option<Subject>) {
let current_room_id = state.current_room;
let state_rooms = state.rooms.clone();
let current_room = match state_rooms.iter().find(|room| room.id == current_room_id) {
Some(room) => room,
None => return (None, None),
};
let inventory_item = action.item.clone().filter(|item| {
state
.player
.inventory
.items
.iter()
.any(|player_item| player_item.id == item.id)
});
let subject = action.subject.clone().filter(|action_subject| {
current_room
.subjects
.iter()
.any(|s| s.id == action_subject.id)
});
(inventory_item, subject)
}
fn show_inventory(state: &State) -> NRResult<ParsingResult> {
let player_items = &state.player.inventory.items;
let items: Vec<String> = player_items
.iter()
.map(|item| {
let mut item_name = item.name.clone();
let article = match item.name.to_lowercase().chars().next() {
Some('a' | 'e' | 'i' | 'o' | 'u') => "an ",
_ => "a ",
};
item_name.insert_str(0, article);
item_name
})
.collect();
if !items.is_empty() {
let mut items_string = items.join("\n");
items_string.insert_str(0, "You are currently carrying: \n\n");
Ok(ParsingResult::Inventory(items_string))
} else {
Ok(ParsingResult::Inventory(
"You are not carrying anything.".to_string(),
))
}
}
fn pick_item(state: &State, item: Item) -> NRResult<(State, ParsingResult)> {
let current_room_id = state.current_room;
let current_room = match state.rooms.iter().find(|room| room.id == current_room_id) {
Some(room) => room,
None => return Err(NoRoom.into()),
};
let room_items = ¤t_room.stash.items;
if room_items.contains(&item) {
if item.can_pick {
player_get_item(state, item)
} else {
Err(CantPick.into())
}
} else {
Err(NoItem.into())
}
}
fn drop_item(state: &State, item: Item) -> NRResult<(State, ParsingResult)> {
let current_room_id = state.current_room;
if state.player.inventory.items.contains(&item) {
let (mut new_state, message) = player_remove_item(state, item.clone())?;
match new_state
.rooms
.iter_mut()
.find(|room| room.id == current_room_id)
{
Some(room) => room.stash.add_item(item),
None => return Err(NoRoom.into()),
};
Ok((new_state, ParsingResult::DropItem(message)))
} else {
Err(NoItem.into())
}
}
fn look_item(state: &State, item: Item) -> NRResult<ParsingResult> {
let current_room_id = state.current_room;
let inventory = &state.player.inventory;
let current_room = match state.rooms.iter().find(|room| room.id == current_room_id) {
Some(room) => room,
None => return Err(NoRoom.into()),
};
let room_items = ¤t_room.stash.items;
let inventory_items = &inventory.items;
if room_items.contains(&item) || inventory_items.contains(&item) {
Ok(ParsingResult::Look(item.description))
} else {
Ok(ParsingResult::Look("I can't see that here".to_string()))
}
}
fn look_subject(state: &State, subject: Subject) -> NRResult<ParsingResult> {
let current_room_id = state.current_room;
let current_room = match state.rooms.iter().find(|room| room.id == current_room_id) {
Some(room) => room,
None => return Err(NoRoom {}.into()),
};
let room_subjects = ¤t_room.subjects;
if room_subjects.contains(&subject) {
Ok(ParsingResult::Look(subject.description))
} else {
Ok(ParsingResult::Look("I can't see that here".to_string()))
}
}
fn look_room(state: &State) -> NRResult<ParsingResult> {
let current_room_id = state.current_room;
let rooms = state.rooms.clone();
let current_room = match rooms.iter().find(|room| room.id == current_room_id) {
Some(room) => room,
None => return Err(NoRoom.into()),
};
let room_subjects = current_room
.subjects
.clone()
.iter()
.map(|subject| subject.name.clone())
.collect::<Vec<String>>()
.join("\n");
let description = ¤t_room.description;
let items = current_room.stash.items.clone();
let items_descriptions = if !items.is_empty() {
format!(
"Here you see: \n{}",
items
.iter()
.clone()
.map(|item| match item.name.to_lowercase().chars().next() {
Some('a' | 'e' | 'i' | 'o' | 'u') => format!("an {}", item.name),
_ => format!("a {}", &item),
})
.collect::<Vec<String>>()
.join("\n")
)
} else {
"".to_string()
};
let mut room_description = if items_descriptions.is_empty() {
description.to_string()
} else {
format!("{}\n\n{}", description, items_descriptions)
};
if !room_subjects.is_empty() {
room_description.push_str(&format!("\n{}", room_subjects));
}
Ok(ParsingResult::Look(room_description))
}
#[cfg(test)]
#[path = "interpreter_tests.rs"]
mod interpreter_tests;