Now, Dialog is at heart a parser system, so it’s hard to actually avoid the parser entirely. Links just insert words into the parser, rather than bypassing it completely, and the player can always type in their own commands if they want to. So we’re going to build an extremely basic parser.
(understand [go to | $Words] as [go $Loc])
*(match $Words as room $Loc)
(understand [search | $Words] as [search $Loc])
*(match $Words as room $Loc)
(understand [question | $Words] as [question $Who])
*(match $Words as person $Who)
(understand [accuse | $Words] as [accuse $Who])
*(match $Words as person $Who)
(understand [wait] as [wait])
Five actions should be plenty. We’re going to take whatever words the player types and parse them into a list: the first element is the action, the second element is the object it’s acting on.
To get those objects:
(match $Words as room $Loc)
(determine object $Loc)
*(room $Loc)
(from words)
(name $Loc)
(matching all of $Words)
(match $Words as person $Who)
(determine object $Who)
*(person $Who)
(from words)
*(dict $Who)
(matching all of $Words)
Notably, this doesn’t do any scope checking: you can search any room you want, and question any person you want, no matter where you are. That’s not ideal. So we’ll have to handle that in the implementation of the actions.
(perform [go (current location $Loc)])
But you're already at (name $Loc).
(perform [go $New])
(current location $Old)
You make your way from (name $Old) to (name $New). (par)
(now) (current location $New)
(describe new location)
Rules are tried in the same order they appear in the source. So if we put our more specific rule first, that one will be tried first; the second rule will only be tried if the first one fails.
We’ll take plenty of advantage of this for our action rules.
(perform [search ~(current location $Loc)])
But you're not at (name $Loc).
(perform [search (crime scene $Loc)])
Your search uncovers some conclusive evidence: a murder took place in (name $Loc)!
(perform [search $])
No crimes seem to have happened here.
First, check if it’s not the current location; then, check if it’s the crime scene; finally, give the default response.
(perform [question $Suspect])
(current location $Loc)
~($Suspect is in $Loc)
But (name $Suspect) isn't here.
(perform [question (murderer $Suspect)])
As you question (name $Suspect) you notice a slight nervous twitch. No doubt about it, you've found your murderer!
(perform [question #peach])
"I do declare! Bless your heart, you really thought I could do such a wicked thing?"
(perform [question #brown])
She shakes her head. "May the Lord forgive you for such an insulting question."
(perform [question #gray])
"Harrumph! An officer of the law commit murder? Unthinkable! Absolutely absurd."
Same thing here, but with a different default response for each person.
(perform [accuse $Suspect])
(current location $Loc)
~($Suspect is in $Loc)
But (name $Suspect) isn't here.
(perform [accuse $Suspect])
(current location $Loc)
(murderer $Suspect)
(crime scene $Loc)
"I knew it! It was (name $Suspect), in (name $Loc), with the (random weapon)!" No one can fault your logic, and soon the murderer is carted away for trial. (par)
(span @bold) { \*\*\* Victory! \*\*\* }
(quit)
(perform [accuse $Suspect])
(current location $Loc)
"I knew it! It was (name $Suspect), in (name $Loc), with the (random weapon)!" But your logic was unsound. The murderer goes free. (par)
(span @bold) { \*\*\* Defeat... \*\*\* }
(quit)
Weapons don’t really feature at all in our little scenario, but let’s add them to the text for a bit of variety.
(random weapon)
(select)
waffle iron
(or)
poisoned croquembouche
(or)
spell of ancient power
(at random)
And last and certainly least:
(perform [wait])
Time passes.