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
- The attributes of the player class
- 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.