Stairs with a door at top in Dialog

There was a question about implementing a door at the end of staircase in Inform 7 subforum.

Two approaches were suggested, one more simulational (by HanonO) than the other one (by DeusIrae).

I am not conversant in Inform 7 (barely know it in read-only mode), but I wanted to try my hand in implementing both suggestions in Dialog. The names of the variables and literals are stolen (without shame) from HanonO’s example code in the thread mentioned above.

Here is the idea by HanonO, where the stairs is an object that can (and must) be actually climbed on from the staircase side to interact with the door.

#laboratory
(room *)
(name *)		Laboratory
(look *)		(descr #staircase)
(from * go #up through #f-door to #forbiddenroom)

#forbiddenroom
(room *)
(name *)		Forbidden Room
(from * go #south through #f-door to #laboratory)

(narrate leaving * #south)
	You walk south through the forbidding door and climb down the staircase.

#f-door
(door *)
(lockable *)
(name *)		forbidding door
(descr *)		It's a perfectly ordinary and
				{
					(* is locked) locked
				(or)
					(* is closed) closed
				(or)
					open
				}
				door.

(refuse $Action)				%%Forbidding any interaction with the door
	(* is one of $Action)		%%if the player is not on the staircase
	(current player $Player)
	($Player is in room #laboratory)
	~($Player is #on #staircase)
	You'll need to climb the stairs first to reach that forbidding door.

#staircase
(name *)		staircase
(dict *)		steps stairs stairway
(actor supporter *)
(descr *)		An eerie staircase climbs along the wall and
				ends at a forbidding door.
(* is #in #laboratory)

(narrate climbing *)
	You climb the rickety stairs one by one...

(narrate leaving *)
	You climb down the rickety stairs...

(before [go #up])
	(current player $Player)
	($Player is #on *)
	(just)				%%Preventing other before rules, because one of them makes
	(#f-door is closed)	%%the player leave the stairs (first leaving the staircase)
	(first try [open #f-door])

(instead of [go #up])
	(current player $Player)
	($Player is #on *)
	(narrate leaving #laboratory #up)
	(enter #forbiddenroom)	%%Leaving to upstairs without getting off the staircase

(instead of [put $Obj #on *])
	(The $Obj) fall(s $Obj) down the stairs to the ground.
	(now) ($Obj is #in #laboratory)
	(now) ($Obj is handled)
								%%Items dropped on the stairs falls into laboratory
(perform [drop $Obj])			%%Not essential, just a detail
	(current player $Player)
	($Player is #on *)
	(try [put $Obj #on *])

#key
(item *)
(name *)	forbidden key
(* is handled)
(* unlocks #f-door)
(* is #in #laboratory)

#player
(current player #player)
(* is #in #laboratory)

Here is the idea by DeusIrae, where the door is a simple one between two rooms, and it looks different depending on where you stand with regards to its faces (and just a couple of action redirects):

#laboratory
(room *)
(name *)		Laboratory
(look *)		An eerie staircase climbs along the wall and
				ends at a forbidding door.
(from * go #up through #f-door to #forbiddenroom)

#forbiddenroom
(room *)
(name *)		Forbidden Room
(from * go #south through #f-door to #laboratory)

(narrate leaving * #south)
	You walk south through the forbidding door and climb down the staircase.

#f-door
(door *)
(lockable *)
(name *)		forbidding door
(descr *)		(if) (current room #laboratory) (then)	%%We vary the looks of the
					(look #laboratory)					%%door, depending on location
				(endif)
				The door is
				{
					(* is locked) locked
				(or)
					(* is closed) closed
				(or)
					open
				}
				.

#staircase
(name *)		staircase
(dict *)		steps stairs stairway
(descr *)		(descr #f-door)
(* is #in #laboratory)

(instead of [climb *]) %%Redirecting climb staircase action
	(try [go #up])

(grammar [go [object]] for [climb *])	%%Just to provide a synonym for climb action
	(current room #laboratory)			%%as well as the [go #door] action

#key
(item *)
(name *)	forbidden key
(* is handled)
(* unlocks #f-door)
(* is #in #laboratory)

#player
(current player #player)
(* is #in #laboratory)

Edit: Mistyping.
Edit:

*(* is one of $Action)	

The multi-query was unnecessary, since we already know the object we are looking for. So I changed the line to a simple query:

(* is one of $Action)	
7 Likes

Thank you for reposting the relevant bits. I went looking for it just now and realized it was gone.

ETA: Disregard, I’m a little dense and mixed up posts.

2 Likes

Very cool!

Is this right? I think it translates to (grammar [go [object]] for [climb #staircase]), and that seems like > go door or various other commands would also try and climb the staircase. But I haven’t tested it.

That’s a nice approach… I always forget one can do stuff like this. (One of the worst things in a WIP of mine, which I may now go change immediately, is making something a vehicle just so someone can exit the room without getting off it.)

4 Likes

First of all, thank you for drawing my attention to this line:

(grammar [go [object]] for [climb *])

It makes no sense to use non-all multi-object token here since we have no way to deal with it in a specific way. Single object token [single] makes much more sense.

(grammar [go [single]] for [climb *])

But it makes no real difference why the line works. This line provides understand rule for

>go staircase

and other synonyms for the staircase (and only for the staircase object) we declared here, :

#staircase
(name *)		staircase
(dict *)		steps stairs stairway

We could achieve the same effect with:

(understand [go staircase/steps/stairs/stairway] as [climb #staircase])

but we need to update the synonyms in two places if we ever want to modify them. Using the (grammar ...) ensures that the understand rules automatically tracks any changes to the (name *) and (dict *) declarations.

This grammar doesn’t match go door or go key because the rule that queries the resulting grammar entry tables ensures that the object that matches the [object] token succeeds only if it matches the object provided in the $Action:

(understand [$Verb | $Words] as $Action)
	*(grammar entry $Verb $Grammar $Action)
	*(match grammar $Grammar against $Words into $ObjList)
	(populate template $Action with $ObjList)

First two queries above will indeed match for go door and go key, as seen in this queries in the debugger output:

> *(grammar entry @go $ [climb #staircase])
Query succeeded: (grammar entry @go [20] [climb #staircase])
Query succeeded: (grammar entry @go [up [on onto] 10] [climb #staircase])
Query succeeded: (grammar entry @go [[on onto] 10] [climb #staircase])
Query succeeded: (grammar entry @go [on top of 10] [climb #staircase])

> *(match grammar [20] against [door] into $ObjList)
Query succeeded: (match grammar [20] against [door] into [#f-door])
> *(match grammar [20] against [key] into $ObjList) 
Query succeeded: (match grammar [20] against [key] into [#key])
> *(match grammar [20] against [staircase] into $ObjList)
Query succeeded: (match grammar [20] against [staircase] into [#staircase])

But the third query in there succeeds only for the staircase:

> (populate template [climb #staircase] with [#f-door])   
> (populate template [climb #staircase] with [#key]) 
> (populate template [climb #staircase] with [#staircase])
Query succeeded: (populate template [climb #staircase] with [#staircase])

Note: [20] token for [object] in the lines above would change to [10] token for [single] if we change the token in the source. The explanations above would still hold true after the change.

– Additional discourse about why go door works as intended –
The grammar table does not contain any matching rule for go door as seen in this output:

> *(grammar entry @go $Grammar $Action)
Query succeeded: (grammar entry @go [20] [climb #staircase])
Query succeeded: (grammar entry @go [up [on onto] 10] [climb $])
Query succeeded: (grammar entry @go [[on onto] 10] [climb $])
Query succeeded: (grammar entry @go [on top of 10] [climb $])
Query succeeded: (grammar entry @go [[in into inside] 10] [enter $])
Query succeeded: (grammar entry @go [[out off] of 10] [leave $])
Query succeeded: (grammar entry @go [off 10] [leave $])
Query succeeded: (grammar entry @go [50] [go $])
Query succeeded: (grammar entry @go [[to further] 50] [go $])
Query succeeded: (grammar entry @go [] [go #out])

We showed above that [20] line doesn’t match the action, and the [50] token won’t match either because door is not a direction object. All other entries require either some prepositions or plain go command with empty grammar.

But the command still works as seen here:

> *(understand [go door] as $Action)
Query succeeded: (understand [go door] as [enter #f-door])

because of another understand rule separate from the grammar table in the standard library matches it:

(understand [go | $Words] as [enter $Obj])
	*(understand $Words as single object $Obj)
	{ (door $Obj) (or) ~(room $Obj) (actor container $Obj) }

Finally lets verify the parsed actions works as intended with (actions on) in the debugger:

> climb staircase
ACTION: [climb #staircase] (get onto the staircase)
ACTION: [go #up] (go up)
(first attempting to open the forbidding door above)
ACTION: [open #f-door] (open the forbidding door above)
The forbidding door is locked.

> go staircase   
ACTION: [climb #staircase] (get onto the staircase)
ACTION: [go #up] (go up)
(first attempting to open the forbidding door above)
ACTION: [open #f-door] (open the forbidding door above)
The forbidding door is locked.

> go door     
ACTION: [enter #f-door] (get into the forbidding door above)
ACTION: [go #up] (go up)
(first attempting to open the forbidding door above)
ACTION: [open #f-door] (open the forbidding door above)
The forbidding door is locked.

Edit: Mistype.

6 Likes

Fascinating and thorough… thanks for the deep dive!

3 Likes