# 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&mdash;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&ndash;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&mdash;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. <img src="camel_orange.png" height="40" width="40"> <img src="camel_black.png" height="40" width="40"> <img src="camel_black.png" height="40" width="40"> **Storyteller:** Tell a great story with your own adventure. We might make the best original adventure available to other students to play. <img src="camel_orange.png" height="40" width="40"> <img src="camel_black.png" height="40" width="40"> <img src="camel_black.png" height="40" width="40"> **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 <img src="camel_orange.png" height="40" width="40"> <img src="camel_orange.png" height="40" width="40"> <img src="camel_black.png" height="40" width="40"> **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 <img src="camel_orange.png" height="40" width="40"> <img src="camel_orange.png" height="40" width="40"> <img src="camel_orange.png" height="40" width="40"> **Master Adventurer:** * a larger vocabulary of commands that enables designers to create puzzles and players to solve them (e.g., manipulating items and rooms&mdash;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).