Referring to objects out of scope

I’m rewriting my EctoComp entry, Super Halloween Horror Show, in Dialog, partly as a learning exercise and partly because, as a limited-parser game, I think it would work really well with the kind of hybrid interface that Dialog excels at. Most of the translation has been pretty straightforward, but I’ve run into an issue that I can’t figure out how to debug.

The majority of interaction in SHHS involves showing things to NPCs, most often your emotions (which appear as objects in your inventory). Because your emotions come and go over the course of play, it’s pretty common for the player to try to show an emotion that they no longer have, and I wanted this to give a more illuminating message than “you can’t see any such thing.”

In Inform 7, I accomplished this as follows:

Understand "show [any emotion] to [someone]" as showing it to.
The access through barriers rule response (A) is "You aren't feeling that emotion right now.".

The key being the [any emotion] token, which means the game recognises the name of an emotion as the noun for SHOW whether or not it’s in scope.

Following the example in the Dialog manual, I have tried to get the same effect like this:

(grammar [show [emotion] to [animate]] for [show $ to $])
(grammar [show [animate] [emotion]] for [show $ to $] reversed)

(grammar [show [emotion]] for [show $])

@(grammar transformer [[emotion] | $Tail] $SoFar $Verb $Action $Rev)
    (grammar transformer $Tail [90 | $SoFar] $Verb $Action $Rev)

(match grammar token 90 against $Words $ into $Obj)
    *(understand $Words as any object $Obj)
    (emotion $Obj)

(before [show $Emotion to $])
    ~($Emotion is #heldby #player)
    You aren't feeling that emotion right now.

(This is in my main .dg file, included before the library. (emotion $) is a predicate which applies to all of the emotion objects. They’re not (item $), in case that’s relevant.)

Unfortunately, it doesn’t seem to work, since when I try to SHOW an NPC an emotion I’m not carrying, I get the generic “I only understood you as far as wanting to show something to the NPC” response, the same as if none of the above code were included. I don’t know how to dig any deeper into the workings of the parser here to determine why the code above isn’t working as it should. Any help?

I think this is a place where it’s easiest to bypass the (grammar $ for $) system entirely and use the lower-level (understand $ as $) predicate.

(understand [show | $Words] as [invalid-show $Obj])
    *(split $Words by [to] into $Left and $) %% We don't care what's on the right
    (filter $Left into $Filtered) %% Remove "the", "a", etc
    (nonempty $Filtered) %% Don't allow an empty noun to match everything
    (determine object $Obj)
        *(emotion $Obj)
    (from words)
        *(dict $Obj)
    (matching all of $Filtered)
(unlikely [invalid-show $])

This is the equivalent of Inform 7:

Understand "show [any emotion] to [text]" as invalid-showing.
Does the player mean invalid-showing: it is unlikely.

Basically, since Dialog doesn’t have an [any emotion] token built in, you can either add it to the grammar transformer, or just implement it yourself in an (understand $ as $) rule. If you only need it once, the latter is easier. The standard library actually does this for recognizing visited rooms: the only action that takes a room is [go to $], so instead of adding a [any room] token, [go to $] is implemented this way.

(understand [go to | $Words] as [go to $Room])
	*(understand $Words as room name $Room)

(understand [go/approach | $Words] as [go to $Room])
	*(understand $Words as room name $Room)

(understand $Words as room name $Room)
	(filter $Words into $Filtered)
	(nonempty $Filtered)
	(determine object $Room)
		*(room $Room)
		($Room is visited)
	(from words)
		*(dict $Room)
	(matching all of $Filtered)
1 Like

what if you add:

~(refuse [show $Emotion to $])
     (emotion $Emotion)

to what you’ve already done?

i’ve never had much success manipulating the parser as outlined in the manual. it’s very frustrating. i think a lot of things have changed re: the parser but the manual has not.

1 Like

Of course, if you want to tie this to your normal showing action, you can also parse the right half of the command with *(understand $Right as single object $Target) and make it be understood as [show $Obj to $Target]. Then you can check for invalid ones in a (prevent $) rule. Lots of versatility here!

Yeah, rewriting that chapter is on my todo list…

1 Like

I tried using this code exactly as you wrote it and I still get “I only understood you as far as …” - is there any way I can get some debug output from the parser to pinpoint what isn’t working?

The usual way is (trace on) (in the debugger), though it’ll produce a lot of output.

1 Like

i’ve had luck before recycling the PURLOIN command in stddebug.dg (since everything is in scope to purloin).

(understand [purloin | $Words] as [purloin $Obj])
	(determine object $Obj)
		*(item $Obj)
	(from words)
		*(dict $Obj)
	(matching all of $Words)

~(refuse [purloin $])

you could use this to re-write SHOW in stdlib, putting in some guardrails so that only items in scope or emotions get through.

1 Like

Ah, I knew there was something like (trace on) but I couldn’t remember how to enable it!

Poring over the traces has revealed the root cause of the problem with my original approach: (understand $Words as any object $Obj) does not, as the name implies, match any object; rather, it only matches objects located in visited rooms. Writing my own version of it which omits this check gets me to the point where I get an explicit refusal: “your (emotion) isn’t here” - which still slightly confuses me as I thought my own refuse rule should take precedence, but I can probably figure it out from here.

The trick with custom (refuse $) rules is that, unless they use (just), they can’t prevent the normal rules from running. I’m working on a way to change that for the next release.

1 Like