Dialog

Thanks for the bug fix, the compile time is much faster around a second or two.

1 Like

Dialog 0f/03 fixes a bug in the Z-machine backend, related to long dictionary words. I discovered the bug when reimplementing the Uptown Girls example; queries would fail to match the dictionary word @medium-height in the passerby description table.

Here is my Uptown Girls variant. The main difference is that the characteristics of the passerby are modelled using values rather than predicates. When using values in this way, one can use dictionary words, objects, integers, or even a mixture. Integers would make the code hard to understand, but both objects and dictionary words are viable options. Objects use a bit more memory, while dictionary words have the side effect of showing up in the game dictionary. In this case, we want to be able to recognize those words in player input anyway, so dictionary words are the best choice.

Uptown Girls, alternative Dialog implementation
(story title)	Uptown Girls
(story author)	Linus Åkesson
(story noun)	A port of the Inform 7 example 132, of the same name,

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%  
%% Original implementation:
%% http://inform7.com/learn/man/RB_3_9.html#e169
%%      'A stream of random pedestrians who go by the player.'
%% ElliotM's Dialog conversion:
%% https://intfiction.org/t/dialog/13922/117
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%  

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

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%  
%% Convenience predicate
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%  

(randomly select $Obj from $List)
	(length of $List into $N)
	(random from 1 to $N into $X)
	(nth $List $X $Obj)

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%  
%% Passerby NPC
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%  

%% Note: Storing properties such as hair colour inside the NPC object makes
%% sense from an object-oriented point of view. But in this story, there is
%% only one passerby, so it would also be possible to track her characteristics
%% using global variables, e.g.:

%% (global variable (height of passerby is $))

%% Sometimes that leads to more readable code, and sometimes it doesn't. It's
%% mostly a matter of taste. Global variables do not use as much memory as
%% per-object variables, which affects performance on vintage computers. I will
%% use per-object variables in this example, because it is closer to the
%% original Inform 7 code.

#npc
(* is #in #riversidedrive)
(female *)

(reset *)
	(randomly select $Hair from [ginger brunette blonde])
	(randomly select $Height from [tall medium-height short])
	(randomly select $Grooming from [messy tidy])
	(now) (* has hair $Hair)
	(now) (* has height $Height)
	(now) (* has grooming $Grooming)

(noun *)
	%% Variety beats consistency here, IMHO:
	(select) woman (or) lady (purely at random)

%% Predicates for printing each characteristic. The default is to simply print
%% the word itself.

(hair *)
	(* has hair $Hair)
	$Hair

(height *)
	(* has height $Height)
	$Height

(grooming *)
	%% Let's try something a bit more elaborate:
	(if) (* has grooming @messy) (then)
		%% A varying description:
		(select) messy (or) disheveled (purely at random)
	(else)
		%% A two-word description:
		well groomed
	(endif)

(dict *)
	passerby woman lady (hair *) (height *) (grooming *)
	(if) (* has grooming @messy) (then)
		%% Might as well include both synonyms:
		messy disheveled
	(else)
		%% We already have 'well groomed' from '(grooming *)'.
		tidy
	(endif)

(name *)
	%% 1 in 4 chance to mention height, grooming, or hair before the noun
	(if) (random from 1 to 4 into 1) (then) (height *) (endif)
	(if) (random from 1 to 4 into 1) (then) (grooming *) (endif)
	(if) (random from 1 to 4 into 1) (then) (hair *) (endif)
	(noun *)

(descr *)
	(* has hair $Hair)
	(* has height $Height)
	(* has grooming $Grooming)
	(passerby table $Hair $Height $Grooming)

%% Table look-up can often be implemented using predicates that take multiple
%% arguments:

(passerby table @ginger @tall @messy)
	An older woman with long red hippie-hair poking out of a ponytail in
	straggles, and bent to hide how tall she is.
(passerby table @ginger @medium-height @messy)
	A shaggy red-head with shingled hair.
(passerby table @ginger @short @messy)
	Almost an urchin, and very young, with ginger hair and a smudged nose
	and far too many freckles.
(passerby table @ginger @tall @tidy)
	A precise career woman with henna-red hair.
(passerby table @ginger @medium-height @tidy)
	Her hair is red in the way that lollipops and fire trucks are red: not
	by nature but by art. The rest of her clothing is pretty ordinary,
	though.
(passerby table @ginger @short @tidy)
	Thin and small in every sense, with chin-length red-hair. Even high
	heels do not bring her head much above your shoulder.
(passerby table @brunette @tall @messy)
	A Juno-esque woman with dark hair, wearing something resembling a tent.
(passerby table @brunette @medium-height @messy)
	An unremarkable woman with dark brown hair and the aura of needing a
	wash.
(passerby table @brunette @short @messy)
	There are mustard stains on the t-shirt of this short brown-haired
	woman. Estimated age ca. 40. Possibly homeless.
(passerby table @brunette @tall @tidy)
	A leggy brunette in business attire.
(passerby table @brunette @medium-height @tidy)
	Medium-height, brown-haired, generally nondescript.
(passerby table @brunette @short @tidy)
	A neat little dark-haired girl.
(passerby table @blonde @tall @messy)
	A tall blonde of about thirteen who looks as though she has not yet
	figured out how to get her wardrobe to catch up with her rate of
	growth. Her t-shirt and her pants are too short.
(passerby table @blonde @medium-height @messy)
	Black leather pants and the wall-o-hair look.
(passerby table @blonde @short @messy)
	One of those shocking platinum blonde types, with a tiger-patterned
	skirt. Reeally trashy.
(passerby table @blonde @tall @tidy)
	Elfin and severe, with perfectly straight hair falling to the middle of
	the back.
(passerby table @blonde @medium-height @tidy)
	A rounded, Marilyn-esque blonde.
(passerby table @blonde @short @tidy)
	Pin-precise in a blue-and-white striped suit and a boyish haircut.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 
%%  Urban space for walking scene
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 

#riversidedrive
(room *)
(proper *)		%% Proper is better than singleton here, I think
(name *)		Riverside Drive
(look *)
			(now) (walking scene is running)
			There's a pleasant late-afternoon view of the Hudson,
			and a snap in the air, and you would rather be here
			than anywhere.

(prevent [leave * $])   Oh, you know where you're going; no need to deviate
			from the usual path.

(perform [wait])        You stroll along enjoying the November crispness.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 
%% Walking scene
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 

(on every tick in #riversidedrive)
	(walking scene is running)
	(atmospheric event)

(atmospheric event)    
	(select)	%% Every tick there is a 1 in 3 chance for event
	(or)
	(or)
		(reset #npc) (notice #npc)	%% Reset and notice passerby
		(atmospheric event #npc)
		(par)
	(purely at random) 
    
(atmospheric event $passerby)            
	(select)
		Slowly (a $passerby) strolls by, turning to look at you as she
		passes.
	(or)
		Some (name $passerby) nearly bumps into you.
	(or)
		You dodge to avoid (a $passerby).
	(or)
		You weave around (a $passerby), who has stalled to look into a
		window.
	(or)
		There's a ruckus as one of the ubiquitous taxis nearly collides
		with (a $passerby) crossing the street.
	(or)
		(The $passerby) beside you waves to a friend across the street.
	(or)
		To your left, (a $passerby) drops her purse, and swears as she
		retrieves it.
	(at random)
3 Likes

Very cool stuff! The styling functionality seems to hint at more expansive CSS support in the future. Is that the plan, and, if so, would that require Glulx support or something else?

1 Like

Those changes look great. While mine worked, I was really just working around aspects of Dialog I knew I didn’t understand yet, so I am glad to see that values and lists make this kind of thing much nicer to write. The nested conditional expressions I made work but that approach won’t scale for larger projects and I’d much rather be using lists, which I understand now seeing your version.

One of the things I struggled with before seeing your version was that I wanted to code like this:

(hair $npc) Blonde

and then be able to change the result during run time, which you can’t do without using variables and lists like your version did.

(randomly select $Obj from $List)
	(length of $List into $N)
	(random from 1 to $N into $X)
	(nth $List $X $Obj)

(reset *)
	(randomly select $Hair from [ginger brunette blonde])
	(now) (* has hair $Hair)

(hair *)
	(* has hair $Hair)
	$Hair

Lists are a real time saver and I’ll be borrowing that convenience predicate for some other ideas of mine.

1 Like

Release 0f/04 fixes a bug in the optimization of certain if-conditions. It turns out you could crash Cloak of Darkness with a simple “notify off”. Oh well, at least it’s fixed now. =)

@Eleas I’m tinkering with a solution for putting Dialog games on the web with full CSS. But it’s not ready for release yet.

1 Like

Was wondering how you might teleport a player to a new room/location. What I have compiles but appears to treat the destination as an object that it thinks is in the current location of the player. If the destination has exits, you’re not able to use them after teleporting. Transcript and code below.

Transcript:

Test with teleport, look, north
 
You are standing in a teleporter...
 
> l
Start Room
You are standing in a teleporter...
 
> teleport
Energizing...
 
Examining your surroundings...
 
Start Room (in the Destination Room)
You are standing in a teleporter...
 
> look
Start Room (in the Destination Room)
You made it! You see something to the north...
 
> north
You can't reach this location.

That last message is from the standard library and is on line 3825. The code below has some hardcoding in it that I’ll remove later once I know it works.

Code:

(story title)	Teleport Test
(story author)	ElliotM
(story noun)	Moving the player directly to the destination without using directional movement

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 


%% stdlib message 'You can't reach this location.' comes from line 3825         

%% Teleport Action
(understand [teleport] as [teleport])

(perform [teleport])
    Energizing...
    (now)(#player is #in #destination)
    (par)(par)
    
    Examining your surroundings...
    (par)(par)
    (try [look])

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

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

#start
(room *)
(singleton *)
(name *)        Start Room
(look *)        
                You are standing in a teleporter...
                (par)
(from * go #north to #destination)
              
#destination
(room *)
(singleton *)
(name *)        Destination Room
(look *)          
                You made it! You see something to the north...
                (par)
(from * go #north to #end)
(from * go #south to #start)

#end
(room *)
(singleton *)
(name *)        End Room
(look *)          
                The end!
(from * go #south to #destination)


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

(intro) Test with teleport, look, north
        (par)(par)
        (look #start)

Another experiment of mine turned up the following compiler error message:

Dynamic predicates can have a maximum of two parameters.

I’m not exactly sure what that means but it is tagging lines 11 and 12 in my code where I am trying to define new exits/connections at run time between the rooms #start and #end. I am looking to replicate the mechanics from All Roads Lead to Mars from the Inform 7 recipe manual.

(story title)	New Exit Test
(story author)	ElliotM
(story noun)	Adding new exits at run time

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% xyzzy
(understand [xyzzy] as [xyzzy])

(perform [xyzzy])
    (now)(from #start go #north to #end)
    (now)(from #end go #south to #start)
    You can now go north now.


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

#player    
(current player *)
(* is #in #start)
(destination of * is)

#start
(room *)
(singleton *)
(name *)        Start Room
(look *)        
                This is the start.
              
%%(from * go #north to #end)

#end
(room *)
(singleton *)
(name *)        End Room
(look *)        
                This is the end.
              
%%(from * go #south to #start)

(intro)
    Test with north, xyzzy, north, south.

This is very interesting indeed, and I’ve been having fun playing around with it. Meanwhile, someone was asking whether there was an emacs mode for it. I’ve cobbled one together, just fontlock and indentation (not any attempt to compile).

Gist Here

Colours etc obviously depend on your font lock defaults. I’ve gone to some trouble to try to stop it highlighting keywords senselessly when they are not in a context where it would make sense, so that If we have a description that says

It's just an open container

That doesn’t come out as

It’s just an open container

But it’s still just regexes under the hood, not proper parsing, so it’s obviously fallible! I have no idea how it would perform on a large file.

Indentation may be more aggressive than some would prefer: the rationale is explained in comments in the file. To indent without shifting lines, don’t forget M-i. (None of that will make any sense to non-emacsers, I’m afraid.)

Install in your load-path and then (require 'dialog-mode).

(Incidentally, on my machines dgdebug segfaults when it tries to hot reload, but that’s probably my fault in some way, because I’m still feeling my way.)

3 Likes

(Incidentally, on my machines dgdebug segfaults when it tries to hot reload, but that’s probably my fault in some way, because I’m still feeling my way.)

Should be fixed now in Release 0f/05. Thanks for reporting!

Was wondering how you might teleport a player to a new room/location.

Moving the current player is a bit of a special case, because the standard library needs to do some housekeeping (such as moving floating objects into the room with the player). You have to use:

	(enter #destination)

This will also print the new room description.

To put the player on/in some other object, you can use:

	(move player to #on #chair)

There’s a section in the manual: Moving the player character

Sometimes, things aren’t that clean-cut, and the environment around the player changes for some other reason. For instance, if the teleporter is an enterable object (actor container in Dialog lingo), and we move the teleporter to another room, we have to explicitly tell the library to do its housekeeping:

	(now) (#teleporter is #in #destination)
	(update environment around player)

Don’t be afraid to look at the definitions of (enter $), (move player to $ $), and (update environment around player) in the standard library. They are quite straightforward. Line 460 in stdlib.dg.

3 Likes

Works great, I’ll take a look at those definitions.

You have come across an important difference between Dialog and Inform, which has to do with the modelling of large-scale changes to the world. I didn’t write much about this in the manual, because I tried to describe Dialog in absolute terms, without comparing it to other systems. But I’ll take this opportunity to elaborate.

In Inform 7 and 6, and probably also ZIL, most aspects of the game world are dynamic, and are represented by data. It’s possible to modify almost everything at runtime: Map connections, the names of objects, what routines are invoked in response to actions, and so on. When a significant narrative event takes place, a flurry of activity ensues as changes are applied to the world model.

In Dialog, most aspects of the game world are expressed using declarative rules, represented by code. These rules may in turn depend on dynamic flags and variables, but those flags tend to describe the state of the narrative, rather than the state of the world. So, for instance, the rules that describe map connections can have rule bodies with conditions in them. When a significant narrative event takes place, a global flag might be set, or a global variable might change. This causes rules all over the program to behave differently, because they explicitly check this flag or variable as part of their definitions.

This is how I would implement the magic xyzzy passage from the example:

(perform [xyzzy])
	(if) (xyzzy passage is available) (then)
		Nothing happens this time.
	(else)
		(now) (xyzzy passage is available)
		You can go north now.
	(endif)

(from #start go #north to #end) (xyzzy passage is available)
(from #end go #south to #start) (xyzzy passage is available)

One benefit of this new approach is that it conserves precious RAM (dynamic memory) on the Z-machine. I also feel (and this is my subjective opinion) that a program becomes easier to understand and debug when the amount of mutable state is minimized. That being said, Dialog tries to find a sensible balance, because certain things are indeed mutable, such as object locations.

2 Likes

Yup. Can confirm that (mis) behaviour seems to be gone. Thanks.

Interesting to hear the about those design differences and decisions. I also like the declarative rules nature of Dialog but haven’t programmed much in a language like this before. Your version of the magic xyzzy passage seems interesting as I wouldn’t have thought to do it this way. Is (xyzzy passage is available) essentially acting as a flag? I understanding how its working in the if else block but I didn’t know you could put flags after (from * go $direction to $location) and have it negate them if the flag isn’t true yet.

(from #start go #north to #end) (xyzzy passage is available)
(from #end go #south to #start) (xyzzy passage is available)

How much is this language influenced by LISP?

I can see the advantages of the declarative style, but it does take some getting used to. I did in the end manage to get a version of the “all roads lead to mars” example working, but it definitely needed some thinking and experimentation.

(story title)	All roads lead to Mars.
(story author)	Paul S
(story noun)	Adding new exits at run time

%% Global variable which holds a record of moves
%% that have already been made. (Actually, strictly
%% moves that have been chosen as possible.)
(global variable (moved []))

(have gone $Dir from $Somewhere to $Elsewhere)
    ($Element = [$Dir $Somewhere $Elsewhere])
    (moved $Previous)
    ($Element is one of $Previous)

%% LOCATIONS
#open
(room *)
(name *)
    Open plain.
(look *)
    A wide, grassy expanse
    (select)    
        , from which you could really
        go any way at all
    (or)        
    (stopping)
    .    

#hilly
(room *)
(name *)
    Hilly place.
(look *)
    The grassland gives way to a somewhat more hilly area,
    though there is still little to guide you to any
    particular way.
(available *)    

#stream
(room *)
(name *)
    Stream
(look *)
    This is the third place you've been today, so the stream
    is welcome. How refreshing.
    (par)
    Beside the stream, behind a sort of makeshift fence, some
    slippery stone steps lead down.
(from * go #down to #cave)
(available *)

%% Cave is not available, so it will never be chosen as
%% a possible place to go at random
#cave
(room *)
(name *)
    Cave
(look *)
    From this dank and drippy cave, you can hear the stream
    trickling above. The only way out is up.
(from * go #up to #stream)    

%% Player
(current player #player)

(#player is #in #open)

%% Directions and their opposites

(counterdir #north #south)

(counterdir #east #west)

(counterdir #northeast #southwest)

(counterdir #northwest #southeast)

(counterdir $A $B)
    (counterdir $B $A)


%% Find a free room
(locate free $Room)
    (collect $Unvisited)
        *(room $Unvisited)
        (available $Unvisited)        
        ($Unvisited is unvisited)
    (into $List)
    ($Room is one of $List)

(from $Origin go $Dir to $Place)
    ~($Dir = #down)
    ~($Dir = #up)    
    {
        (have gone $Dir from $Origin to $Place)
    (or)
        (counterdir $Dir $DirC)
        (have gone $DirC from $Place to $Origin)
    (or)
        (locate free $Place)
        (record choosing $Dir from $Origin to $Place)
    }

(record choosing $Dir from $Room to $Target)
    (bound $Dir)
    (bound $Room)
    (bound $Target)
    %% Not sure those should be necessary, but I had
    %% trouble at one point without them    
    (moved $Previous)
    ($This = [$Dir $Room $Target])
    ($Appended = [$This | $Previous])
    (now)(moved $Appended)    
3 Likes

Yes, that’s correct, it’s acting as a global flag.

In general, rule definitions can have conditions in the rule body. From the manual:

rule bodies can contain text and values to be printed, as well as queries to be made to predicates. As soon as a query fails, the entire rule fails. Hence, the rule can be regarded as a conjunction (boolean “and”) of queries.

Thanks for sharing, Paul, your version of All Roads works and that is farther than I managed to get so I’ll have to take a closer look at this.

First of all, Dialog borrows heavily from Prolog, and Prolog includes a number of Lisp-like features. For instance, both Prolog and Lisp represent lists using chains of head-and-tail pairs (cons cells) with a special marker (called nil in Lisp) at the end. All three languages support partial lists: [a b c | d] in Dialog and Prolog is equivalent to (a b c . d) in Lisp. Where Lisp has symbols, Prolog has atoms, and Dialog has objects and dictionary words.

In Prolog, the basic building block is called a term, and it consists of a name (which is an atom) and zero or more parameters. x(y, z) is a term named x, with parameters y and z (which happen to be atoms). Cons cells are actually terms with the name . (period), and two parameters corresponding to the head and tail. The square-brackets are syntactic sugar, so [a b c | d] is just a cleaner way of writing .(a, .(b, .(c, d))). In Dialog however, cons cells are the only non-atomic term allowed, and the square-bracket syntax is its proper textual representation. This could be regarded as a step in the direction of Lisp.

The closures introduced in Dialog 0f can of course be traced all the way back to the lambda operator of Lisp (and lambda calculus). Still, many recent languages contain some variant of this functionality, anonymous functions, so perhaps it’s no longer considered a Lisp-like feature.

Another core feature of Lisp (and many other languages) is the garbage collector that runs in the background and reclaims memory for unreachable objects. Prolog can be implemented in a way that is primarily stack-based (see Warren Abstract Machine), with no need for a garbage collector, and the same is true for Dialog. Memory is reclaimed as part of backtracking, and any data that needs to survive backtracking (e.g. during a collect operation) is serialized temporarily on a different stack.

An important difference between Dialog on the one hand, and Lisp and Prolog on the other, has to do with the similarity of code and data. Lisp code is made of lists, and Prolog code is made of terms, so in both these languages, code is expressed using the fundamental building block used for data manipulation. Both languages allow procedures to be constructed as data at runtime, and then evaluated (using eval in Lisp, and call in Prolog). Dialog diverges from this particular path, with its markup-like syntax, and a clear distinction between predicates and values. This design choice is also related to performance (eval would be very slow and memory-hungry on the Z-machine).

Well, this quickly derailed into a compare-and-contrast extravaganza. I don’t know if my post answers your question, but it was a pleasure to write. =)

5 Likes

Another small but important upgrade: Release 0f/06.

Apparently, versions 0f/01-05 generate startup code that is incompatible with Gargoyle (all blame is on Dialog this time). Although I run a large number of automated tests before each release, my scripts can’t deal with graphical interpreters, so this has gone under the radar for a while.

2 Likes