Linking rooms?

Generally, room links are symettric (east from foyer to library generally means west from library to foyer).

I attempted the following:

@(link $R1 $Direction to $R2)
   (from $R1 go $Direction to $R2)
   (opposite of $Direction is $Return)
   (from $R2 go $Return to $R1)

#middle-of-nowhere
(room *)
(name *) Middle of Nowhere
(#knock is #in *)
(link * #south to #backtracking)

#backtracking
(room *)
(name *) Backtracking

… but it seems to get confused:

Middle of Nowhere
You are here.

> exits
Obvious exits are:
South.

> s
You walk south.

Backtracking
You are here.

> exits
Obvious exits are:
North to the Middle of Nowhere.

> s
You walk south.

Middle of Nowhere
You are here.

> s
You walk south.

Backtracking
You are here.

> e
You walk east.

Middle of Nowhere
You are here.

> e
There doesn't appear to be an exit in that direction.

> w
There doesn't appear to be an exit in that direction.

> s
You walk south.

Backtracking
You are here.

> w
You walk west.

Middle of Nowhere
You are here.

> 

I suspect this is an issue with (the Dialog equivalent of) macro expansion, and (opposite of $Direction is $Return) is just not going to work in an @ rule; but I’m curious if there is a way to make this work?

1 Like
%% Use (link $Room1 $Direction to $Room2) to supply
%% (from ...) predicates in both directions.

(from $R1 go $Direction to $R2)
    (link $R1 $Direction to $R2) (or)
    { 
        (opposite of $Direction is $Return)
        (link $R2 $Return to $R1)
    }

seems to work; I’m not too concerned with runtime costs, though this feels somewhat expensive.

I think your suspicion is correct. The access predicate expands to three independent rules which do not share parameters:

#middle-of-nowhere
(from * go #south to #backtracking)
(opposite of #south is $)
(from #backtracking go $ to *)

The last rule says that you can go in any direction to get from #backtracking to #middle-of-nowhere, which matches your output. (And the second rule states that everything is the opposite of #south, which is probably also unintended.)

Alas, this only works to a point. The exits command doesn’t seem to understand the links.

Are you sure? It seems to work for me, which makes sense since exits just makes a multi-query to *(from $Room go $Dir to $Target).

Ok, the version above is broken but this seems to work:

(from $R1 go $Direction to $R2)
    *(link $R1 $Direction to $R2) 
    (or)
    *(link $R2 $Return to $R1)
    (opposite of $Direction is $Return)

I’m still getting the hang of using * to set up choice points.

1 Like

The following should also work:

(from $R1 go $Direction to $R2)
    (link $R1 $Direction to $R2) (or)
    { 
        *(opposite of $Direction is $Return)
        (link $R2 $Return to $R1)
    }

The crux is that (from $R1 go $Direction to $R2) gets queried with an unbound $Direction by EXITS (and by path-finding). The inner query (opposite of $ is $) is therefore made with both parameters unbound. If this is limited to a single match, you will just obtain the first pair of opposite directions from the library, which is (opposite of #north is #south).

As for performance, both versions are problematic because they query (link $ $ to $) with an unbound first parameter (and a bound third parameter). This will cause a loop through every link-rule, until a match is found. The execution time increases linearly with the number of link-rules in the game, so it can get slow for games with lots and lots of rooms.

Dialog is optimized for dispatching on the first parameter. In database terms, an index is automatically created for the first parameter (where applicable). When you query (link $ $ to $)—or anything else—with a bound first parameter, the execution time only increases logarithmically with the number of link-rules, which is quite fast even for a sprawling map.

Getting back to the original question, I would propose something like this:

@(place $R1 north of $R2)
    (from $R2 go #north to $R1)
    (from $R1 go #south to $R2)

@(place $R1 northwest of $R2)
    (from $R2 go #northwest to $R1)
    (from $R1 go #southeast to $R2)

...

And probably also:

@(place $R1 north of $R2 through $Door)
    (from $R2 go #north through $Door to $R1)
    (from $R1 go #south through $Door to $R2)

...

That’s a total of 24 new access predicates. I’m torn about whether to include this in the standard library. The upside is that it makes the story code shorter, and reduces the risk of missing or buggy exits. But the downside is that it makes the story code less transparent to people who are still learning about Dialog. I’ll have to think more about it.

For some time, I’ve been toying with the idea of adding a language feature where access predicates would be subjected to pattern matching at compile-time. That would allow:

@(place $R1 #north of $R2)
    (from $R2 go #north to $R1)
    (from $R1 go #south to $R2)

@(place $R1 #northwest of $R2)
    (from $R2 go #northwest to $R1)
    (from $R1 go #southeast to $R2)

...

Again, this could make the language easier to write in, but potentially harder to read.

Here is another approach:

@(link $R1 $Direction to $R2)
        (from $R1 go $Direction to $R2)
        (from $R2 go opposite of $Direction to $R1)

(from $R1 go $Direction to $R2)
        (if) (bound $Direction) (then)
                (opposite of $Direction is $OppDir)
                (from $R1 go opposite of $OppDir to $R2)
        (else)
                (from $R1 go opposite of $OppDir to $R2)
                (opposite of $OppDir is $Direction)
        (endif)
1 Like