Allow me to present Bidirectional Connections, a 25-line long extension containing a full seven lines of actual code (or two if you remove some line breaks).
I was initially going to post this on April 1st, since the extension seems almost too small to release, but I wanted to make sure it was fully tested. Now, instead of specifying (from #field go #north to #barn) and (from #barn go #south to #field), you only need to type (#barn is #north of #field)!
Here’s the extension:
(extension version) Bidirectional Connections version 1 by Melisande.
%% The purpose of this extension is to simplify the declaration of regular, symmetrical room connections.
%% With it, you no longer have to state (from #kitchen go #east to #pantry) (from #pantry go #west to #kitchen). You can now include a single line of code: (#pantry is #east of #kitchen).
%% Both ($Room1 is $Dir of $Room2) and ($Room1 is $Dir from $Room2) are valid syntax. When stating connections between rooms, all three parameters must be fully bound. This syntax should only be used to establish connections, not to query them!
%% If you want to use a query to find connections between rooms, always use the (from $ go $ to $) syntax. This will return all of the appropriate connections. If you try to use the bidirectional connection syntax in a query, it will likely miss some.
%% Warning: if you use this with custom directions (like ship directions), be sure to state opposite pairs (e. g. "(opposite of #port is #starboard)(opposite of #starboard is #port)").
%% For the cardinal and ordinal directions, this is a more idiomatic way of phrasing it.
@($Room1 is $Dir of $Room2)
($Room1 is $Dir from $Room2)
%% Since the standard library checks to see if there is a matching rule head, (from $ go $ to $) just needs to evaluate true or false correctly.
%% Because the syntax for a bidirectional connection only explicitly mentions a one-way connection, the query also needs to succeed if the reverse of the established connection is true.
%% When checking for the reverse, it first finds all of the connections matching the bound parameters then finds the reverse of each connection, because this method doesn't require $Dir to be bound. If it instead tried to find the reverse of the direction then check for connections, $Dir would need to be bound and pathfinding wouldn't work.
(from $Room1 go $Dir to $Room2)
*($Room2 is $Dir from $Room1)
(or)
*($Room1 is $OppositeDir from $Room2)
(opposite of $OppositeDir is $Dir)
The only other thing I’m curious about is optimization. But until we have a proper profiler, it’s hard to say what impact this will have on it. (I just know (from $ go $ to $) was very carefully named so that it’s always queried with a bound first parameter, because that makes for the cleanest lookups.)
So I might be tempted to try something like…
@($X is $Dir from $Y)
(from $Y go $Dir to $X)
(from $X go $Dir to $Y reversed)
(from $X go $Dir to $Y)
*(from $X go $Opposite to $Y reversed)
(opposite of $Opposite is $Dir)
In other words, transforming it at compile-time into two predicates that follow the library’s argument structure. But premature optimization is the root of all evil, and I don’t understand the predicate optimizer well enough to say whether this would actually make a difference.
(extension version) Bidirectional Connections version 2 by Melisande.
%% Thanks to Daniel Stelzer for showing me how to optimize this extension by using access predicates.
%% The purpose of this extension is to simplify the declaration of regular, symmetrical room connections.
%% With it, you no longer have to state (from #kitchen go #east to #pantry) (from #pantry go #west to #kitchen). You can now include a single line of code: (#pantry is #east of #kitchen).
%% Both ($Room1 is $Dir of $Room2) and ($Room1 is $Dir from $Room2) are valid syntax. When stating connections between rooms, all three parameters must be fully bound. This syntax should only be used to establish connections, not to query them!
%% If you want to use a query to find connections between rooms, always use the (from $ go $ to $) syntax. This will return all of the appropriate connections. If you try to use the bidirectional connection syntax in a query, it will likely miss some.
%% For two rooms connected by a door, the syntax is similar: ($Room1 is $Dir through $Door of $Room2) or ($Room1 is $Dir through $Door from $Room2).
%% Warning: if you use this with custom directions (like ship directions), be sure to state opposite pairs (e. g. "(opposite of #port is #starboard)(opposite of #starboard is #port)").
%% For the cardinal and ordinal directions, this is a more idiomatic way of phrasing it.
@($Room1 is $Dir of $Room2)
($Room1 is $Dir from $Room2)
%% Since the standard library checks to see if there is a matching rule head, (from $ go $ to $) just needs to evaluate true or false correctly.
%% Because the syntax for a bidirectional connection only explicitly mentions a one-way connection, the query also needs to succeed if the reverse of the established connection is true (hence the addition of "(from $Room2 go $Dir to $Room1 reversed)").
%% When checking for the reverse, it first finds all of the connections matching the bound parameters then finds the reverse of each connection, because this method doesn't require $Dir to be bound. If it instead tried to find the reverse of the direction then check for connections, $Dir would need to be bound and pathfinding wouldn't work.
@($Room2 is $Dir from $Room1)
(from $Room1 go $Dir to $Room2)
(from $Room2 go $Dir to $Room1 reversed)
(from $Room1 go $Dir to $Room2)
*(from $Room1 go $OppositeDir to $Room2 reversed)
(opposite of $OppositeDir is $Dir)
@($Room1 is $Dir through $Door of $Room2)
($Room1 is $Dir through $Door from $Room2)
@($Room2 is $Dir through $Door from $Room1)
(from $Room1 go $Dir through $Door to $Room2)
(from $Room2 go $Dir through $Door to $Room1 reversed)
(from $Room1 go $Dir through $Door to $Room2)
*(from $Room1 go $OppositeDir through $Door to $Room2 reversed)
(opposite of $OppositeDir is $Dir)
Thank you @Draconis for the optimized version of the code. I tend to forget that Dialog is designed to run on retro hardware.
The extension does generate two apparently spurious compiler warnings, but it works correctly.
Warning: bidirectional_connections_v2.dg, line 40: Possible typo: Local variable $Room2 only appears once.
Warning: bidirectional_connections_v2.dg, line 40: Possible typo: Local variable $Dir only appears once.