DSL ~ DSL Tutorial - uchicago-cs/chiventure GitHub Wiki

DSL is a tool that allows game authors to write games on the Chiventure engine, without necessarily understanding and learning all of the syntax and formatting associated with WDL files. Instead, game authors write largely in plain text and the DSL module generates the WDL file for them.

In this tutorial, we will make and run a game using the DSL tools provided.

Setup

Before we can run DSL files, we will need to set up the environment. This tutorial will assume that we are using the Linux CS Machine, and more information on how to set up the environment on a personal machine can be found in the README.

First make sure your working directory is set to chiventure/src/dsl. Next we install the environment with: \n conda env create -f conda_environment.yml

This command will likely take a little bit to run to completion. Afterwards, when we are ready to convert a DSL file to WDL, we run:\n conda activate cs220-dsl

Writing a DSL File

While more simple than WDL files (which follow json format), there are still syntax patterns and rules to follow when writing a DSL file.

Let’s consider the following example, and see how we can convert it to DSL.

  1 {
  2   "GAME": {
  3     "start": "room B",
  4     "end": {
  5       "in_room": "room C"
  6     },
  7     "intro": "Welcome!"
  8   },
  9   "ROOMS": {
 10     "room C": {
 11       "items": [],
 12       "long_desc": "This is a room C. ",
 13       "short_desc": "room C"
 14     },
 15     "room B": {
 16       "short_desc": "A dungeon room.",
 17       "items": [
 18         "Door"
 19       ],
 20       "long_desc": "This is a room B. "
 21     }
 22   },
 23   "ITEMS": {
 24     "DOOR": {
 25       "short_desc": "A large wooden door",
 26       "actions": [
 27         {
 28           "action": "OPEN",
 29           "condition": "Door is in front of you.",
 30           "text_success": "You open the door.",
 31           "text_fail": "You can't open the door."
 32         },
 33         {
 34           "action": "BREAK",
 35           "condition": "Door is in front of you.",
 36           "text_success": "You break the door.",
 37           "text_fail": "You can't break the door."
 38         }
 39       ],
 40       "in": "room B",
 41       "long_desc": "This is a DOOR. A large wooden door"
 42     }
 43   },
 44   "PLAYERS": {
 45     "KNIGHT": {
 46       "short desc": "KNIGHT",
 47       "long desc": "This is a KNIGHT.",
 48       "attributes": {
 49         "noble": "TRUE",
 50         "strong": "TRUE"
 51       },
 52       "base_stats": {
 53         "stat_setting": {
 54           "id": "health",
 55           "state": {
 56             "CURRENT": "20",
 57             "MAX": "10"
 58           }
 59         }
 60       }
 61     }
 62   },
 63   "NPCS": {
 64     "OAK": {
 65       "location": "lab",
 66       "short_desc": "Kanto's premier Pokemon expert",
 67       "long_desc": "Enjoys exploring human-Pokemon relationships",
 68       "age": "80",
 69       "gender": "Male",
 70       "INVENTORY": {
 71         "item_id1": "CHARMANDER",
 72         "item_id2": "SQUIRTLE",
 73         "item_id3": "BULBASAUR",
 74         "item_id4": "POKEBALL"
 75       }
 76     }
 77   }
 78 }

This short example declares the game start condition first. This includes what room the player starts in, where the game ends, and what the introduction text would look like.

It then goes on to declare the two different rooms, what the short/long descriptions for the rooms are, and what items are in the rooms.

Next, it describes the one item in the game, the door. This includes where it is located, its short/long descriptions, and the two different actions the player can do with it. Those action themselves have conditions and success/failure text depending on whether the action could be completed.

Next, it describes a player class. This includes the short and long descriptions of this class as well as its attributes and base stats.

Lastly, it describes a NPC. It contains information about its location, its short/long descriptions, age, gender, and inventiry.

In contrast, let us look at its DSL counterpart.

 1 GAME START room B END room C
  2    intro: "Welcome!"
  3
  4
  5 ROOM room B
  6    short desc: "A dungeon room."
  7    long desc: "This is a room B."
  8    ITEM Door IN room B
  9     short desc: "A large wooden door"
 10     action: OPEN, BREAK
 11       OPEN condition: "Door is in front of you."
 12         OPEN success: "You open the door."
 13       OPEN fail: "You can't open the door."
 14       BREAK condition: "Door is in front of you."
 15         BREAK success: "You break the door."
 16       BREAK fail: "You can't break the door."
 17
 18 ROOM room C
 19    short desc: "Room C"
 20    long desc: "This is a room C."
 21
 22 # testing: player class with id of knight. Testing if attributes and base_stats are generated correctly
 23 PLAYER_CLASS KNIGHT
 24     short desc: "Knight"
 25     long desc: "This a Knight"
 26     ATTRIBUTES
 27         noble TRUE
 28         strong TRUE
 29     BASESTATS
 30         health
 31             CURRENT 20
 32             MAX 100
 33
 34 NPC OAK IN lab
 35   short desc: "Kanto's premier Pokemon expert"
 36   long desc: "Enjoys exploring human-Pokemon relationships"
 37   age: "80"
 38   gender: "Male"
 39   INVENTORY
 40     item_id1: "CHARMANDER"
 41     item_id2: "SQUIRTLE"
 42     item_id3: "BULBASAUR"
 43     item_id4: "POKEBALL"

Instead of the constant repetitive information, this is much more concise and readable. Let’s examine the DSL syntax. Firstly, curly braces {} are not used in the DSL format. Additionally, instead of defining rooms and items separately, there is a hierarchy in-place such that we define the GAME state and the ROOMS. Then ITEMS are a step below ROOMS, and ACTIONS are a step below ITEMS.

As can be observed from the above, GAME has a START and an END where specific ROOM names are specified. Then a step below that (separated by two spaces) we define the intro: text.

The first ROOM is room B. One step below, we describe its short/long descriptions, and defined its first ITEM. When defining the ACTIONS for the ITEM, we list all the names first and then define conditions for the ACTION, the success and the failures.

The PLAYER_CLASS is also much neater. The ATTRIBUTES and BASESTATS have their own hierarchies such that each attribute is given a name and set to TRUE/FALSE, and each stat is given a name, which is followed by the CURRENT/MAX state of the stat.

Lastly, the NPC for OAK contains all the same information as before, and the INVENTORY has a nice hierarchy with the objects inside it. It is nice to notice that the "location" information was switched to be included in the NPC declaration, rather than under it.

What is Currently Supported in the DSL

The DSL is still in early stages of development. As such, many features that may be functional or in-progress through a WDL format are not yet implemented.

Features currently supported:

  • GAME
    • Where the player starts
    • Where the player ends
    • Intro text
  • PLAYER_CLASS
    • The attributes of the player class
      • Attributes can be either TRUE or FALSE
    • The base stats of the player class
      • Each base stat has a CURRENT level and a MAX level
    • Short description, Long description, etc
  • NPC
    • The location of the NPC (in declaration)
    • The inventory of the NPC
    • The Dialogue graph of the NPC
    • Short description, Long description, Age, Gender, etc
  • ROOM
    • Short description
    • Long description
    • Connections (which rooms/locations it is linked to)
      • (example) connections: WEST TO room C
  • ITEM
    • Short description
    • Long description
    • Actions
  • ACTION
    • Conditions
    • Action Fail
    • Action Success
  • VARIABLES
    • Note: Can be defined anywhere
    • Short, long, connection, items, actions, etc.

Syntax and Notes on Variables

Variables can be extremely useful for when multiple items/objects in the DSL have the same format with few differences. Some examples detailing the use of variables are listed below.

Example 1

(Defining:)

$intro = "hello {firstname} {lastname}"

(Use:)

intro: $intro {firstname: John, lastname: "Smith"}

Example 2

   $room_var = """ROOM room {letter}
  	short: "This is room {letter}"
        long: "This is room {letter}. There's a {contents} and an exit to the {exitdir}."
        connections: {exitdir}   TO room {nextletter}
        ITEM {contents}"""
  
   GAME START room A END room C
  
   $room_var {letter: A, contents: chair, exitdir: SOUTH, nextletter: B}
   $room_var {letter: B, contents: table, exitdir: WEST, nextletter: C}
   $room_varm {letter: C, contents: trophy, exitdir: NORTH, nextletter: A}

Defining and using a variable necessitates the use of β€˜$’ followed by the variable name (i.e. $var_name). When defining a variable, we use the syntax $var_name = with either one pair of double quotes (i.e. $var_name= β€œ β€œ) if it fits on a single line, or a pair of three double quotes (i.e. $var_name= β€œβ€β€ β€œβ€β€) for a more complex, multi-line definition.

Anytime there is an element that may change depending on context, we use curly braces (i.e. {input}) to define it.

When actually using the variable, we use $var_name {}. Inside the curly braces, we explicitly define what our desired input is (i.e. {input: desired}).

For more specifications on DSL Grammar, refer to the page here: https://github.com/uchicago-cs/chiventure/wiki/DSL-~-DSL-Grammar

Unit Testing

The dsl module has built in tests to ensure than any new changes and features implemented do not affect the output of previously implemented features. These tests can be found in the tests subdirectory.

From the src/dsl directory, you can run all of the tests in the tests/dsl_tests subdirectoy using:

python src/unit_test.py

The testing module also supports flags --show and --file=filename:

python src/test.py --show --file=room_test.dsl --file=min_test.dsl

The --show flag will print out the first discrepancy between expected and actual output for each test that fails. The --file=filename.dsl flag will test just one file located in the tests/dsl_tests subdirectory. This flag can be used multiple times in order to run multiple files at once.