Struggling with moving around in the dark

I’m working on an puzzle where, if it is somewhat dark, an attempt to move around instead hints to you about an object (a desk containing a flashlight).

In Inform, it looks like:

Staging Area is either dim or bright. Staging Area is dim.

Instead of going to a room regionally in Office Interior from dim Staging Area: say “You take a step and crack your shin against what feels like a metal desk. Ouch.” Instead of going nowhere from dim Staging Area: try going north.

My version of this is:

(instead of [leave * $Direction])
    <leave * $Direction> (line)
    (lit by headlights *)
    { (from * go $Direction to $TargetRoom)
      (or) 
      ($TargetRoom = *) 
      staying put (line)
      }
    target = $TargetRoom (line)
    (office interior $TargetRoom)
    You take a step and crack your shin against what feels like a metal desk.

My intention is: if there’s a valid room connection, then $TargetRoom gets set to that room, otherwise, set $TargetRoom to * (#staging-area).

But this isn’t working for an important case: you should be able to go #out because (from * go #out to #crumbling-concrete) exists, and does not have the (office interior $) trait.

Output:

Staging Area
Faint shafts of light from your dimming headlights seep through a boarded-up pane of cracked glass, but you can barely make out anything of the interior.

> out
<leave #staging-area #out>
target = #crumbling-concrete
staying put
target = #staging-area
You take a step and crack your shin against what feels like a metal desk.

> 

So there’s some kind of multi-query that is kicking in when (office interior $TargetRoom) fails I believe, but I’m not sure where that is or what to do about it. Any ideas on this, or how to narrow it down a bit?

Ah, I love having debug and tracing and all that; I got more info, but still am in the dark:

> (instead of [leave #staging-area #out])
| | | | QUERY (instead of [leave #staging-area #out])
| | | | | ENTER (instead of [leave #staging-area #out]) src/staging-area.dg:43
| | | | | QUERY (lit by headlights #staging-area) src/staging-area.dg:44
| | | | | | ENTER (lit by headlights #staging-area) src/light.dg:30
| | | | | | QUERY (in range of headlights #staging-area) src/light.dg:32
| | | | | | FOUND (in range of headlights #staging-area) src/light.dg:32
| | | | | | QUERY (brightly lit #staging-area) src/light.dg:33
| | | | | | | ENTER (brightly lit #staging-area) src/light.dg:26
| | | | | FOUND (lit by headlights #staging-area) src/staging-area.dg:44
| | | | | QUERY (from #staging-area go #out to $) src/staging-area.dg:45
| | | | | | ENTER (from #staging-area go #out to #crumbling-concrete) src/crumbling-concrete.dg:24
| | | | | FOUND (from #staging-area go #out to #crumbling-concrete) src/staging-area.dg:45
| | | | | QUERY (office interior #crumbling-concrete) src/staging-area.dg:49
| | | | | QUERY ($ = #staging-area) src/staging-area.dg:47
| | | | | FOUND (#staging-area = #staging-area) src/staging-area.dg:47
| | | | | QUERY (office interior #staging-area) src/staging-area.dg:49
| | | | | FOUND (office interior #staging-area) src/staging-area.dg:49
You take a step and crack your shin against what feels like a metal desk.
| | | | FOUND (instead of [leave #staging-area #out])
Query succeeded: (instead of [leave #staging-area #out])
> 

These two lines:

| | | | | QUERY (office interior #crumbling-concrete) src/staging-area.dg:49
| | | | | QUERY ($ = #staging-area) src/staging-area.dg:47

seem to be a the heart of the issue; still don’t see the solution though.

1 Like

With this code:

    { (from * go $Direction to $TargetRoom)
      (or) 
      ($TargetRoom = *) 
      staying put (line)
      }

we are setting up a choice point. We’ll try the first branch first, and keep the second branch around in case things fail. The choice point remains in effect until the end of the rule—or even longer if a multi-query is being made.

So in this case, (from * go $Direction to $TargetRoom) succeeds as (from #staging-area go #out to #crumbling-concrete), but the choice point is still active. Then, further down in the code, (office interior $TargetRoom) fails. This failure makes the system backtrack to the choice point, and try the other branch. Now $TargetRoom becomes #staging-area, and (office interior $TargetRoom) succeeds.

To prevent this backtracking, we have to avoid leaving a choice point around. One way to do that is to move the block with the disjunction into a separate predicate:

(from $A go $Dir to $B or stay)
        (from $A go $Dir to $B)
        (or)
        ($A = $B)

By making a regular query (not a multi-query) to this predicate, we can be certain that it only returns once, with a single result.

Another option is to put the disjunction inside the condition of an if-statement. If-conditions are only evaluated once. It doesn’t look pretty in this case, but the technique is generally useful:

(instead of [leave * $Direction])
    <leave * $Direction> (line)
    (lit by headlights *)
    (if)
        (from * go $Direction to $TargetRoom)
        (or)
        ($TargetRoom = *)
        staying put (line)
    (then)
        target = $TargetRoom (line)
        (office interior $TargetRoom)
        You take a step and crack your shin against what feels like a metal desk.
    (endif)

In this particular case, I think I would prefer to rephrase the condition as “if it’s not the case that we are leaving the office”, like this:

(instead of [leave * $Direction])
    <leave * $Direction> (line)
    (lit by headlights *)
    ~{
        (from * go $Direction to $TargetRoom)
        ~(office interior $TargetRoom)
    }
    You take a step and crack your shin against what feels like a metal desk.

As a final nitpick, I would suggest (from $ go $ to room $) which will handle doors transparently, and I would put the code in a prevent-rule:

(prevent [leave * $Direction])
    (lit by headlights *)
    ~{
        (from * go $Direction to room $TargetRoom)
        ~(office interior $TargetRoom)
    }
    You take a step and crack your shin against what feels like a metal desk.
1 Like

Thanks!

I think the biggest challenge with Dialog/Prolog is learning the mindset of checkpoints; the idea that functions may “return” values multiple times even though invoked only once, etc.

If it isn’t obvious, I’m working through https://www.amazon.com/Creating-Interactive-Fiction-Inform-7/dp/1435455061?ref_=fsclp_pl_dp_3 converting it to Dialog, which is giving me a lot of insight into how both systems work. And as cool as Inform is, I’ve never fully cracked that nut, but Dialog seems like something I could actually master.