Dialog - a programming language for dialogs

ArticleCategory

Software Development

AuthorImage

Philipp Gühring

AuthorName

AboutTheAuthor

Philipp has just passed the A-Levels at the HTL Wiener Neustadt, a higher technical school for EDP. So now he has got time for his software-development group, called Futureware 2001. He is a Linux fan and an active member of the Linux User Group Austria.

Abstract

Dialog is a programming language, which is for dialogs with the user. It was used in the trading-simulation Würstelstand. This article describes the development of Dialog and its applications.

ArticleIllustration

Josi, one of the customers

ArticleBody:

Introduction

Würstelstand is an Austrian trading-simulation in German in which the player has to manage a hot-dog stand, where the contact to the customers became nearly an adventure game. And for exactly this customer-contact I developed a dialog language, which should fulfill the following needs: When it came to write the telephone conversations, I reused the dialog engine, and enhanced it. The player controls the character Leni, who owns the hot-dog-stand, with a multiple-choice system. The customers are simulated by the computer. The aim of the player is to advise the customers, to sell them something and to chat with them. The customers appear automatically as soon as they are hungry and have the time to do so. Additionally the player has the capability to be active on his own, and to call several numbers on his telephone.

Dialog language

The dialogs are stored in Ascii text files, and are interpreted line by line, although it is possible to advance to a further chosen line at any time. We used to create them with a simple text editor. The filename is Name.BAT (e.g.: HALE.BAT) When the player(Leni) says something automatically:
Leni: Text
Leni: Good morning, Sir! 
      What can I get you?
Leni: Look at these youngsters!
      Unbelievable!
Leni is talking
Look at these youngsters! Unbelievable!
Did you fell out of the bed?
Customer is talking
Two Frankfurter with bread and two Coke. Come on, get going, old man!
When the conversation partner says something: (Kunde is customer in German, Telefon is telephone in German)
Kunde: Text
Kunde: Two Frankfurter with bread and Coke. 
       Come on, get going, old man!
Telefon: Futureware 2001, Philipp Gühring.
         What can I do for you?

Every normal dialog ends with

Ende
. (Ende is end in german)

An easy example:
Leni: Good morning, Sir! What would you like?
Kunde: Good day! A Käsekrainer please!
Leni: Just a moment.
Leni: Here you are.
Kunde: Thanks a lot. Bye!
Leni: Bye!
Ende

Jump targets are defined by a colon at the beginning of a line, after which the name of the jump target itself follows. You can jump to the targets with the command Sprung (Sprung is jump in German):

:Target
//Jump example follows:
Sprung Target

Example
...
Leni: 1
//First we do this
SPRUNG MENU_0
//I'll be back!
...
//These commands are not executed
Leni: 2
:MENU_0
//I am back!
Leni: 3
What does the interpreter do with this example? First he finds the command Leni: and outputs the text 1. Then there is a comment in the next line (First we do this), which will be ignored. In the following line there is the command Sprung. The interpreter searches through the whole dialog for the target MENU_0, finds it a few lines below, and jumps there. Then there is a comment again (I am back!). And the last command is Leni:, which prints 3 on the screen. So in the end he skipped the command Leni: 2 in the example , and Leni does not say 2.

As we have seen, a line can:

Comments start with ;(semicolon),//(two slashes), (space),*(asterisk). They enhance the understanding and documentation of the dialog and are therefore ignored by the interpreter. e.g:
// This is a comment
**************************************
*Like this, one can make comments too*
**************************************
Comments must not be in the same line as commands:
Leni: I don't unterstand nothing anymore.  // NO COMMENT
The interpreter will recognise I don't unterstand nothing anymore. // NO COMMENT as the text to be output!

The Multiple Choice System

Multiple-Choice
  • What do you do for work?
  • Do you like my Würstelstand?
  • What can I get you?
Dialog invents the following system: The system maintains a list, into which the entries are inserted, which are the answering possibilities the user will have. When the time has come, the menu will be displayed on the screen. Now he can choose, and then the interpreter jumps to the part of the program, which handles the chosen entry.

Firstly you insert the entries with the commands NEU and ALT into the list. Both commands are followed by the jump target and the text of the entry. The text can be a very long line, as the system breaks the lines up automatically. With the command MENÜ the whole list gets displayed, and the user can choose an entry.



Now there are three types of choices:

Topics, not depending on context

This type is usable for discussion topics:
Neu buy,A hot one, as usual, ok?
Neu work,How is it going at work?
Neu language,Are you still attending the language course at WIFI?
Neu family,How is your family doing?
Neu weather,Are you enjoying this weather?
Menü
With this type the player has the possibility to play through all the entries one after the other. The choices are reusable. The entries which were not chosen remain in the list, and can be chosen at a later stage. So, let's choose work from the above example:
:work
Leni: How is it going at work?
Kunde: Too much to do, as always.
Menü
As mentioned, only the chosen line gets extinguished. At the following menu remain these topics: Anyway, there are two other ways of choices:

Options, Speech dependend on context

Kunde: How many would you like?
Alt some, 10 Pieces
Alt more, 20 Pieces
Alt most, 100 Pieces
Menü

:some
//We continue here, when the user chose 10 pieces

:more
...

:most
...
As it doesn't make sense to leave the chosen entries any longer in the list, all the entries should be automatically deleted after to the choice of the user. If for example the user chose 20 pieces, the interpreter jumps to the target more:
:more
Kunde: Are you sure?
Leni: Yes, I want 20 Pieces.
Kunde: How soon do you need them?
Alt 1, Tomorrow
Alt 2, The day after tomorrow
Alt 3, Sometimes
Menü
And finally one can combine the two types:

Context, Change of topic

We realized yet another subtlety: Once one has discussed a topic with his conversation partner, but has still got something left to say, one can either act out that remark or one can change the topic. If one changes the topic, the context of the remark becomes invalid, and the remark turns useless. Therefore one has got the possibility to stick to a topic or to jump to yet another one. We implemented this in Dialog in the following way: The remark gets inserted as a normal option into the list, which already contains topics. All options next to the users choice will be extinguished, whereelse all topics which haven't been chosen remain.
Kunde: Remember the good old days.
Alt Memory,Yeah, I just rememberd when, ...
MENÜ

Implementation

You probably wonder, how we put all these different concepts into practice. You've probably realized by now, that the distinction lies in the usage of NEU or ALT. If one inserts a topic into the list with the command NEU, then it remains in the list, unless it has been chosen. On the other hand, if one inserts an option into the list with the command ALT, it will be automatically deleted, regardless whether it's been chosen or not.

Several lists

What happens for example, if one needs to make a choice while discussing a topic, yet he doesn't want to display the other topics in the menu? For this purpose I developed not just one, but three lists:

List 0 is recommended for options. List 1 is for the general topics, for example family, work, leisure, food, ... If one wants to talk about work for example, and there are other topics within that subject of work, one chooses list 2. We have got one example in the dialogue of Hale. In case one should need more lists than those three one needs to change the constant in the sourcecode of the interpreter.

How to use the different lists?

At the beginning the list 1 is the current list. With the command LISTE one can make another list the current list.
LISTE 0
LISTE 1
LISTE 2
Obviously all entries of all lists remain in their lists in this process. The commands like NEU, ALT, MENÜ, LÖSCHEN always relate to the current list.

Old version of Dialog

In the old version of Dialog, which was used for Würstelstand, the choice system worked a bit differently: Instead of the command ALT, we added a paragraph to the command NEU after the comma:
Neu Memory,§Yeah, I just rememberd when, ...
The list 0 was after the menu automatically deleted. It was therefore only useful for options, not for topics.

I recommend now to look at the examples HALE.BAT and PETER.BAT, where we used the lists exceedingly.

LÖSCHEN target
deletes every entry of the current list, which points to target. For example:
LÖSCHEN familiy
To delete all entries of the current list, one adds an asterisk:
LÖSCHEN *
(If desired, one can add regular-expression support ;-)

Menü shows all entries of the actual list, and allows the user to choose amongst them. After this the chosen entry and all added options (inserted with ALT) will be deleted. Finally the interpreter jumps to the target accordingly. In case of only one entry in the list, it's understood that this entry is the choice and therefore no multiple choice menu is presented. If there is no entry in the list, or if the target can't be found, the interpreter will continue after MENÜ with the next line.

Interfaces

How is the dialog able to react on its environment, influence the environment and exchange data with other dialogs?

Memory/Registers

In Würstelstand, every dialog has access to 256 registers. Each of these registers contains a number in the range of -2 billion to +2 billion. The 256 registers are divided into three parts:

Systemregisters

The first 100 registers (from 0 to 99) are reserved by the system: They are being loaded with their values before the dialog starts, so that the dialog can react on its environment. All registers which are marked with //S will be analysed and used after the end of the dialog. This is the influence of the dialog. Here is the list of the system registers from Würstelstand:
1 Event;   //event number (look at texte.h)
2 geliefert; //S //0-10 how many tenths will be deliverd
3 wtag;   //day of the week
4 tag;   //day of the month
5 monat;   //month
6 jahr;   //year
7 Datum;   //serial day (1.1.1997 = 0)
8 wetter;   //today's weather
9 konto; //S //bank account
10 kapital; //S //cash
11 ausgaben; //S //today's expenses
12 einnahmen; //S //today's income
13 sterne; //S //quality rating of the hot-dog stand(0-5 stars)
14 wverkauf;   //number of products sold this week
15 weinnahmen;   //weekly income
16 wausgaben;   //weekly expenses
17 0; //S //new income/expense (triggerd by the dialog)
18 Nachrichtenserie;   //which news series (0=Elch,1=...)
19 Nachricht;   //which news in the current series (0=1.Tag,1=2...)
20 LottoNr[0];   //how many Lottery numbers are used(0-6)
21 LottoErgebnis[0];   //how many Lottery numbers were right
22 LottoGewinn[LottoErgebnis[0]];   //how much Leni won
23 S.Image; //S //Leni's image
24 S.Override; //S //Overriding-Event
25 S.wverkauf[1];   //products sold last week
26 S.weinnahmen[1];   //last weeks income
27 S.wausgaben[1];   //last weeks expenses
28 S.wverkauf[2];   //products sold two weeks ago
29 S.weinnahmen[2];   //income two weeks ago
30 S.wausgaben[2];   //expenses two weeks ago
31 S.NOverride; //S //tomorrows overriding event
32 S.wetter_bericht;   //which weather forecast
33 Gesamtwert();   //total value of the hot-dog stand
34 Wetterbericht[S.wetter_bericht].Ereignis;   //Which weather event
35 Tageszeit;   //daytime in minutes
70..79 Lagermenge   //stocks
80..89 Verkaufspreis //S //price of product
90..99 Kaufmenge //S //amount of order

Dialog registers

The next 100 registers (from 100 to 199) are private for every dialog. They are set to zero at the beginning of the game, and are persistent for the dialog throughout the whole game. They are being saved in the savegames, ..., and are only accessible from their dialog. The system and all other dialogs can not read/modify the dialog registers. One has to document at the beginning of the dialog which dialog registers one wants to use for what.
batch.cpp
// Customer: Peter Hinzing 
// 
// Usage of the registers
//[100] How often he was here 
//[101] Pocket money
//[102] Several events
//[103] Random number: order
//[104] Random number: answer to order
//[105] Different dialogs: Work on the 5th day
//[106] Deal
//[107] The game starts, after having been chosen
//[108] Game.stake.type
//[109] Game.stake.quantity
//[110] Game.choose.Peter
//[111] Game.choose.Leni
//[112] Activation of the Hobby 
//[113] Activation of the Home
//[114] Dialog about Würstelstand 
//[115] total stock coke
//[116] too much ?*************************
//* not yet done
In the register [100] Peter remembers, how often he visited the hot-dog stand. When he arrives the first time, he introduces himself. On his 10th arrival, he relaxes and becomes informal. In [101] he manages his own pocket money, ...

Shared Memory

The last 56 registers (could be more, the exact number is not that important) are Shared Memory between all the dialogs. This means that all dialogs have access to these registers, and all dialogs see the same memory. So there should be a central point where the usage of these registers has to be regulated. The following three registers were used by the dialogs from Würstelstand (documented in the daten.h):
[200]: Leni can go to the immigration office with Hale
[201]: Leni read the dog's wanted circular
[202]: Leni had played Stein-Schere-Papier with Peter! (evil!)

Events

We developed an Event system for Würstelstand. Every Event has a unique Number, which has to be coordinated in a central file. Events can trigger the following things: How is all of this done?
For the products, customers, telephone dialogs and news series you just have to insert the number of the event into the corresponding data file as the start/end value.

How can I trigger Events?

Aktion expression
// activating Cheats:
Aktion 3
// activating the event, which was calculated into register 100:
Aktion [100]
What can you do with these Events? Here is a part of the Event list from Würstelstand:
0 Error/Never Event 0 is caught and left unhandled
1 Initialising is triggerd at the beginning and activates many products, customers, ...
2 End should be triggerd at the end of the game
3 activating FW-Cheat Who coded this?!?
4 deactivating FW-Cheat Keep this secret!
5 Leni.competition.activating newspaper Leni's image is good enough -> activating newspaper article about the upcoming contest
6 Leni.competition.Zeitung->TelefonNr Newspaper article activates the telephone number to join the contest
7 Leni.competition.deactivating TelNr After having phoned and sorted everything out, the phone number gets deactivated
8 deactivating Hale Hale deactivates himself, because Leni offended him.
9 Hale recommends Josi Hale activates Josi as soon as having talked about it, (Smalltalk is important!)
10 deactivating Josi Josi deactivates himself
11 deactivating Peter Peter deaktiviert sich selbst
12 Sepp Nachricht without Leni aktivieren Sepp offers illegal production, Leni refuses, the whole thing comes in the open and public.
13 Sepp Nachricht with Leni aktivieren Leni agrees to the illegal production, problems follow
14 lost game The postman Gottfried initiates the end of the game
15 won game Gottfried realizes the value of the hot-dog stand, and Leni wins
16 Hale.news article Asyl activate Leni talked to Hale about his family, which initiated the newspaper article about the right of asylum.
17 Hale.news article->Telefonnr activate The newspaper show the telephonnumber, which can be used now
18 Hale->Zeitungsbericht->Telefonnr deactivating The conversation deactivates the phone number
19 Hale->Familie activating Hale's family reveices asylum
20 activating the spy Leni should have been recommended a detective, but this has never been put into practice.
33 New products 1 (New supplier) This event extends the product range
100 won contest Leni won the contest, the customers can talk about it, ...
101 losts contest
102 Lotteryprice Leni won in the Lottery
As one can see, the events are a very powerful tool to implement the logic of the game.

Calculations

With the command Rechne (Rechne is calculate in German) you can calculate mathematical expressions and store them in a register. e.g.:
Rechne [100]: 20 + [30] * 10
The content of the register 30, multiplied by 10, added to 20 is stored into the register 100.

The following mathematical operations are possible:

Operation Notation Example Solution
Klammern (a) (10+20)*30 900
Register [a] [20] The contents of the register 20
Multiplication a*b 3*4 12
Division a/b 10/5 2
Rest a%b 10%3 1
Addition a+b 1+1 2
Subtraction a-b 1-1 0
Zuweisung [a]:b [10]:20 Schreibt in Stelle 10 den Wert 20
Vergleiche a?b Ja(1) oder Nein(0)
Ist gleich a=b 10=20 Nein(0)
Kleiner a<b 10<20 Ja(1)
Größer a>b [10]>[20]
AND a&b 1=1 & 2=2 Wenn 1 gleich 1 ist UND 2 gleich 2 ist
OR a|b 1=1 | 2=2 Wenn 1 gleich 1 ist ODER 2 gleich 2 ist
Random number a Z b 1 Z 6 Returns a random number between 1 and 6

Comparisons result in numbers: 1 for TRUE and 0 FALSE/WRONG. These can also be written in the register. Spaces in expressions ( 10 + 20 ) are allowed, but not neccesary (10+10).

The biggest challenge was the development of the mathematical evaluator, which is now capabel to process the expressions like the following:

Assumption: [100]=5, [24]=14, 1Z6=2

[[100]+1]:((1Z6)*([24]>3)+10/2-10%5)
[5    +1]:((2  )*(14  >3)+10/2-10%5)
[6      ]:(2    *(1        )+5   -0   )
[6      ]:(2    *1          +5        )
[6      ]:(7                          )
[6      ]:7
Solution: [6]:7 The value 7 is written into the register 6.

Reaction on registers

With the command Wenn (Wenn is if in German):
Wenn condition
then
one can make comparisons. e.g.:
Wenn [100+1]>10
Kunde: The number in the register 101 is bigger than 10 !
Wenn 1>1
Kunde: ERROR!
If the condition is true, the interpreter proceeds in the next line, otherwise he skips one line. This is implemented in the way that the following line is skipped if the condition is false. This can be used in connection with jumps:
Wenn [102]<10
Sprung SMALLER
Wenn [102]=10
Sprung EQUAL
Wenn [102]>10
Sprung BIGGER
...
:SMALLER
...
:EQUAL
...
:BIGGER

Viewing pictures

BILD expression
(Bild is picture in German) If for example
Bild 5
is mentioned in the HALE.BAT, then it HALE5.DAT (a special picture format) is shown. A mouse click is expected and the dialog can proceed.

Command reference

To gain a better overall view, I have developed a command reference:
// comment: COMMAND REFERENCE

Kunde: text The customer says something
Tel: text Conversation partner says something
Leni: text Leni says something
:target Target where the interpreter can jump to
Liste number Makes a list the current list
Löschen * Deletes all entries of the current list
Löschen target Deletes all entries of the current list, which point to target
Aktion number Triggers Events
Ende Ends the dialog
Bild number Shows the picture with the filename NameNumber.dat
Sprung target Jumps to the target
Neu target,Text Inserts new topic into the current list
Alt target,Text Inserts new choice into the current list
Menü Show the menu, let the user choose, ...
Wenn condition Comparison (see next lines)
//then If true, the interpreter proceeds in the next line
//else If false, the interpreter skips one line
Rechne expression Calculates expressions into a register
Bild expression Show pictures, waits for mouse click

Disadvantages of the multiple choice systems

Dialog Maker

Markus Muntaneau developed a Delphi program called Dialog-Maker, with which it should be easier to develop dialogs. Unfortunatly it has never been finished (Some bugs still remain) and it is therefore not very useful. Though it is still recommendable for developers to have a look at it.

Programming tricks

As the whole Würstelstand project comprises of about 10.000 lines of C(++) code, and the compiling times were still acceptable, I did without a clean modularisation (ok, I admit to laziness too) I developed the Test-Include system instead: The module code gets integrated into a .c file, which is executable on its own, and which delivers at the same time a testprogram to the routines of the modules. This in return is deactivated by #ifdef if it is included from the main module. Like this, one saves header files. ;-)

batch.cpp
#ifndef _DIALOG_H 
#define _DIALOG_H 
 
#ifndef MAIN_MODULE
  #define DIALOG_TEXT 
  #define DEBUG 
  //Here are the necessary included Header files
  #include <stdio.h>
  //... 
#endif 
 
//Here are the whole dialog routines
//..
S2 Dialog(char *Filename, TYP Array[])
{
  //...
}

#ifndef MAIN_MODULE
 //Here is everything for the test programs
TYP Feld[256]; 
int main(short argc,char *argv[]) 
{ 
  //Testprogram
  Dialog(Filename,Feld);
} 
#endif
wurst.cpp
#define MAIN_MODULE
#include "batch.cpp"
TYP Felder[10][256];
int main(short argc,char *argv[]) 
{ 
  Dialog(Filename,Felder[i]);
}

Final remarks

The Linux version of simulation Würstelstand is available at Futureware (http://poboxes.com/futureware). The 1.1 version of dialog can be downloaded from here (dialog-1.1.tar.gz)
In case of a great demand (E-Mails to the author), further articles about the practical examples of Dialog will follow.