# A2: Adventure
**Soft deadline:** Thursday, 09/24/15, 11:59 pm
**Hard deadline:** Saturday, 09/26/15, 11:59 pm
*This assignment is to be done as individuals, not with partners nor with teams.*
In this assignment, you will develop a *text adventure game*
(TAG), also known as *interactive fiction*. 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 classic example of this genre is the
[Colossal Cave Adventure][cave].
**Exercise:** Spend a few minutes playing an [online version of
Colossal Cave Adventure][playcave] before you read the rest of
this assignment. (But note that game is far bigger than what
you will build in this assignment.)
[cave]: http://rickadams.org/adventure/
[playcave]: http://www.amc.com/shows/halt-and-catch-fire/exclusives/colossal-cave-adventure
## Overview
For this assignment, you are to implement a *game engine* that
could be used to play many *adventures*. Here, the game
engine is an OCaml program that implements the gameplay and
user interface. An adventure is a [JSON][json] data file that is
input by the game engine and describes a particular gaming
experience: exploring a cave, hitchhiking on a spaceship,
finding the missing pages of a powerful magical book, etc.
This factoring of responsibility between the engine and
input file is known as *data driven design* in games.
[json]: http://json.org/
The gameplay of TAGs is based on an *adventurer* moving
between *rooms* through *corridors*. Rooms might represent
actual rooms, or they might be more abstract—for example,
a room might be an interesting location in a forest, and a
corridor might be the path leading between such locations.
Normally corridors can be traversed in both directions, but
not always. Each room also has a text description
associated with it. Some rooms have *items* in them. These items
can be taken by the adventurer and carried to another
location, where they can then be dropped. The adventurer
begins the game in a predetermined room, possibly with some
predetermined items.
The *player* does not so much win a TAG as *complete* the
TAG by accomplishing various tasks: exploring the entire
*map* of rooms and corridors, finding items, moving items to
specified locations, etc. To indicate the player's progress
toward completion, a TAG gives a player a numeric *score* at
each moment in the game. The TAG also tracks the number of
*turns* taken by the player. Savvy players attempt to
achieve the highest score with the lowest number of turns.
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 generally be two-word phrases of
the form VERB OBJECT:
* **Movement** is performed by using the verb "go" followed by the
direction. The room itself determines what the allowed
directions are; common possibilities include "north",
"south", "east", "west", "up", and "down". If such movement
is possible, the movement occurs, the adventurer is in a
different room, and the description of that room is
displayed. If the movement is impossible, an error message
is displayed. As a shorthand, the player may simply type
the direction itself without the verb "go".
* **Items** can be
manipulated by the verbs "take" and "drop" followed by the
name of the item: "take" transfers an item from the current
room to the adventurer's *inventory*, and "drop" transfers an
item from the inventory to the current room. Of course, if
an item is present in a room, it must be mentioned when the
room is described.
* **Other commands** include "quit", which ends
the game, "look", which re-displays the description of the
current room, "inventory" (shorthand: "inv"), which gives a
list of what the adventurer is currently carrying, "score",
which displays the current score, and "turns" which displays
the current number of turns.
For this assignment, scoring will be simple: every room is
worth a certain number of points, which are earned simply
for entering the room at least once, and every item is worth
a certain number of points. The points for an item are earned
if the item is located in a particular room, which depends on the
item. Dropping the item in that room earns points, and taking
the item away from that room loses points. Both cause the
engine to print a message telling the player so. When the player
earns the maximum score possible for the adventure, the game engine
informs the player that they have completed the adventure.
And, for this assignment, you may choose your own definition
of what counts as a turn. As a starting point, we recommend
that each "go", "take", and "drop" count as a turn.
Your task is to develop a game engine and to write a small
adventure of your own.
## Objectives
* Design user-defined data types, especially records and variants.
* Write code that uses pattern matching and higher-order functions on lists and
on trees.
* Interact with the environment outside the program by reading and writing
information from files and the user.
* Use JSON, a standard data format.
* Practice writing programs in the functional style using immutable data.
* Optionally, start learning to use Git, a distributed version control system.
## Recommended reading
* [Lectures and recitations 4–7][web]
* [RWO chapters 3, 5, 6, 7, and 15][rwo]
* The front page of the [JSON website][json]
[rwojson]: https://realworldocaml.org/v1/en/html/handling-json-data.html
[web]: http://www.cs.cornell.edu/Courses/cs3110/2015fa/
[rwo]: https://realworldocaml.org/v1/en/html/index.html
## Requirements
The primary requirements are the following:
* Your game engine must implement all the functionality and commands described
in the Overview above.
* We must be able to compile and run your game engine as described
below under "Compiling and running".
* Your game engine must be compatible with the adventure file format
described below under "Adventure file format",
so that we can test your engine on our own adventures.
* Your game engine must be robust: we should not see any unhandled exceptions,
infinite loops, or other faulty behavior during our testing of it.
* Your own small adventure must have at least 5 rooms and 3 items.
* Your code must be written with good style and be well documented.
As for gameplay, we leave most of it up to your own creativity. In grading,
we will not be strictly diffing (comparing) your text output against
expected output, so you have freedom in designing the interface.
The only interface requirements we impose are the following:
* When the adventurer enters a room, the engine must print a room description,
what items are currently in the room, and the descriptions of those items.
* When the adventurer drops an item, the engine must provide feedback on
whether the item has been dropped in the right room to score points for it.
* The prompt must be clearly indicated to the player.
* The text interface must be case-insensitive.
## What we provide
In the release code on the course website you will find these files:
* A relatively minimal adventure file `minimal.json` that you could
use as a basis for writing new adventures.
* A larger sample adventure file `gates.json`.
* A JSON schema `schema.json` for adventure files that defines
the format of such files.
* A template file `a2.txt` for submitting your written feedback.
## What to turn in
Submit files with these names on [CMS][cms]:
* `game.ml`, containing your solution code.
* `adv.json`, containing your new adventure.
* `a2.txt`, containing your written feedback, including your testing plan.
[cms]: https://cms.csuglab.cornell.edu/
## Compiling and running
We will compile and run your solution as follows:
```
cs3110 compile -p yojson,str game.ml
cs3110 run game.ml -- f.json
```
where `f.json` will be replaced by the name of the adventure file
we want to test. The `-p yojson,str` flag says to link in two additional
packages, which are discussed below. The `--` says that the remaining command-line
arguments should be passed to the OCaml program being run rather than to `cs3110`.
## Grading issues
* **Compiling and running:** If we cannot compile and load `minimal.json` exactly
according to the instructions given above, your solution will receive minimal credit.
* **Code style:** Refer to the [CS 3110 style guide][style].
Ugly code that is functionally correct will nonetheless be penalized.
Take extra time to think and find elegant solutions.
* **Late submissions:** Carefully review the [course policies][syllabus] on
submission and late assignments. Verify before the deadline on CMS
that you have submitted the correct version.
* **Environment:** Your solution must function properly in the
[3110 virtual machine][vm], which is the official grading
environment for the course.
[style]: http://www.cs.cornell.edu/Courses/cs3110/2015fa/handouts/style.html
[syllabus]: http://www.cs.cornell.edu/Courses/cs3110/2015fa/syllabus.php
[vm]: http://www.cs.cornell.edu/Courses/cs3110/2015fa/vm.html
## Prohibited OCaml features
You may not use imperative data structures, including
refs, arrays, and mutable fields. Strings are allowed, but
the deprecated mutable functions on them are not.
You have not seen these features in lecture or recitation,
so we doubt you'll be tempted. Your solutions may not
require linking any additional libraries except `yojson`
and `str`.
## Design Hints
**Model:** One of the first things you should do is design the data type(s)
you will use to capture the state of the game: what the map is,
where the adventurer is located, what items are in the inventory, etc.
Those data type(s) are called the *model* of the game. It will be
difficult to implement your game engine if you repeatedly make changes
to the model during the implementation.
**REPL:** Think carefully about how to structure the main read-eval-print "loop"
of your game: keep the amount of code in it small by defining and
calling additional functions. Sketch out how you will implement the
eval phase with an eye toward whether your model is appropriate.
## Implementation Hints
[str]: http://caml.inria.fr/pub/docs/manual-ocaml/libref/Str.html
[yojson]: http://mjambon.com/yojson-doc/Yojson.html
**Text interface:**
All the I/O functions you need are in the `Pervasives`
module. The `Printf` module might be helpful for output.
The `Scanf` module is probably higher power than necessary
for input in this assignment.
**Parsing command-line arguments:**
Although the OCaml standard library provides a full-featured
`Arg` module for parsing command-line arguments, you won't
need it for this assignment. Rather, the following expression
evaluates to a `string list` containing all the command-line
arguments:
```
Array.to_list Sys.argv
```
The first element of the list will be the name of the program as it was
invoked; the second element is where you should expect to find the name
of the JSON adventure file to be loaded.
Using this expression will not be construed as violating the prohibition on
imperative data structures, even though it uses the `Array` module. In fact,
the expression does not cause any mutation or side effects.
**Parsing player commands:** Although you are unlikely to need
any functionality outside of the standard `String` module,
you are welcome to use the OCaml [Str library][str].
**Parsing adventure files:**
We recommend using the [Yojson library][yojson] for parsing JSON
adventure files. Although you are welcome to use any functionality
provided by the library, we suggest concentrating your attention
as follows:
* Use `Yojson.Basic.from_file` to read the contents
of a JSON file and construct a `Yojson.Basic.json` tree.
* Use the `Yojson.Basic.Util` module to extract information
from that tree.
[RWO chapter 15][rwo] has a tutorial on Yojson.
## Testing hints
Spend time developing a plan as to how you will test your game engine.
In your written file `a2.txt`, tell us about your testing plan.
**Model:** Write unit tests for functions that update your model, e.g.,
functions that implement the commands. You could hardcode some models
and commands for testing purposes, rather than relying on parsing code.
**Parsing:** Write unit tests for your player-command parser to ensure
that verbs and objects are correctly understood. Create small adventure
files to test your JSON parser. Consider writing a function that validates
an adventure file, such that if the adventure file is malformed, your game engine
detects that and notifies the user. Note: debugging parsers is hard, so leave
sufficient time for it.
**Adventures:** Write small adventure files to test specific commands and
functionality, e.g., the "go" command, the "take" and "drop" commands, etc.
In the best case, write code that automates the running of these adventures,
to frequently check whether any changes to your implementation have broken
these tests.
**Playtesting:** When your implementation is reaching maturity, get your
non-3110 friends to play an adventure using your game engine. A good game
that no one can learn to play is a bad game!
* What do your playtesters say about the game?
* Where and how do they run into trouble?
* Bear in mind not to coach them while they play.
* Don't take any criticism personally.
* Remind them to think out loud as the play—you want to know
everything going on in their head.
Gameplay will be at most a small part of the evaluation of your engine.
But bear in mind that really poor gameplay could make it difficult or
impossible for the grader to evaluate your engine.
## Adventure file format
Adventure files are formatted in JSON. We provide a JSON
schema for adventure files in `schema.json`. Your game engine is
required to be compatible with this schema, which defines the
required elements of the file and their names.
Using a JSON [schema validator][validator], you can check
the well-formedness of an adventure file again the schema.
This could be helpful in determining whether you have a bug
in your parser or a bug in your adventure file.
[schema]: schema.json
[validator]: http://www.jsonschemavalidator.net/
According to the schema, an adventure file contains four main entries:
* The rooms. Each room contains six entries:
- an id,
- a description,
- the items in it,
- the number of points exploring the room is worth,
- the exits from the room (an exit itself contains two entries: the
direction of the exit, and the room to which it leads), and
- the treasures that should be dropped in the room.
* The items. Each item contains three entries:
- an id,
- a description, and
- the number of points the item is worth when it is dropped in its designated room.
* The starting room (where the adventurer begins).
* The starting items (which are initially in the inventory).
The `minimal.json` adventure file included in the release
code provides an example of how adventure files must be
formatted in JSON. Note that, according to the schema, the
JSON property names in that file may not be changed.
Also note that many of the property values are strings,
which can contain arbitrary characters (including whitespace).
Here is some example code using `Yojson` that parses
some information from `minimal.json`:
```
(* OK: this function extracts the room ids *)
let extract_room_ids1 (t : Yojson.Basic.json) : string list =
let open Yojson.Basic.Util in
let room_array = member "rooms" t in
let room_ids = map (fun r -> member "id" r) room_array in
let room_id_list = to_list room_ids in
let ids = filter_string room_id_list in
ids
(* Better: this function uses the same algorithm as the first
* function, but it uses the |> operator which is more
* idiomatic. Note that [x |> f] is equivalent to [f x]. *)
let extract_room_ids2 (t : Yojson.Basic.json) : string list =
let open Yojson.Basic.Util in
t
|> member "rooms"
|> map (fun r -> member "id" r)
|> to_list
|> filter_string
(* Best: this function uses only exception-free filters *)
let extract_room_ids3 (t : Yojson.Basic.json) : string list =
let open Yojson.Basic.Util in
[t]
|> filter_member "rooms"
|> flatten
|> filter_member "id"
|> filter_string
let t = Yojson.Basic.from_file "minimal.json"
let ids1 = extract_room_ids1 t
let ids2 = extract_room_ids2 t
let ids3 = extract_room_ids3 t
```
## Git
The next assignment (A3) will require you and a partner to use [Git][git], a
distributed version control system. We strongly encourage
you to get started using it with this assignment, while you
are still working as an individual. To get started...
1. Do this [Git tutorial][git-tutorial].
2. Use what you have learned to create your own local
git repo. Throughout your development of
A2, commit your changes to it. Use those checkins
to provide checkpoints, in case you need to restore
your development to a previous point.
3. Consider using a remote repo to backup your development.
Both [GitHub][github] and [BitBucket][bitbucket] provide free private
repos to students.
**Private repos are of the utmost importance.
A public repo would share your code with the entire world, including your
classmates, thus violating the course policy on academic integrity.
Therefore we require that you keep all your CS 3110 related code in private repos.**
To create a private repository, simply make sure you select the "Private" radio
button when creating a new repository at GitHub, or check the
"This is a private repository" check box on BitBucket.
[git]: https://git-scm.com/
[git-tutorial]: https://try.github.io/
[github]: http://www.github.com
[bitbucket]: http://www.bitbucket.org
If you sign up for a GitHub or BitBucket account, make sure to sign up as a student
with your Cornell `.edu` email to take advantage of the free offers that are
available. BitBucket currently seems to account for this automatically
based on your email address. For GitHub, it seems you should currently
sign up at this link: [https://education.github.com/pack][github-pack].
[github-pack]: https://education.github.com/pack
## Karma
You are highly encouraged to go beyond the minimal
requirements for this assignment as described above.
But, no matter what you implement, be sure to maintain
compatibility with the basic adventure file format and
required commands. Note that it should be possible to add
additional information (e.g., properties and objects) to the JSON
file and still remain compatible with the required schema.
All your karma implementation must be located within `game.ml`
or `adv.json`; it cannot rely on additional files.
**Storyteller:**
Tell a great story with your own adventure. We might make the
best original adventure available to other students to play.
**Experienced Adventurer:**
* getting lost
* item sizes and weights, and inventory limits on those
* game save and restore
* consumable items (e.g., money, which could be earned and spent)
* time passing as adventurer explores, with effects occurring as a result
(e.g., the sun rises and sets, and what is possible changes as a result)
* other in-game characters (which could be stationary or could themselves
wander around) with which the adventurer can have conversations
**Seasoned Adventurer:**
* text-based graphics to display images of rooms, a map of the area
explored so far, etc.
* a level editor that makes it easy for designers to create adventure files
without having to write JSON themselves
**Master Adventurer:**
* a larger vocabulary of commands that enables designers to create puzzles
and players to solve them (e.g., manipulating items and rooms—locks and
keys are a good place to start)
* flexible command parsing so that users can type in interesting natural
language instead of two-word commands
* an automated bot that completes adventures without human assistance
* * *
**Acknowledgement:** Adapted from Prof. John Estell (Ohio Northern University).