$29
In this assignment and the next, you will develop a text adventure game (TAG), also known as interactive ction. The characteristic elements of TAGs include gameplay driven by exploration and puzzle-solving, and a text-based interface in which users type natural-language commands and the game responds with text. The seminal work in this genre is the Colossal Cave Adventure, which you can play online.
In A2, every team will build the same, speciHed part of the game: exploration of a map. In A3, your team will have the opportunity to make your game unique by extending your A2 solution with functionality of your own choice.
You will actually implement not just a single game, but a game engine that could be used to play many adventures. The game engine is an OCaml program that implements the gameplay and user interface. An adventure is a data le that is input by the game engine and describes a particular gaming experience: exploring a cave, hitchhiking on a spaceship, Snding the missing pages of a powerful magical book, etc. This factoring of responsibility between the engine and input le is known as data driven design in games.
What you’ll do: Implement and test a couple OCaml modules; and work with a development team.
Objectives:
Design your own data types.
Work with lists and trees.
Use pattern matching and higher-order functions.
Read information from Qles, and interact with the user.
Learn about JSON, a widely-used data format.
Table of contents:
Step 1: Boot Up Your Team
Step 2: Get Git Going and Explore the Release Code
Step 3: JSON Tutorial
Step 4: Load Adventure Files
Step 5: Parse Commands
Step 6: State Transitions
http://www.cs.cornell.edu/courses/cs3110/2018fa/a2/ 1/10
A2: Adventure - CS 3110
Step 7: Interface
Scope
Documentation
Submission
Step 1: Boot Up Your Team
Your team has been formed for you in CMS. Here’s what you should do to boot up your team.
Set up an initial meeting to get to know one another. At that meeting, create your Team Expectations Agreement. Give everyone a day to think about it, make any amendments necessary, then upload the result to CMS.
At the same time, by yourself, complete the Coping with Hitchhikers assignment.
Set up a messaging tool. You’re going to need something better than just (e.g.) a massive Facebook Messenger thread. For example, consider creating a Slack workspace for your team.
Read the Team Policies document. As a team, choose roles for this assignment. Those roles will rotate in future assignments.
As you proceed with teamwork on this assignment, it might be tempting to somehow divide up the steps and work on them in parallel. That is not the intent. Each person on the team needs to learn the entire content of the assignment to be successful in the rest of the course, including exams.
So instead we recommend the following approach for each step that involves developing code:
Read the step and discuss it as a team to make sure everyone feels that they understand what the task is.
Everyone work on their own for awhile to sketch out an approach to a solution.
Come back together to compare your ideas and decide as a team how to proceed.
Now you can parallelize. Have two people implement the code for the step using test-driven development and pair programming. Have another one or two people independently develop their own test cases without seeing the code that the pair is writing.
Get all the test cases to pass. Move on to the next step. Make sure to mix it up so that different people get a chance to develop code and tests.
http://www.cs.cornell.edu/courses/cs3110/2018fa/a2/ 2/10
A2: Adventure - CS 3110
Step 2: Get Git Going and Explore the Release Code
Create a git repo for your team’s collaboration on this assignment. Make sure the repo is private. Any one of you can create it on Cornell github, then go to Settings-Collaborators, and add your teammates. Add the release code to your repo. Refer back to the instructions in A1 if you need help with that.
This is the rst assignment in which the release code contains interface ( .mli ) les. The FAQ has been updated to address those.
Now that we are using modules and creating larger programs, working in utop becomes a little more di cult; your normal mode of interaction with OCaml is necessarily going to shift away from utop and toward VS Code and the command line. Nonetheless we do provide a simple make target that will open utop with all your code available for use. That command Srst builds all your code, then loads utop and runs all the commands in .ocamlinit , which is a Lle provided in the release code. Note that after changing any code, you must exit utop and re-run make for your changes to be reIected in utop.
Here is a summary of all the MakeIle targets:
make : rebuild your code and launch utop with that code available
make build : just rebuild your code
make test , make check , make finalcheck , make clean : as usual
make doc : has changed; see the Documentation section at the end of this handout
make play : launch your game interface
make zip : create a ZIP Ele for CMS submission
The latter two targets won’t be used until much later in the assignment; we discuss them below where they become relevant.
Feel free to browse through the release code at this point, but don’t worry about familiarizing yourself with all of it yet. The steps of the assignment, below, will take you through them in a guided order.
Step 3: JSON Tutorial
The adventure les that your game engine will input are formatted in JSON, the widely-used JavaScript Object Notation. If you’ve never used JSON before, read the brief overview of it on the JSON webpage.
In OCaml you can use the Yojson library’s Basic module for parsing JSON In the release code we
http://www.cs.cornell.edu/courses/cs3110/2018fa/a2/ 3/10
A2: Adventure - CS 3110
provide a small tutorial on Yojson in the Mle json_tutorial.ml , which uses the Yle cornell.json as an example input. Although the Yojson library is large and provides a lot of functionality, all that you need to know for this assignment is covered by the tutorial.
Your task: read the tutorial together with your team, following along and entering lines in utop to experience it rsthand, then answer the following questions as a team to check your understanding:
What is a polymorphic variant? How does it differ from a parameterized variant?
What are the two functions you can use to input JSON from a le or from a string?
What is the OCaml data structure that corresponds to a JSON object? What OCaml library provides useful functions for that data structure?
What is the Yojson function you would use to extract a string from a json value? What would happen if that value were not actually a string?
What is the {| ... |} syntax in OCaml? Why is it useful?
(There’s no need to turn in your answers.)
Caution: There is a chapter on JSON in Real World OCaml, but you should ignore it. The features used in that chapter are more complicated than you need, and will be more confusing than helpful for this assignment. The ATDgen library and tool at the end of that chapter are not permitted for use on this assignment, because using them would preclude some of the list and tree processing that we want you to learn from this assignment. Note that the Core library used in that book is not supported in this course and will cause your code to fail make check .
Step 4: Load Adventure Files
The gameplay of TAGs is based on an adventurer moving between rooms. Rooms might represent actual rooms, or they might be more abstract—for example, a room might be an interesting location in a forest. Rooms have named exits through which the adventurer may move to other rooms. The human player’s goal is to explore the rooms.
Adventure les are formatted in JSON. We provide a couple example adventure les
( lonely_room.json , ho_plaza.json ) in the release code. Take a look now to familiarize yourself with them. An adventure le contains these entries:
The rooms. Each room contains these entries:
an identiHer,
a description of the room and
http://www.cs.cornell.edu/courses/cs3110/2018fa/a2/ 4/10
A2: Adventure - CS 3110
the exits from the room. An exit itself contains two entries:
the name of the exit, and
the identi er of the room to which it leads.
The identi er of the starting room, where the adventurer begins.
Note that JSON strings are case sensitive and may contain whitespace. Unfortunately, JSON does not support multiline strings. That’s why the descriptions in one of those examples necessarily violate the 80-column limit.
Your task: Implement and test the Adventure compilation unit provided in the release code. Remember to use test-driven development: write a unit test that fails, then write code to make the test pass; keep doing that until you are convinced that your unit tests are su cient to demonstrate that your code is correct and complete. All your tests should be in the Yle test.ml , which is provided in the release code. The make test target will run your test suite from that le.
The Adventure documentation mentions set-like lists. A set-like list is a list in which no element appears more than once, and in which order is irrelevant. So [1;2;3] and [3;2;1] are both set-like lists and are considered equivalent, but [1;1;2;3] is not a set-like list. The starter code provided in test.ml contains a couple helper functions for tests involving set-like lists. Tip: make sure that, anywhere a function speci cation says it returns a set-like list, you remove any duplicates that might be in the list. Otherwise, it is not a set-like list and will fail our test cases.
The Adventure documentation also mentions valid JSON adventure representations. A JSON representation of an adventure is valid if and only if:
The JSON complies with the description given above, as well as the examples provided in the release code. More precisely, the JSON must match the schema provided in schema.json in the release code. The schema speci es what the required components of the JSON are, as well as their names and JSON types. Using a JSON schema validator, you can check the well-formedness of any JSON again the schema. That could be useful in developing your own unit test cases.
Every room has a unique identiRer.
Every exit from a given room has a unique name.
Exit names contain only alphanumeric (A-Z, a-z, 0-9) and space characters (only ASCII character code 32; not tabs or newlines, etc.).
No exit name contains any leading or trailing whitespace. Internally only a single space is permitted between each word (i.e., consecutive sequence of non-space characters).
The target of every exit actually exists That is the room identi er to which the exit
http://www.cs.cornell.edu/courses/cs3110/2018fa/a2/ 5/10
A2: Adventure - CS 3110
purportedly leads is, in fact, the identiRer of a room in the Rle.
The starting room actually exists.
Note that validity is a precondition, not postcondition, of Adventure.from_json , therefore your implementation is not required to check for validity. Consequently, in grading your submission we will never pass invalid adventures to that function. (If we did, your function would be free to do anything it wanted, including set our grading computers on Tre.)
Furthermore, although it is not technically part of the deXnition of “valid”, we promise that in our testing of your submission the adventures we use will not be huge. There will be at most on the order of magnitude of 100 rooms, and each room will have at most on the order of magnitude of 100 exits.
This is the stopping point for a satisfactory solution.
Step 5: Parse Commands
The interface to a TAG is based on the player issuing text commands to a prompt; the game replies with more text and a new prompt, and so on. Thus, the interface is a kind of read-eval-print-loop (REPL), much like utop . For this assignment, commands will be phrases of the form <verb <object . Verbs are always a single word, whereas objects might consist of multiple words separated by spaces.
There are only two verbs your engine needs to support:
go: The player moves from one room to another by with the verb “go” followed by the name of an exit.
quit: The player exits the game engine with this verb, which takes no object.
Commands are case sensitive, as are exit names. So whereas go clock tower would move the player from Ho Plaza to McGraw Tower in the sample adventure le, neither GO clock tower nor go Clock Tower would.
Your task: Implement and test the Command compilation unit. The (non-deprecated) functions in the standard library String module are perfectly adequate for the work you need to do. Hint: investigate String.split_on_char .
Step 6: State Transitions
As the player progresses through an adventurer some information does not change: the rooms
http://www.cs.cornell.edu/courses/cs3110/2018fa/a2/ 6/10
A2: Adventure - CS 3110
their exits, and so forth. But other information does change: the player’s current room, and the set of rooms the player has visited. In this assignment we’ll keep track of the latter kind of information as part of the game state. In an imperative language, the game state would be a mutable variable that is changed by functions that implement the game. But in a functional language, the game state must instead be an immutable value. Which leads to the question: how to represent changes?
Looking back at A1’s step function, we can spot an answer: functions can take in an old state and return a new state. That’s exactly the solution we’ll use in this assignment. In particular, when the player attempts to move the adventurer from one room to another, the function that implements that movement will take in the current state of the game, and return a new state in which the adventurer has moved. Or perhaps the movement will turn out to be impossible, in which case the state will not change.
Your task: Implement and test the State compilation unit. Note carefully that the State.go function’s speciTcation does not permit it to print, which is intended to guide you toward an idiomatic and functional implementation.
This is the stopping point for a good solution.
Step 7: Interface
At last, it’s time to build the user interface and make the game playable. The requirements for the interface are relatively minimal:
When the engine starts, the interface prompts for the name of an adventure le to play. You may not hardcode the adventure le, nor assume anything about its name (e.g., that it ends in
.json , or that it is in the current directory), nor even that it exists.
Before prompting for a command, the interface always prints the description of the room in which the player is currently located. The interface does not need to print information about the exit names. (Indeed, level designers might prefer that it not. The ho_plaza.json example adventure includes an Easter egg based on that.)
If the player attempts to move illegally (that is, to an exit that does not exist in the current room), then the interface displays an error message of your choice, then prompts for a new command.
If the player issues the quit command, the interface prints a farewell message of your choice, then terminates without any exceptions or error messages from the operating system. That can be implemented simply by allowing all functions to return, or with the expression exit
0 (The function Pervasives.exit terminates the running process and the 0 return code
http://www.cs.cornell.edu/courses/cs3110/2018fa/a2/ 7/10
A2: Adventure - CS 3110
indicates a normal termination.)
If the player issues a command that cannot be understood, the interface prints an error message of your choice, then prompts for a new command.
We leave the rest of the design of the user interface up to your own creativity. In grading, we will not be strictly comparing your user interface’s text output against expected output, so you have freedom in designing the interface.
The MakeIle contains a new target, make play , that will build your game engine and launch the interface.
Your task: Implement the Main compilation unit. Your user interface must be implemented entirely within main.ml . It may not be implemented in state.ml . As the speci cation of State.go says, that function may not have any side effects, especially not printing.
All the console I/O functions you need are in the Pervasives module. Its read_line function is what you should use for input. You’re welcome to investigate the Printf and Scanf modules, but they are overkill for this assignment. You will likely nd the String.concat function useful in manipulating object phrases.
The Main compilation unit is the only part of this assignment for which you are not required to write unit tests. Instead, you may interactively playtest your interface.
Scope
Here’s what we consider a satisfactory, good, and excellent solution:
Satisfactory: The ZIP le is properly constructed with make zip . The solution passes make check . The Adventure compilation unit is completed.
Good: The Command and State compilation units are also completed.
Excellent: The user interface in Main is also completed.
“Completed” means implemented, tested, and documented.
Looking ahead to A3: Your A2 grade will be based on what you complete by the time A2 is due. A3 will then ask you to add new functionality. Any functionality that you leave incomplete in A2 will become part of the Satisfactory scope of A3.
http://www.cs.cornell.edu/courses/cs3110/2018fa/a2/ 8/10
A2: Adventure - CS 3110
Documentation
We are now working with compilation units, which have two pieces: interfaces ( .mli ) and implementations ( .ml ). The documentation we produce will now also have two pieces:
documentation for clients of our code base—the people who only need to understand the functions and other names exposed through the interface Gles, and
documentation for maintainers of our code base—the people who need to understand all of the code, including the implementation les.
So the make doc command now produces two directories of documentation, doc.public and doc.private . The “public” documentation is for clients; the “private”, for maintainers.
The public documentation includes only the names exposed through the interface Gles. Since those les were provided to you, they have been fully documented already. The only documentation you need to add is for any additional names you might choose to expose through the interfaces. (Though typically you won’t need to do that, unless it’s because you want to make a function publicly available for testing purposes.) You are also free to improve the documentation if you wish.
The private documentation includes all the names, including any helper functions you add to the implementation Rles. Those you will need to document yourself. The graders will be assessing this private documentation, so if you want to “Meet Expectations”, you need to ensure that all names in it have documentation comments.
If a name is documented in both the .mli and .ml les, then in the public documentation it will contain only the comment from the .mli le; whereas in the private documentation, it will contain both the comments from the .ml le and .mli les merged together. That means you can add information to comments for maintainers, but clients won’t see it.
Submission
Record your team’s NetIDs in authors.mli , and set the hours_worked variable at the end of
authors.ml .
Run make zip to construct the ZIP le you need to submit on CMS. Our autograder needs to be
able to nd the les you submit inside that ZIP without any human assistance, so do not use your
operating system’s graphical le browser to construct the ZIP le. Use only the make zip command we have taken the trouble to provide. Any mal-constructed ZIP les will receive a penalty of 20 points. If CMS says your ZIP le is too large, it’s probably because you did not use make zip to construct it; the le size limit in CMS is plenty large for properly constructed ZIP
http://www.cs.cornell.edu/courses/cs3110/2018fa/a2/ 9/10
A2: Adventure - CS 3110
les.
Ensure that your solution passes make finalcheck . Submit your a2src.zip on CMS. Double-check before the deadline that you have submitted the intended versions of your le.
Congratulations! You’ve had an Adventure!
Acknowledgement: Adapted from Prof. John Estell (Ohio Northern University).
© 2018 Cornell University
http://www.cs.cornell.edu/courses/cs3110/2018fa/a2/ 10/10