CSCI 151 Project 2: Adventure!
You are in MC Reynolds 315. Banks of desks stretch southwards
and upwards into the gloom. The blackboard broods, dark and empty,
like a giant watchful eye. The door is to the west.
There is an eraser here.
? take eraser
Taken.
? inventory
You are carrying:
an eraser
? go west
You are standing at the east end of the hallway on the third
floor of MC Reynolds. 315 is to the east, and Dr. Goadrich's
office is to the south. The hallway continues to the north and
west.
? go south
You start to go that way but then change your mind.
? go north
You are standing in a hallway on the third floor of MC Reynolds,
which extends to the south. To the east is 317; to the north,
a doorway; to the west, a bathroom.
? go east
You are in MC Reynolds 317. It is bright and sunny.
The chalkboard is covered with pictures and writing.
There is a stick of chalk here.
? drop eraser
Dropped.
Overview
In this project, you will implement an adventure game engine which can read in a description of a world and allow the user to explore it, as shown in the example above.
The goals of this project are to:
- give you more experience using common data structures such as lists, maps/dictionaries, and sets;
- and to give you experience putting together a medium-size application with multiple interacting classes.
Timeline
- You are required to turn in a design document by Monday, April 8. See the
Design Document
section below for more specific info on what should go in it. - You are required to turn in a basic working version by April 12; see below for the requirements.
- The final version is due in three weeks, on Friday, April 19, which should give you plenty of time. Get started early!
Don’t Panic
This project might at first glance appear huge, and there is certainly a lot to read and understand before you can get started. But (a) it is not quite as bad as it looks (trust me!) and (b) it is easy to develop incrementally, so you can get something very simple working first and then keep adding features as you go. Also, (c) you have plenty of time—get started early, ask for help when you are stuck, and keep plugging away at it.
As a point of reference, my solution (which implements all the required extensions, though not an additional extension) is about 500 lines of code, not counting the starter code. But keep in mind that a sizable fraction of that (perhaps half?) consists of blank lines, comments, and boring getter/setter methods.
A Note on Dictionaries and Sets
In this project you will almost certainly use a lot of dictionaries. For a fast Java dictionary implemention, you should use the HashMap
class. We don’t yet know how it is implemented (we will learn about it later in the semester) but you already know how to use it.
In addition, you can use HashSet
if you want to keep a collection of things in no particular order, and be able to easily add and remove items. For example, this sort of structure is ideal for keeping track of the player’s inventory.
A Note on Reading
Read this project description carefully! There are a lot of moving parts to this project and a lot of things that have to be explained. You will certainly not be able to remember everything and will need to refer back to this document frequently. I strongly suggest printing this project description on paper so you can more easily refer to it, write notes, and so on.
Requirements
Here are the basic requirements for the project, which must be completed individually.
- Submit a design document by Monday, April 8. See the Design Document section below for more information.
- By April 12 you must submit a basic working version which handles the following minimum requirements:
- Accept the provided world description file
Hendrix.yaml
, or any other file using the same format. (There is much more info on how to do this below.) Keep in mind that you are not writing a program to let the user play a particular adventure, but an engine which will allow the user to explore any world described in a file of the proper format. - Keep track of the player’s current location, and print out a description of the location and any items at that location.
- Allow the user to move around via the
go
command.
- Accept the provided world description file
Other basic requirements (the version you turn in on April 12 does not need to have these implemented yet, though I strongly suggest you try to get them implemented by then):
- Allow the user to pick up and drop items via the
take
anddrop
commands. - Keep track of what the user is currently carrying and allow them to view their current inventory with the
inventory
command. - Keep track of the user’s current score, and increase their score when they drop an item in that item’s goal location.
- Design your own world (by making another
.yaml
file) and submit it along with your project. These will be collected and published for the whole class (and future classes!) to enjoy.
A project which meets all the above basic requirements and uses good code style and documentation will receive a B.
Note that it is not a requirement to make your output look exactly like mine in the example above! Use your creativity and judgment. If you have an idea of something you would like to do differently and are unsure whether it would meet the requirements, feel free to ask.
To receive an A, you should do the above in addition to extending your engine with additional features. See the section on Extensions below for details.
Materials
I have provided some starter code you can use.
- Start by making a new Eclipse project. Then download each of these
.java
files and copy them into the default package in your project: - Download
Hendrix.yaml
and copy it into the top level of your project. - Download
snakeyaml-1.18.jar
and copy it into the top level of your project as well. Then go toProject
>Properties
>Java Build Path
, selectAdd JARs...
and navigate tosnakeyaml-1.18.jar
.
Below you will find more information about the contents of each provided file.
Direction.java
and Verb.java
Direction.java
represents a direction in which the player can move, such as “south”, “northwest”, or “up”. Verb.java
represents something the player can do, such as “go”, “take”, or “drop”.
Both are implemented as enumerations, declared using the enum
keyword, which is like a special class that provides a list of possible values. You can refer to these special values by prefixing the name of the enumeration: for example, Direction.SOUTHEAST
or Verb.GO
. Adding a new direction or a new kind of action (if you decide to do so while making your own extensions to the project) is as simple as adding a new name to the appropriate list.
Both Direction
and Verb
also provide a parse
method, which can be used to convert from a String
. For example, Direction.parse("up")
returns Direction.UP
.
Command.java
Command.java
provides the Command
class, which is responsible for taking a String
representing something the player types, and breaking it down into pieces such as a Verb
and/or a Direction
. In particular, a Command
may contain three pieces of information extracted from the player’s String
:
- A
Verb
- A
Direction
- The name of an item (called the
Noun
)
For example, Command c = new Command("look north")
creates a Command
object which breaks down the string look north
into pieces. Calling c.getVerb()
will return Verb.LOOK
, and c.getDirection()
will return Direction.NORTH
.
As another example, Command c = new Command("look at eraser")
will create a Command c
such that c.getDirection()
is Direction.UNKNOWN
, but c.getNoun()
is "eraser"
. Note that small words like "at"
are simply ignored.
There is a bit more you can do with Command
; take a look at the comments in Command.java
for more info.
Hendrix.yaml
The provided Hendrix.yaml
file describes a simple world centered around the third floor of MC Reynolds. Note that you do not have to write code to read Hendrix.yaml
(or any other .yaml
files)! Instead, you should use the provided SnakeYAML library to read them for you. See the section below on snakeyaml
and AdventureDemo.java
for an explanation of how to do this.
Hendrix.yaml
uses the standard YAML format, which is intended to be a simple, human-readable format for describing structured data.
- Key-value pairs (i.e. dictionaries) are specified as
key: value
. - Lists are specified as either
[item1, item2, ...]
or as a sequence of bullet points. - Dictionaries and lists can be nested using indentation.
Entries in the file are separated by three hyphens ---
. Let’s look at a single entry from Hendrix.yaml
as an example.
type: location
id: MC317
name: MC Reynolds 317
desc: You are in MC Reynolds 317.
longdesc: |
You are in MC Reynolds 317. It is bright and sunny.
The chalkboard is covered with pictures and writing.
exits:
west: hallwayEB
out: hallwayEB
items:
- name: stick of chalk
aliases: [chalk]
desc: |
It is a fat, high-quality piece of Japanese chalk,
with the word "Hagoromo" written on the side in
black letters. You seem to recall that Dr. Yorgey
always uses this kind of chalk. Perhaps he
accidentally left it here after teaching Algorithms.
goal: yorgey
goalmessage: |
You return the chalk to Dr. Yorgey, who seems very grateful.
score: 5
- name: blackboard
aliases: [board, chalkboard, pictures, picture, writing]
portable: false
desc: |
The blackboard has several pictures of graphs traced
over in yellow, green, blue, and orange chalk, along
with lots of writing. You can make out a few words
like "lemma" and "theorem", but the rest seems to be
about things like "flows" and "cuts" and "values".
You realize it is probably left over from Algorithms.
- name: sun
portable: false
desc: You could hurt your eyes that way!
As you can see, an entry consists of a top-level dictionary with keys like type
, id
, name
, and so on. Let’s go through each key one by one:
The
type
specifies what kind of thing is being described by this entry (a location, an item, a monster, a magic spell, …?, etc.). The onlytype
in the providedHendrix.yaml
file islocation
, but you could extend it with your own new kinds of entries. (Note that if you make your own new kinds of entries, you can use whatever keys make sense for that entry: everything else in the list below is specific tolocation
s. For example, atype: monster
entry would not need to haveexits
; it would have whatever keys and values are needed to describe a monster.)The
id
is a string used to uniquely identify this location. As we will see, it is used to specify which locations link to which other locations. (Hint: you will probably want to use a dictionary (HashMap
) mappingid
s to locations!)The
name
is a short name for the location. Unlike theid
, it does not have to be unique (for example, inHendrix.yaml
there are multiple locations which are all namedHallway
).The
desc
is a short (one-line) description of the location which can be displayed to the user.The
longdesc
is a longer description of the location which can be displayed to the player. Notice how it starts with the|
character, which is special YAML syntax indicating the start of a long string which extends over multiple lines.Up to this point, each key has been associated to a string value. However, the next key,
exits
, is associated to a value which is itself a dictionary. (In YAML, structured types (dictionaries or lists) can be nested inside others just by indenting them.) The dictionary associated toexits
maps directions to location IDs. This specifies which directions the player cango
from this location, and theid
of the location the user will end up in if they do. Notice that this means locations do not inherently exist in a grid or any other sort of physical space; they can be linked together however you want.In the given example, two directions (
out
andwest
) both lead to the same location (hallwayEB
); in general there could be more exits leading to different locations.- The last key is
items
, which contains a list of dictionaries. Each element of the list is marked by an indented hyphen, and contains a dictionary describing a single item which is at this location. Items can contain the following keys:name
: the name of the item. This should be unique.aliases
: a list of alternate names the player can use to refer to the item. If noaliases
key is present (as in the case of thesun
item in the example above), the item has no aliases.desc
: a description of the item.portable
: atrue
/false
value specifying whether the player can pick the item up. If noportable
key is present (as in the case of thechalk
), it defaults totrue
.goal
: this is theid
of alocation
where the player should take this item anddrop
it. In the above example we can see that the player should take thechalk
item to the location with the idyorgey
.goalmessage
: a message which can be shown to the player when they drop the item at thegoal
location.score
: the number of points the player receives when they drop the item at thegoal
location.
A few miscellaneous notes:
- What is the difference between
name
,desc
, andlongdesc
? It is really up to you, but here is how I use them in my implementation:name
is used when telling the user what they can see in a given direction:? look east You can see MC Reynolds 317 in that direction.
longdesc
is shown to the player the first time they visit a location, or when they explicitlylook
at their surroundings.desc
is shown to the player on subsequent visits to a location after the first (because it might be annoying to keep printing the same long description over and over every time they come back to a location).
- One last feature present in
Hendrix.yaml
is that some locations have the keyarticle
. This is a grammatical aid to make the output flow better, and indicates an article which can be prepended to the name of the location when using it in a sentence. For example, sinceMC Reynolds 317
has noarticle
, it would simply be displayed as:? look east You can see MC Reynolds 317 in that direction.
whereas the locations with namehallway
specifyarticle: a
, and will thus be displayed as:? look east You can see a hallway in that direction.
rather than"You can see hallway in that direction."
, which sounds wrong.
snakeyaml-1.18.jar
and AdventureDemo.java
You do not have to write code to read in YAML files! Instead, you should use the provided SnakeYAML library to read them for you. This will return some structured objects from which you can extract the information you want. Doing things this way has several advantages:
- You do not have to write your own parser (which can be rather tricky). All the parsing is handled for you by SnakeYAML.
- It is easy to extend the format with your own new features.
- It makes it possible to exchange world files with others, even if your adventure engines support different sets of features. Your engine can simply ignore entry types or keys that it does not know what to do with.
AdventureDemo.java
provides an example of how to use SnakeYAML to parse a .yaml
file, and how to extract information from its output. It also demonstrates how to read user input and parse it using the provided Command
class. Feel free to use AdventureDemo.java
as the basis for your own implementation.
Design document
By April 8, you should turn in a design document planning out the overall structure of your implementation. Your design document should contain a list of classes you plan to develop. Under each class, list:
- The fields the class will contain, along with their types and a description of what they will be used for
- Any methods the class will have (including constructors). Make sure to list the argument types and return type of each method, and a short description of what the method does or what it will be used for.
Think about the different entities you will need, and what information they will keep track of. As one example, you will most likely want a Player
class, which contains:
- The ID of the player’s current location (
String
) - A set of items representing the player’s inventory (
HashSet<Item>
) - The player’s current score (
int
)
You may think of other things that should go in the Player
class as well. Of course, you should also describe the methods that Player
will have.
A good implementation will probably have something like 5-10 classes (including the provided starter code), although yours may have more if you add extra features.
Basic working version
By April 12, you should turn in a basic working version which:
- Reads the provided world description file
Hendrix.yaml
, and loads the information into appropriate objects. - Keeps track of the player’s current location, and prints out a description of the location and any items at that location.
- Allows the user to move around via the
go
command.
Extensions
In order to get an A on the project, you should implement the required extensions in addition to at least one additional significant feature. See the Additional extensions
section below for some ideas.
Required extensions
- Add a
look
command.The basic version takes the name of an item and simply prints out a description of the item. Note you should only print a description of items which are either in the current location or in the player’s inventory. ``` ? look at eraser A dusty old eraser.
? look at chalk I don’t see one of those here. ```The
look
command by itself should simply print a description of the current location. ``` ? lookYou are standing at the east end of the hallway on the third floor of MC Reynolds. 315 is to the east, and Dr. Goadrich’s office is to the south. The hallway continues to the north and west.
? drop eraser Dropped.
? look
You are standing at the east end of the hallway on the third floor of MC Reynolds. 315 is to the east, and Dr. Goadrich’s office is to the south. The hallway continues to the north and west.
There is an eraser here. ```Finally, extend the
look
command to take a direction, which prints out the name of the location in that direction (if any). ``` ? look west You can see a hallway in that direction.? look east There’s nothing to see that way. ```
After visiting a location once, on subsequent visits just print the
desc
instead of thelongdesc
. ``` You are in MC Reynolds 315. Banks of desks stretch southwards and upwards into the gloom. The blackboard broods, dark and empty, like a giant watchful eye. The door is to the west.There is an eraser here.
? go west
You are standing at the east end of the hallway on the third floor of MC Reynolds. 315 is to the east, and Dr. Goadrich’s office is to the south. The hallway continues to the north and west.
? go east
You are in MC Reynolds 315.
There is an eraser here.
? look
You are in MC Reynolds 315. Banks of desks stretch southwards and upwards into the gloom. The blackboard broods, dark and empty, like a giant watchful eye. The door is to the west.
There is an eraser here. ```
Distinguish between portable and non-portable items; only allow the user to pick up portable items. In addition, non-portable items should not be listed when telling the user what items are in a particular location. ``` ? look at blackboard The blackboard is empty. It looks as though it has been recently washed.
? take sock I don’t see one of those here.
? take blackboard You can’t take that! ```
Allow the user to refer to items by any of their listed
aliases
. ``` ? look at board The blackboard is empty. It looks as though it has been recently washed.? look at chalkboard The blackboard is empty. It looks as though it has been recently washed. ```
Implement a
Available commands: gohelp
command. For example, it might look like this (but might be different depending on what other features you add to your engine): ``` ? help Explore your surroundings. You can get points by taking certain objects to certain other locations.- move in a certain direction look - look at your surroundings look
- see what location lies in a given direction look [at]
- look at an object in your location or inventory take - pick up an object drop - put down an object inventory - see what you are carrying score - see your current score help - show this help message quit - quit
- see what location lies in a given direction look [at]
- move in a certain direction look - look at your surroundings look
Additional extensions
Most other extensions will require you to extend the .yaml
file format with additional keys or entry types. Fortunately, that is as easy as simply adding them, and then looking for the new key or entry type in your engine. For example, suppose you wanted to add a weight
key to items. You would simply add something like weight: 10
as a key-value pair in the description of an item; then, in your engine, you could do something like
Integer w = itemMap.get("weight");
int weight = (w == null) ? 5 : w;
In this example, I chose to use a default weight of 5 if the value w
is null
(meaning the key weight
was not found). This means my engine will still be compatible with .yaml
files that do not specify a weight
key for items; it will simply use a default weight.
Here are some ideas for other ways you could extend your engine and the types of worlds it can support. Of course, you should not feel limited to these. Use your imagination! Please come by and discuss your ideas with me; I am happy to help you brainstorm and plan how to implement them.
- Add other characters and/or monsters which can inhabit the world along with the player, and possibly even move around. You could, for example, add a new key
characters:
to locations, much like the existingitems:
key, to specify characters which start out at that location. Alternatively, you could create a new kind of top-level entry such astype: character
which describes a character. Depending on what kind of characters you want to create, you would have to decide what keys and values to use to describe them.- If you want the player to be able to fight the other characters, you could give them keys such as
health
,attack
, etc. - If you want the player to be able to converse with the other characters, you could specify a conversation tree of statements and potential responses the player can choose among.
- If you want the player to be able to fight the other characters, you could give them keys such as
Add a combat system that allows the player to fight monsters.
Limit the amount the player can carry; give each item a weight.
Items can have special actions associated with them which print special messages, give the player points, or cause other things to happen in the world. For example, a
shovel
item might have a specialdig
action associated with it, which allows the player to reveal special items or dig a tunnel to a previously inaccessible location.Add simple conditionals that allow the world to change in certain ways depending on the actions of the player. For example, certain directions might be locked unless the player is carrying a certain item; certain messages might change depending on what has happened so far.
What to Hand In
For your final submission, you should turn in a .zip
file containing:
All the
.java
files necessary to run your project. Please include even the provided starter code, even if you did not change them, to make it as easy as possible for me to run your code.Any
.yaml
world files you created (you must create at least one). If you added any special features to your engine, this is your opportunity to show them off.A
README.txt
file containing a description of your engine, any.yaml
files you submitted, and any special features you added that I should look for.