use std::collections::BTreeSet;
use plotline::{Answer, Has, Library, Requirement, Runner, SequenceRef, Status, Step};
#[derive(Debug)]
enum Action {
Say(&'static str, &'static str),
Choose(Vec<(&'static str, SequenceRef)>),
Shake,
Remember(Key),
Take(Item),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
enum Item {
GoldRing,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
enum Key {
MetTheElder,
CarriesRing,
Night,
}
struct World {
facts: BTreeSet<Key>,
pack: BTreeSet<Item>,
hour: u32,
}
impl Has<Key> for World {
fn has(&self, key: &Key) -> bool {
match key {
Key::CarriesRing => self.pack.contains(&Item::GoldRing),
Key::Night => self.hour >= 20 || self.hour < 6,
Key::MetTheElder => self.facts.contains(key),
}
}
}
type Script = Library<Action, Key>;
fn say(who: &'static str, line: &'static str) -> Step<Action, Key> {
Step::act(Action::Say(who, line))
}
fn script() -> Script {
let mut library = Script::new();
library.insert(
"hub",
[
Step::when(
Requirement::not(Requirement::has(Key::MetTheElder)),
Step::call("introduction"),
),
say("Elder", "Have you found my ring?"),
Step::when(Requirement::has(Key::CarriesRing), Step::goto("returned")),
Step::act(Action::Choose(vec![
("Where did you lose it?", "hint".into()),
("Not yet.", "later".into()),
])),
],
);
library.insert(
"introduction",
[
say("Elder", "Welcome, traveler. I keep this village."),
Step::act(Action::Remember(Key::MetTheElder)),
],
);
library.insert(
"hint",
[
say("Elder", "Near the old well."),
Step::when(
Requirement::has(Key::Night),
say("Elder", "Look for it by moonlight."),
),
Step::goto("hub"),
],
);
library.insert("later", [say("Elder", "Come back when you have.")]);
library.insert(
"returned",
[
Step::act(Action::Shake),
Step::act(Action::Take(Item::GoldRing)),
say("Elder", "You have my thanks."),
],
);
library
}
fn refs_in(action: &Action) -> Vec<SequenceRef> {
match action {
Action::Choose(replies) => replies.iter().map(|(_, target)| target.clone()).collect(),
_ => Vec::new(),
}
}
fn main() {
let library = script();
for warning in library.validate(refs_in) {
println!("warning: {warning}");
}
let mut world = World {
facts: BTreeSet::new(),
pack: BTreeSet::new(),
hour: 22,
};
println!("== First visit ==");
talk(&library, &mut world, &mut [0, 1].into_iter());
println!("\n== After the search ==");
world.pack.insert(Item::GoldRing);
talk(&library, &mut world, &mut std::iter::empty());
println!("pack: {:?}", world.pack);
}
fn talk(library: &Script, world: &mut World, picks: &mut impl Iterator<Item = usize>) {
let mut runner = Runner::default();
runner.start("hub").expect("no other chain runs");
let mut status = runner.advance(library, world);
loop {
let answer = match status {
Status::Act(action) => perform(action, world, picks),
Status::Finished => return,
Status::Aborted(why) => {
println!("[the conversation broke off: {why}]");
return;
}
Status::Waiting | Status::Idle => unreachable!("every action gets an answer at once"),
};
status = runner.resume(answer, library, world);
}
}
fn perform(action: &Action, world: &mut World, picks: &mut impl Iterator<Item = usize>) -> Answer {
match action {
Action::Say(who, line) => println!("{who}: {line}"),
Action::Choose(replies) => {
let (reply, target) = &replies[picks.next().unwrap_or(0)];
println!("> {reply}");
return Answer::Goto(Some(target.clone()));
}
Action::Shake => println!("[the ground trembles]"),
Action::Remember(key) => {
world.facts.insert(*key);
}
Action::Take(item) => {
world.pack.remove(item);
println!("[you hand over the {item:?}]");
}
}
Answer::Done
}