pub mod helpers;
pub(crate) mod choices;
pub(crate) mod dispatch;
pub(crate) mod eval;
pub(crate) mod exec;
pub(crate) mod resolve;
pub(crate) mod turn;
pub(crate) mod validate;
use crate::interactive_fiction::data::{Choice, RuntimeState, World};
use std::sync::Arc;
pub use validate::ValidationError;
use validate::validate;
pub struct Engine {
world: Arc<World>,
rule_index: dispatch::RuleIndex,
trace_rules: bool,
}
impl Engine {
pub fn new(world: World) -> Result<Self, Vec<ValidationError>> {
validate(&world)?;
let world = Arc::new(world);
let rule_index = dispatch::RuleIndex::build(&world);
Ok(Self {
world,
rule_index,
trace_rules: false,
})
}
pub fn world(&self) -> &World {
&self.world
}
pub fn set_rule_tracing(&mut self, enabled: bool) {
self.trace_rules = enabled;
}
pub fn rule_tracing_enabled(&self) -> bool {
self.trace_rules
}
pub fn start_state(&self) -> RuntimeState {
let mut state = RuntimeState {
current_room: self.world.start_room.clone(),
rng_state: 0x9E3779B97F4A7C15,
..RuntimeState::default()
};
for (item_id, item) in &self.world.items {
let location = item
.initial_location
.clone()
.unwrap_or(crate::interactive_fiction::data::ItemLocation::Nowhere);
state.item_locations.insert(item_id.clone(), location);
}
for (entity_id, entity) in &self.world.entities {
if let Some(location) = &entity.initial_location {
state
.entity_locations
.insert(entity_id.clone(), location.clone());
}
if let crate::interactive_fiction::data::EntityKind::Character {
initial_disposition,
} = &entity.kind
{
state
.dispositions
.insert(entity_id.clone(), *initial_disposition);
}
}
for (quest_id, quest) in &self.world.quests {
state
.quest_stages
.insert(quest_id.clone(), quest.start.clone());
let mut seen = std::collections::BTreeSet::new();
seen.insert(quest.start.clone());
state.quest_history.insert(quest_id.clone(), seen);
}
state
}
pub fn start(&self, state: &mut RuntimeState) {
turn::start(self, state);
}
pub fn pick(&self, state: &mut RuntimeState, index: usize) {
turn::pick(self, state, index);
}
pub fn available_choices(&self, state: &RuntimeState) -> Vec<Choice> {
choices::assemble(self, state)
}
pub fn resolve_text(
&self,
state: &RuntimeState,
text: &crate::interactive_fiction::data::Text,
) -> String {
resolve::resolve(self, state, text)
}
pub fn describe_current_room(&self, state: &mut RuntimeState) {
exec::describe_current_room(self, state);
}
pub fn apply_all_examine(&self, state: &mut RuntimeState) {
use crate::interactive_fiction::data::{ChoiceAction, ExamineTarget, TranscriptEntry};
let mut seen: Vec<ExamineTarget> = Vec::new();
let mut first = true;
loop {
let choices = self.available_choices(state);
let next = choices.iter().position(|choice| {
if let ChoiceAction::Examine(target) = &choice.action {
!seen.contains(target)
} else {
false
}
});
match next {
Some(index) => {
if let ChoiceAction::Examine(target) = &choices[index].action {
seen.push(target.clone());
}
if !first {
state.push_transcript(TranscriptEntry::Separator);
}
first = false;
self.pick(state, index);
if state.game_over.is_some() {
break;
}
}
None => break,
}
}
}
pub(crate) fn rule_index(&self) -> &dispatch::RuleIndex {
&self.rule_index
}
}