Conversation and Orders

Taking a tiny break to document process, because the last few days have been an awful lot like this.

The following exchange represents a lot of fiddling around, but I’m now reasonably happy with it (spoilers for the early turns of Alabaster inevitable):

>instruct snow white to wipe her lips
“Wipe your lips,” you suggest.

She looks at you incredulously: there is of course no way she could do this with her hands tied behind her back.

“Untie me,” she says. “It will make everything easier for both of us.”

In the depressing way of these things, this looks completely ordinary to the outside view. Under the surface, here is what the code has to do:

1) recognize a command and convert it to >SNOW WHITE, WIPE HER LIPS so that the Inform parser will be able to read the command.

2) recognize that in context, “her” should be converted to “Snow White’s”, referring to her subsidiary body part, so that the parser will be able to understand the action.

3) now that the parser understands what the player’s command means, further process the player’s command into text suitable to be presented as the player character’s direct dialogue.

In this case, that means converting “Snow White’s” into “your”, but there are other special cases. At this point if the player had given a command with an abbreviation (like X) it would have been expanded to “examine”, and so on, but otherwise if possible the parser preserves the phrasing that the player used. So if the player had typed “look at”, the game would parrot “look at” rather than substituting “examine”.

4) choose a command format based on the current mood of the conversation, and expand the player’s dialogue accordingly (depending on mood, this line could have come out to anything from ‘”Please wipe your lips,” you beg.’ to brusque orders).

5) have Snow White recognize that the command in question involves touching something she can’t reach. This generates the “incredulously” line (one of several randomized options for this situation).

6) then, because this is the first time in the game you’ve told her to try an impossible touching action (and because she hasn’t already used up the “untie me” plea), queue and fire that request.

Next move:

>untie her
You consider it — and then you consider your exsanguinated hounds.

The rumors might be false. You’ve always tried to be a rational man. The Queen’s taste for witchcraft disturbs you very much.

Nonetheless, the dogs were dead, all three. Bloodless.

From the expression on her face, she was able to read most of your thoughts just now.

Thinking about asking her about whether she can read your thoughts is almost like asking her. And thinking about thinking about asking… No, stop.

Here there’s less to do: a simple action processing for “freeing Snow White” handles everything down through “Bloodless.”

Then because the game knows that Snow White was just asking you to untie her, it queues up the “From the expression…” quip, in order to give that aborted action context within the conversation.

That in turn makes it plausible that the player will ask Snow White about whether she can read thoughts, so the game generates a contextual hint prompting such a question.

Move three:

>ask her about whether she can read my thoughts
“I don’t like that look,” you say. “In fact I can’t help but feel like you just took a glance into my mind, which may I remind you belongs to me.”

“There are some things I cannot help,” she says. “Call it intuition. Besides, you are hardly in a position to take a moral stance on privacy.” Snow White’s chin gestures toward her chained wrists.

All around you is silence. It might be just a trick of the light, but it almost looks as though the hart moved.

This time we process a request to speak the quip that was just hinted at, and print the player’s comment and her response.

Then, the game notices that the conversation is at an effective dead end — there’s nothing else interesting that the player can say about thought-reading right now, so it will end the turn by moving the conversational context forward.

“All around you is silence” is a beat produced to indicate the passage of a short period of time and to remind the player of the surroundings. (There are quite a lot of different beat texts, which vary with conversation mood and context.)

“It might be just…”, on the other hand, is a text that has been queued up waiting to happen whenever the conversation came to a reasonable pause. It became available several turns earlier — two turns before the “wipe your lips” turn — but because other interesting conversation was happening, it hasn’t triggered until now.

I think my favorite thing about this project is that when this works (which is certainly not all the time), the game sometimes patches together fragments of text into paragraphs that feel not only deliberate, but meaningful in a way that neither I nor any of the other collaborators specifically envisioned. The juxtaposition of her response to your comment with a new bit of conversation she’s been waiting to speak is often especially fruitful.

That’s when the game’s not producing drivel, of course. And run-time errors. Speaking of which, the salt mines await.


A technical footnote:

Irene Callaci wrote for Inform 6 a sweet bit of hackery I always admired, and several times used. It was to convert player orders of the form

>TELL SNOW WHITE TO SING
>ASK THE HART TO WAVE
>ORDER SNOW WHITE TO LOOK AT THE BOX

into

>SNOW WHITE, SING
>HART, WAVE
>SNOW WHITE, LOOK AT THE BOX

Here is what it looked like:


[ BeforeParsing i j k w skip inc;
i = 0; j = 0; k = 0; w = 0; skip = 0; inc = 0;
! Find verb_wordnum (usually 1, but not when the command is NPC, VERB THE )
for (i = 2 : i = num_words) wn = 1;
for (i = parse->2, j = 2 : j < i : j++)
{ w = NextWord();
switch (w)
{ 'ask', 'tell':
! First, count any blank spaces at the beginning of input
for (i = 2 : i 1 : i++)
{ if (buffer->i == ' ') skip++;
else break;
}
! Next, count the number of letters in the first word (ASK or TELL)
skip = skip + WordLength(wn - 1) + 1;
! Now get the next word. If it's an article, count the number of
! letters it contains so we can overwrite it later. If we don't do
! this, we end up with invalid input like: THE NPC, VERB
w = NextWord();
if (w == 'a//' or 'an' or 'the')
{ skip = skip + WordLength(wn - 1) + 1;
w = NextWord();
}
! Now we've reached a likely spot for the NPC's name. Because NPCs can
! have more than one name (CHARLIE SMITH, for example), we loop until
! we find the word "to" or some other word that signals the start of
! the actual command.
while (w ~= 0)
{ ! We need to know how long the NPC name is so that we can skip over
! it later without overwriting it.
inc = inc + WordLength(wn - 1) + 1;
w = NextWord();
switch (w)
{ 'about':
! Stop reparsing here to avoid problems with ASK COP ABOUT WITNESSES TO THE ACCIDENT (otherwise, the 'to' below will trigger an NPC order)
rfalse;
'to':
! Find the word "to" in the input and replace it with a
! comma followed by a blank (to erase the "o").
for (i = skip + inc + 1 : i 1) + 1 : i++)
{ if (buffer->i == ' ' && buffer->(i+1) == 84 or 116 && buffer->(i+2) == 79 or 111 && buffer->(i+3) == ' ')
{ buffer->(i+1) = 44; ! ascii code for comma
buffer->(i+2) = ' ';
break;
}
}
! Move the NPC's name and everything following it to the
! left, overwriting as we go.
for (i = 2 : i 1) + 1 : i++)
buffer->i = buffer->(i + skip);
! Truncate the command and retokenise the input.
buffer->1 = (buffer->1) - skip;
Tokenise__(buffer, parse);
}
}
default:
rfalse;
}
break;
}
];

Here is what the same function looks like in I7:

After reading a command:
let N be indexed text;
let N be the player's command;
replace the regular expression "(ask|tell|order) (.+) to (.+)" in N with "\2, \3";
change the text of the player's command to N.

One thought on “Conversation and Orders”

Leave a comment