This is the first version of the Dialogue Manager Script (DMS) documentation, which will demonstrate the current state and capabilities of DMS through a tutorial, outline the syntax and semantics, and discuss how to write effective DMS. DMS is a high-level language that compiles down to [[DMPL]] compliant JSON.

Getting Started

The code snippets presented in this page can be evaluated by hitting the "Run" button, which compiles DM Script into [[DMPL]] JSON, and then processes that JSON on a JavaScript runtime. The compiler can be found on GitHub [[dms-compiler]].

Tutorial: Guessing Game

This guessing game chat-bot will help us better understand DMS. Let's build a simple prompt that asks the user for some input. We start by printing out messages using act, and then awaiting the user's input.

          act "Guess the number!"
          act "Please input your guess."
          input -> guess {
              _ {
                  act "You guessed " + guess
              }
          }
        

          
          
        

The input -> statement awaits for user input, and feeds it to a variable of our choosing. In this case, the variable guess is populated with the user's input.

          once {
            act "Guess the number!"
            secret_number = pick(range(1, 101))
          }

          act "Please input your guess."
          input -> guess {
              to_num(guess) > secret_number {
                  act "Too big!"
              }
              to_num(guess) < secret_number {
                  act "Too small!"
              }
              _ {
                  act "You win!"
                  pop true
              }
          }
        

          
          
        
Once the user guesses the correct number, the system will output "You win!", and finallly the script will end, as indicated by pop true, which pops the current script from the runtime stack.

Syntax and Semantics

The DMS syntax is loosely based off Rust. Curly-brackets { } establish a block of code, statements do not need a trailing semi-colon ;, and labels before the curly-brackets annotate the behavior of the block. Similar to ECMAScript, DMS is not a strongly typed language.

Scripts written in DM Script are also called components. The runtime shall evaluate the component in an infinite loop, similar to game-loops inherent in most game engines. Components may call other components through run or use statements.

Comments

Programmers may leave notes, or comments, in their code. These comments are ignored by the compiler, but are useful for other programmers reading the code.

          // Write your comments here.
        

Variables

In programming, a variable is nothing more than a placeholder for some value. The strings “Chai”, and “Moo” are simply values that a placeholder dog_name could take on, as can be seen in the following example.

            // Initial value for a dog name.
            dog_name = "Chai"
  
            // Later on dog_name can be changed to a different value if needed.
            dog_name = "Moo"
          

          

Note that in DMS, there is always an implicit infinite loop wrapping our code. During execution it will keep creating and assigning a variable named dog_name the values "Chai” and "Moo”.

Control Flow

In order to remove these redundant variable creation and assignments we can wrap code in a block beginning with the keyword once. The once keyword tells the compiler that the variable creation and assignments should only be executed one time over the lifetime of the component.

            once {
              dog_name = "Chai"
            }
            dog_name = "Moo"
          

          

Deciding whether or not to run some code depending on if a condition is true is a basic building block in most programming languages. The most common construct thats allows programmers to control the flow of execution of DMS code are if statements. Recall that in DMS there is always an implicit infinite loop wrapping our code, meaning that loops are inherently built in, and any code outside of a once block will be repeatedly executed over the lifetime of our program.

If Statements

An if statement allows us to branch our code depending on conditions. These statements start with the keyword if, and follow up with a condition that evaluates to a Boolean value.

              once {
                number = 1
              }
              
              if number >= 2 {
                act "Number is greater than or equal to 2."
              }
              else {
                act "Number is less than 2."
              }

              number = number + 1
            

            

We can link multiple conditions by combining if and else in an else if block. For example:

              once {
                number = 7
              }
              
              if number % 3 == 0 {
                act "Number is divisible by 3."
              }
              else if number % 2 == 0 {
                act "Number is divisible by 2."
              }
              else {
                act "Number is not divisible by 2 or 3."
              }
            

            

Fork Statements

Forks are generalized if statements. They're the heart and soul of DM Script. As a refresher, let's consider the trivial example below of branching logic.

            if true {
              act "Let's talk about animals."
            }
            else {
              act "Let's talk about colors."
            }
          

          

A fork statement allows a more general way of representing branching behavior. First, let's recreate the example above using fork. Think of it as a fork in the road, where we can only go down one candidate path. Each candidate has an entry-condition. In the example below, notice that the underscore _ is a shortcut for the Boolean value true.

            fork {
              _ {
                act "Let's talk about animals."
              }
              _ {
                act "Let's talk about colors."
              }
            }
          

          

By default, a fork picks the first path whose entry-condition is met, from top to bottom. This method for resolving a fork is also called the "greedy" strategy, because it picks the first possible candidate instead of considering all candidates.

Forks allow powerful customization of the branching behavior. In the example below, the fork picks a child-block at random. We can optionaly change the fork-strategy by providing a dictionary in a decorator #{} directly before the statement. In the example below, a strategy of {depth: 0} is associated with the fork.

            #{depth: 0}
            fork {
              _ {
                act "Let's talk about animals."
              }
              _ {
                act "Let's talk about colors."
              }
            }
          

          

The content of the dictionary specifying the fork-strategy depends on what the underlying runtime supports. In this case, {depth: 0} means use bounded depth-first search (BDFS), with a depth of 0 to resolve the fork. A depth of 0 in BDFS is effectively picking a candidate at random. Different run-times may support different search algorithms, such as Monte Carlo Tree Search.

The heuristic function for the search algorithm is specified by declaring a model. The model of a fork-strategy is a list of lists, representing preferences. For example, the pair [{x: 0}, {x:10}], declares that the value of x is preferred to be 0 as opposed to 10. Internally, the runtime infers a utility-function, which is a real-valued function over the variables in DM Script, that provides a total-ordering of preferences.

            once {
              x = 5
            }
            #{depth: 1, model: [[{x: 0}, {x: 10}]]}
            fork {
              _ {
                x = x + 1
              }
              _ {
                x = x - 1
              }
            }
          

          

Changing the preference model changes the program behavior. For example, by swapping the pair [{x: 10}, {x: 0}], the program now counts up instead of counting down.

            once {
              x = 5
            }
            #{depth: 1, model: [[{x: 10}, {x: 0}]]}
            fork {
              _ {
                x = x + 1
              }
              _ {
                x = x - 1
              }
            }
          

          

If we want to say that the value of x should be 5, we can write the model as a list of preference-pairs, as follows.

            once {
              x = 3
            }
            #{depth: 1, model: [[{x: 5}, {x: 0}], [{x: 5}, {x: 10}]]}
            fork {
              _ {
                x = x + 1
              }
              _ {
                x = x - 1
              }
            }
          

          

Consider the following scenario, where a autonomous agent has 3 possible actions, (1) eat food, (2) do nothing, or (3) work for food. In DM Script, we can use fork to define the pre-conditions and outcomes of each action. We specify that {health: 10} is desirable over {health: -10}, and use BDFS to resolve the fork, with a depth of 1.

            once {
              health = 2
              food = 1
            }
            #{depth: 1, model: [[{health: 10}, {health: -10}]]}
            fork {
              food > 0 {
                act "eat"
                food = food - 1
                health = health + 3
              }
              _ {
                act "do nothing"
                health = health - 1
              }
              _ {
                act "work"
                food = food + 1
                health = health - 2
              }
            }
          

          

Unfortunately, the program chooses the suboptimal sequence of actions: it chooses to do nothing after eating up all the available food. Let's increase its intelligence by changing the fork-strategy to {depth: 3}. Notice that now, the program will alternate between eating food and working, which is the optimal strategy.

            once {
              health = 2
              food = 1
            }
            #{depth: 3, model: [[{health: 10}, {health: -10}]]}
            fork {
              food > 0 {
                act "eat"
                food = food - 1
                health = health + 3
              }
              _ {
                act "do nothing"
                health = health - 1
              }
              _ {
                act "work"
                food = food + 1
                health = health - 2
              }
            }
          

          

We'll cover more interesting use-cases of fork in the effective DMS section.

Primitive Types

The example in the variables section only allowed the variable dog_name to take on string values. However, DMS is a dynamically typed language meaning that variables can take on any basic data type such as string, float, and Boolean. Consider the following example where we have a variable named current_thought which denotes what a programmer might be thinking about throughout the day.

            once {
              // Early in the morning their first thought could potentially be
              current_thought = "coffee"
              
              // Next they could be thinking, do I want office coffee?
              current_thought = false
              
              // Note that we don't use double quotes when assigning a boolean value to a variable.
              
              // And so they decide to buy coffee elsewhere, which has an associated cost.
              current_thought = 5.20
            }
          

          
Every value in DMS has a data type which tells DMS what kind of data is being specified so it knows how to work with the data. Recall that DMS is dynamically typed meaning that it doesn’t need to know the types of all variables at compile time.

Structures

The primitive types defined in the previous section are all atomic literals. DMS also allows programmers to build more complex structures such as lists, or dictionaries.

List

A list is an ordered arrangement of other structures. All elements are enclosed within square-brackets [ ], and separated by commas as follows.

              once {
                // list of integers
                student_grades = [87, 90, 88, 92, 93]
                
                // list of strings
                student_names = ["Nawar", "Tom", "Chris"]
                
                // list of arbitrary types
                a_bunch_of_stuff = [false, 1, "two", [3, 4, 5]]
              }
            

            

Dictionary

A dictionary is a structure that maps data in key-value pairs. The value of a corresponding key can be any structure.

              once {
                student_grades = {
                  Nishant: 0,
                  Carol: 93,
                  Daniel: 90
                } 
              }
            

            

Take note of the syntax that was used to create the dictionary in the above code snippet. The keys of the dictionary are symbols which are a finite sequence of characters without the double quotation-marks, and the values in this example are simply integer values. Note that when accessing the values of a dictionary, the keys must be enclosed within double quotation marks.

              once {
                student_grades = {
                  Nishant: 0,
                  Carol: 93,
                  Daniel: 90
                } 
                act student_grades["Nishant"]
              }
            

            

Functions

Custom function definitions in DMS start with the keyword def and have a set of parentheses after the function name. The curly-brackets { } tell the compiler where the function body begins and ends. Lastly, a pop statement returns the value to be used by the caller. Consider the following function which simply returns the string, "Greetings!".

          def greet() {
            pop "Greetings!"
          }
        

Our declaration of the greet function begins with the keyword def, is followed by the function name greet, and ends with a set of empty parentheses (). The function body contains all the logic that is expected to be executed when calling the function. In the above example, we simply return the string "Greetings!" using the keyword pop. In order to call our function and print the corresponding greeting, we use the keyword act.

            once {
              def greet() {
                pop "Greetings!"
              }
              act greet()
            }
          

          

Functions in DMS can also have parameters, which are special variables that are part of the function declaration. The following re-declaration of the greet function allows programmers to pass in a variable of their choice, such as the name of a user in the following example.

            once {
              programmer = "Naitian"
              def greet(user) {
                pop "Greetings " + user
              }
            }
            act greet(programmer)
          

          

The variables introduced in a function are local variables that are only accessible within the current scope, and they shadow the variables defined in the outer scope. In the example below, we define a variable called greetings within a function, and later try to access that variable out of scope. Undefined variables by default evaluate to null.

            once {
              programmer = "Naitian"
              def greet(user) {
                greetings = pick(["Greetings", "Howdy", "Sup"])
                pop greetings + " " + user
              }
              act greet(programmer)
              act programmer
              act greetings
            }
            
          

          

You can also implement closure using functions. For example, in the following code-snippet we show how to define a function that raises a number to an exponent.

            once {
              def powerOf(power) {
                  def f(num) {
                      pop pow(num, power)
                  }
                  pop f
              }

              square = powerOf(2)
              cube = powerOf(3)

              act square(3)
              act cube(3)
          }
          

          

In DMS, functions are pure, so act or input statements are not recommended in the body of user defined functions.

Built-in Functions

A handful of functions are available out of the box in DMS, forming a standard library. The sections below cover the essential functions.

Comparison operators

Check for equality with == or !=. Compare numbers with >, >=, <, and <=.
                once {
                  x = 1
                  y = 2
                  act x == y
                  act x != y
                  act x < y
                }
              

              

Boolean operators

Typical Boolean functions such as &&, ||, and ! are supported.
                once {
                  x = true
                  act x || false
                  act (x && !x) == false
                }
              

              

Arithmetic operations

Frequently used mathematical functions over numbers are available, such as +, -, *, /, %, and floor.
                once {
                  x = (1 + 2 * 3) / 4
                  y = (x + 0.25) % 2
                  z = floor(x)
                }
              

              

Type casting operators

The to_num and to_str operators allow changing types between numbers and strings.
                once {
                  number = 13.5
                  act "The number is " + to_str(number)
                  guess = "10"
                  act "You're off by..."
                  act number - to_num(guess)

                }
              

              

++

The ++ operator concatenates two lists. Note, on the other hand, the + operator combines strings.

                once {
                  act [1, 2, 3] ++ [4, 5, 6]
                }
              

              

len

The len function returns the length of the given argument. Possible argument types include: string, list, or dictionary.

                once {
                  school_data = {
                    student_names: ["Jeremy", "Earle", "Chad"],
                    school_name: "Institute of Good Learning"
                  }
                  act len(school_data)
                  act len(school_data["student_names"])
                  act len(school_data["student_names"][0])
                }
              

              

pick

The pick function allows programmers to pseudo-randomly select an item from a list.

                once {
                  athlete_rankings = [
                    { lonzo: 2 },
                    { zion: 1 }
                  ]
                }
                
                act pick(athlete_rankings)
              

              

get

The get function allows users to select a specific element of a list. In the above example if we wanted to retrieve the last element we could do so as follows.

                act get(len(athlete_rankings) - 1, athlete_rankings)
              

Or, use the [ ] notation, which is just a syntactic-sugar of get, as shown below.

                act athlete_rankings[len(athlete_rankings) - 1]
              

range

The range function generates a list from the starting value (inclusive) to the ending value (exclusive).

                once {
                  act range(1, 11)
                  act range(1, 5) ++ range(5, 11)
                }
              

              

An optional third argument specifies the step-size.

                once {
                  act range(1, 11, 2)
                }
              

              

map

The map function iterates through a list passed in, and produces a new list with modified elements.

                once {
                  act map(to_str, [1, 2, 3])
                }
              

              

Multiple lists may be passed in, as long as the operator passed in supports that number of arguments.

                once {
                  act map("+", [1, 2, 3], [4, 5, 6])
                }
              

              

This opens up a lot of useful list manipulation techniques, such as implementing zip below.

                once {
                  def zip(xs, ys) {
                    def pair(x, y) {
                      pop [x, y]
                    }
                    pop map(pair, xs, ys)
                  }
                  act zip([1, 2, 3], ["a", "b", "c"])
                }
              

              

foldl

The foldl function iterates through a list from the left to yield a value by applying a given operator to each element of the list with an accumulator.

                once {
                  def sum(xs) {
                    pop foldl("+", 0, xs)
                  }
                  act sum([1, 2, 3])
                }
              

              

sort, shuffle, reverse

The sort, shuffle, and reverse functions operate on lists.

                once {
                  xs = [4, 3, 2, 1]
                  ys = shuffle(xs)
                  act sort(ys) == xs
                  act reverse(sort(ys)) == xs
                }
              

              

in

The in function is an in-fix operator that checks if an element exists within a structure.

                once {
                  month = {
                    days: range(1, 31),
                    month: "June"
                  }
                  act "days" in month
                  act 31 in month["days"]
                }
              

              

edit

the edit function performs a mutation on a dictionary variable based on the specified key value pair. The general syntax is as follows: edit(dictionary, value, key). Note that if the specified key does not exist, an entry will be create within the specified dictionary variable with the specified value.

                once {
                  info = {name: "unknown", inventory: {belt: 1, jacket: 2}}
                  act edit(info, "Bob", "name")
                }
              

              

patch

The patch function mutates a dictionary variable based on instructions encoded by another dictionary. The general syntax is as follows: patch(dictionary, key_values). Note that like edit if patch encounters a key that does not exist within the specified dictionary variable, patch with create an entry with the missing key and corresponding value.

                once {
                  info = {name: "unknown", inventory: {belt: 1, jacket: 2}}
                  act patch(info, {name: "Bob", inventory: {jacket: 3}})
                }
              

              

from_list

The from_list function converts a list of pairs (2-element lists) into a dictionary.

                once {
                  table = [
                    ["glasses", 1],
                    ["jacket", 2],
                    ["shirt", 0]
                  ]
                  act from_list(table)
                }
              

              

exists

The exists function allows programmers to check whether the given variable has been defined or not.

                if exists("count") {
                  count = count + 1
                }
                else {
                  act "Initializing count to 0"
                  count = 0
                }
                act count
              

              

Note that because we haven't defined the variable count anywhere, our program defaults to the else branch of the conditional statement.

pow

The pow function raises a base number to a specified exponent.

                once {
                  base = 2
                  exponent = 3
                  act pow(base, exponent)
                }
              

              

hop

Recall that DMS compiles down to [[DMPL]] compliant [[JSON]], which is a tree-like structure. The hop statement allows programmers to revisit the ancestors of the current code block. Consider the following example, keeping in mind the notion of code blocks having ancestors.

                act "What's the largest planet?"  // <-- hop 3, comes back to here

                input -> result { // <-- hop 2, go to grandparent
                  result == "jupiter" {
                    act "Correct!"
                  }
                  _ { // <-- hop 1, go to parent
                    act "Nope"
                    act "Try again..."
                    hop 3
                  }
                }

                pop true
              

                
                
              

The above example can be hard to understand at first sight so lets break it down. At a high-level the component itself is the root of our tree structure. From there, code blocks and statements having equal scope can be thought of as children of the root. In the above example, the act statement and the code block following the input statement have equal scope and hence are children of the root. Stepping into the code block following the input statement, the code blocks following result == "jupiter" and _ have equal scope; as such they are both children of the input statement. Finally looking at the code block following _, both act statements and the hop statement have equal scope, and hence are all children of the _ statement. With this in mind the hop statement is essentially saying, return to the third ancestor of this code block.

Input Handling

Up to now all program behavior has been predefined. Variables have been assigned persistent values, and all output can be predetermined ahead of time. However part of what makes any program engaging and meaningful, is when a user or programmer is able to directly interact with the application. In this section we showcase how DMS captures and processes user input. In DMS, user input is handled by a special structure which assigns intents to a temporary user-defined variable, as follows.

            once {
              act "Hi there! I'm Parrot-Bot. I repeat anything you say!"
            }

            input -> result {
              result == "Hello" {
                act "Ahoj to you!"
              }
              _ {
                act "You said: " + result
              }
            }
          

            
            
          

In order to begin capturing input, we use the syntax input ->. The expression can be thought of as follows, “Anything that is typed in or captured, redirect and store in the variable immediately following the arrow -> “. In the example above, all captured input is stored in the variable result. Once DMS is done capturing input, the body of the input block begins its execution. Simply put, the conditions contained within the body of the input block can be thought of as if-else expression. This means that the first expression, using the input, that evaluates to true will be executed. In the above example, whenever user input happens to be "Hello" the program will output "Ahoj to you!", (Note: Ahoj is Czech for Hello) and in all other cases will default to the expression beginning with the underscore _. Note that within an input block whenever an underscore _ is used as the condition of an expression, it will always evaluate to true; essentially acting as the default else of a branching statement.

Running components

Most programming languages allow programmers to use external packages/libraries and launch child processes from anywhere within the main process. In this chapter we demonstrate how programmers are able to import and run components which can be thought of as scripts from within the main DMS program. Being able to import and launch components allows for programmers to develop modular and sophisticated DMS programs.

use statement

The use statement allows programmers to import variables and user-defined functions from an external component or script. Consider the following example where we have a component containing a number of math functions, and a main component wanting to reuse them. Create the following two files: main.dms and math.dms. Inside math.dms place the following code segment.

            once {
              pi = 3.14
              def square(number) {
                  pop number * number
              }
              
              def increment(number) {
                  pop number + 1
              }
            }
          

Now suppose that we wanted to use both the square and increment functions. Inside the main.dms file we write the following,

            once {
              use "math" import _
            }
            
            act square(2)
            act increment(3)
            act pi
          

In order to introduce the component, we begin with the keyword use, followed by the component name, in this case math. Note that we leave off the file extension. Next in order to bring all predefined functions into scope within main.dms we use the keyword import _. This can essentially be thought of as bringing everything that was defined within main.dms into scope and made usable. The manner in which components are introduced might be a little tough to understand. At a high level the use of components can be thought of as follows. Suppose we're making curry. We have a recipe, which calls for spices. Naturally we ask ourself, which ones? To which our recipe might say, all of them. And so we proceed to bring all the individual ingredients defined in spices and use them within our curry recipe.

run statement

In the last section we introduced the idea of a component or simply a script. Components not only make a programmers life easier by allowing DMS code to be modular, but also save developers from writing their entire code base within a single component. DMS allows programmers to launch sub-components from within a main-component. At a high level this means that if our current task is to make dinner, completing the sub-task of washing dishes gives us the clean dishes we need to finish our main task. Consider the following DMS code which does exactly that. Create the following two files: main.dms and wash_dishes.dms. Inside wash_dishes.dms place the following code segment.

              once {
                num_dishes = 1
              }

              if num_dishes >= 2 {
                act "done"
                pop true
              }

              num_dishes = num_dishes + 1
            

            

Similarly within main.dms place the following.

            once {
              clean_dishes = false
            }
            act "Lets make some food!"
            if !clean_dishes {
              run "wash_dishes" () -> result {
                result {
                  act "Ok lets get to cooking!"
                }
                _ {
                  act "On second thought, lets order in."
                }
              }
            }
          

There’s a lot going on in the above example, so lets break it down. In order to launch a sub-component we use the keyword run. The line containing run "wash_dishes" () -> result tells DMS that we want to pause our current component, run the sub-component, and wait for it to finish. Upon finishing, the wash_dishes sub-component returns a Boolean value denoting its success, which is stored in result. (Note that the returned value of the sub-component does not have to be a Boolean value, and can actually be any of the atomic or structure data types defined in the previous chapter.)

The expressions that follow are again an example of conditional if-else expressions. The first expression that evaluates to true will be executed, and the others will be ignored. In this case because we happen to be returning a Boolean value, whenever the result is true, the first expression will be executed, and our program defaults to the case beginning with an underscore _ as this evaluates to true.

_args builtin

In the run example we showed how to launch a sub-component by calling, run "wash_dishes" () -> result from within the main component. Note the empty parentheses, (), following the name of our sub-component. In DMS we can pass arguments to our sub-component by placing all intended parameters (i.e. arguments) within (). The sub-component can then access these arguments by unpacking or indexing the builtin _args variable. In the following example we show how to pass arguments to our sub-component and access their values using both array indexing and unpacking notation.

Create two files: jury.dms and decision.dms. Within jury.dms place the following code snippet.

          once {
              first = "Nishant"
              last = "Shukla"

              run "decision" (first, last) -> verdict {
                  verdict {
                      act "Jail forever."
                  }
                  _ {
                      act "Freedom!"
                  }
              }
          }
          

Similarly, within decision.dms place the following code snippet. Here we show how to unpack the _args builtin variable using both aforementioned notations.

          // unpack the array
          first, last = _args
	    
          // or, access the array by index
          first = _args[0]
          last = _args[1]
	  
          pop first == "Nishant" && last == "Shukla"
          

Note: The _args builtin variable is null in any component that is not called by another component, such as a main component, and is otherwise an empty list.

Effective DMS

Beyond this point we begin to define the key ideas that make DMS special. We introduce the notion of what makes DMS a task-oriented programming language and illustrate how tasks can be broken down into actions that have a corresponding utility. With this notion of tasks, actions, and utility we show how DMS allows programmers to rid themselves of the overhead of having to think about the sequential-steps required to achieve a task and simply let DMS pick the best sequence of actions for us. Lets dive in!

The fork statement is the most important aspect of DMS. It lists possible branches of program flow in a declarative manner.

In this section, we'll design a chat-bot that asks "What's the capital of France?". If we type an incorrect answer, such as "Rome", then the script will diagnose the mistake, and help us get to the answer.

First, set up a couple variables and constants. A score variable will be used to track dialogue quality. The entities, such as ROME, and relations, such as CITY_IN, will be declared as variables, just to keep our code clean. We'll also track a user_response variable that indicates whether the dialogue has already processed the user's input.

          once {
            score = 0
            EUROPE, FRANCE, ITALY, ROME, PARIS, LYON = 
                ["Europe", "France", "Italy", "Rome", "Paris", "Lyon"]
            COUNTRY_IN, NEXT_TO, CITY_IN, CAPITAL_OF = 
                ["a country in", "next to", "a city in", "the capital of"]
            user_response = null
          }
        

        
The remaining bit of code obtains the user's input and then interpets the answer. The program loops at the else branch until the user_response variable is reset to null.
        if user_response == null {
          // get the user's input
        } else {
          // interpret the user's input
        }
      

The complete script is shown below.

          once {
            score = 0
            EUROPE, FRANCE, ITALY, ROME, PARIS, LYON = 
                ["Europe", "France", "Italy", "Rome", "Paris", "Lyon"]
            COUNTRY_IN, NEXT_TO, CITY_IN, CAPITAL_OF = 
                ["a country in", "next to", "a city in", "the capital of"]
            user_response = null
          }
          
          if user_response == null {
              answer = ""
              relation, entity = [CAPITAL_OF, FRANCE]
              act "What is " + relation + " " + entity + ": " + 
                  "Italy, Rome, Paris, or Lyon?"
              input -> response {
                  _ {
                      user_response = response
                  }
              }
          }
          else {
              #{depth: 4, model: [[{score: 10}, {score: 0}]]}
              fork {
                  [relation, entity] == [COUNTRY_IN, EUROPE] {
                      answer = ITALY
                  }
                  [relation, entity] == [NEXT_TO, FRANCE] {
                      answer = ITALY
                  }
                  [relation, entity] == [COUNTRY_IN, EUROPE] {
                      answer = FRANCE
                  }
                  [relation, entity] == [NEXT_TO, ITALY] {
                      answer = FRANCE
                  }
                  [relation, entity] == [CAPITAL_OF, ITALY] {
                      answer = ROME
                  }
                  [relation, entity] == [CITY_IN, ITALY] {
                      answer = ROME
                  }
                  [relation, entity] == [CITY_IN, FRANCE] {
                      answer = LYON
                  }
                  [relation, entity] == [CAPITAL_OF, FRANCE] {
                      answer = PARIS
                  }
                  [relation, entity] == [CITY_IN, FRANCE] {
                      answer = PARIS
                  }
                  relation == CAPITAL_OF {
                      relation = CITY_IN
                      act "Not every city is the captial"
                  }
                  relation == CITY_IN {
                      relation = CAPITAL_OF
                  }
                  relation == CAPITAL_OF {
                      relation = NEXT_TO
                  }
                  relation == NEXT_TO {
                      relation = CAPITAL_OF
                  }
                  entity == FRANCE {
                      entity = ITALY
                      act "I think you're thinking of a different country"
                  }
                  entity == ITALY {
                      entity = FRANCE
                      act "I think you're thinking of a different country"
                  }
                  answer == user_response {
                      user_response = null
                      act answer + " is " + relation + " " + entity
                      score = score + 1
                  }
              }
          }