Assigned: 2020-02-05
Due Date: 2019-02-11 by 11:59PM U.S. Central Time
Write a simple text-based "adventure game" that takes a description of the world in JSON and lets a user interactively navigate through the world. The game should print to and take input from the console.
Get your copy of the assignment here: https://classroom.github.com/a/Jadzir0H.
The rubric can be found here.
There is no autograder for this assignment, but you still must submit your GitHub repository to Gradescope.
Make sure IntelliJ recognizes the project as a Maven project. Jackson and
JUnit are already listed as dependencies in the pom.xml
file.
The JSON describing the game world can be found here: https://courses.grainger.illinois.edu/cs126/sp2020/resources/siebel.json
You should store JSON files in the src/main/resources/
folder.
You should avoid copy-pasting the entire JSON file into your code. You may hand-write small snippets of JSON for testing purposes and store them in your tests, or write your own JSON files and load them into your tests.
You'll be using Jackson to deserialize
JSON into objects representing the game world. You will need to be able to allow
the user to enter a filepath to the JSON file so that they can play a different JSON
adventure if they wish. If the user doesn't provide a filepath, default to the siebel.json
file.
Serializing (or marshalling) is the process of converting objects in code into a different representation for storage, whether that's JSON, YAML, binary data, or something else. Deserializing (or unmarshalling) is the reverse: converting serialized data into actual objects in your code. In this case, we are transmitting serialized data about an adventure map to you, so that you can deserialize it into Java objects for processing.
A schema is a definition of how to represent some data. For example, if we want to describe a course at UIUC in words we might say the following:
There is a natural translation between a schema describing objects stored in JSON and objects in Java.
The adventure map is stored as JSON, a data format popularized by the JavaScript programming language. Here's an example of some JSON, representing a group of courses at UIUC:
{ "courses": [ { "department": "Computer Science", "number": 126, "isDifficult": false, "isTimeConsuming": true, "professors": ["Craig Zilles", "Carl Evans", "Michael Woodley"] }, { "department": "Computer Science", "number": 421, "isDifficult": true, "isTimeConsuming": false, "professors": ["Elsa Gunter", "Mattox Beckman"] } ] }
Things to note about syntactically valid JSON:
{
and }
The JSON we're providing to you is already valid, so you don't need to worry about any of this unless you want to edit or write your own JSON for testing purposes. If you want to check that your JSON is valid, you can use a JSON validator like this one.
Rather than having you parse JSON all by yourself, you're going to use
the Jackson library to do it for you. Jackson is already included in
your project; all you have to do is import
and use it.
If we have some JSON like above, we need to define some Java classes that match the schema so that Jackson can deserialize the JSON into instances of those objects.
First, we define the outermost class with its fields:
public class CourseExplorer { private List<Course> courses; // -- Constructor and getter/setter methods omitted for brevity -- }
Then we define the inner class, with its own fields:
public class Course { private String department; private int number; private boolean isDifficult; private boolean isTimeConsuming; private String[] professors; // -- Constructor and getter/setter methods omitted for brevity -- }
Finally, we deserialize our JSON into an instance of CourseExplorer
:
courses.json
):File file = new File("src/main/resources/courses.json"); CourseExplorer explorer = new ObjectMapper().readValue(file, CourseExplorer.class);
String myJson = "..."; CourseExplorer explorer = new ObjectMapper().readValue(myJson, CourseExplorer.class);
Note that you can define your collections as either Object[]
or
List<Object>
at your preference, and Jackson will convert it to the
appropriate format.
If you need more documentation and examples on how to use Jackson, see the User's Guide.
@Before
As with the previous assignment, we expect you to write unit tests as
you develop. We're going to introduce a new feature of JUnit to make
writing some kinds of tests easier for you: the @Before
annotation.
Here's the motivation: your unit tests should be self contained so
that running one test doesn't influence the results of another. When
you have objects that are shared between tests, that can be hard to
guarantee. When you apply the @Before
annotation to a function in
your testing class, it tells JUnit that it should run that function
once before each and every one of your unit tests, so we can use an
@Before
-annotated method to reset any necessary state between test
cases.
Specifically, this is great for tests involving Jackson:
public class CourseExplorerTest { private ObjectMapper mapper; @Before public void setUp() { mapper = new ObjectMapper(); } @Test public void testCaseOne() { mapper.readValue(/* ... */); } @Test public void testCaseTwo() { mapper.readValue(/* ... */); } }
Now we don't have to repeat the line ObjectMapper mapper = new ObjectMapper()
in all
of our tests, and the ObjectMapper instance will be reset before each and
every one of our unit tests. For more information, see JUnit's
documentation.
Your project should accept the description of the game world using the following schema:
Direction := {
directionName : String
room : String // Name of the room this direction points to
}
Room := {
name : String
description : String
directions : Direction[]
items : String[] // Not used for this assignment; items in the room.
}
Layout := {
startingRoom : String // The name of the start room for the game
endingRoom : String // The name of the end room for the game
rooms : Room[]
}
Feel free to use any tools that convert the schema into Java code. For example, here is a great tool: http://www.jsonschema2pojo.org/. The options should be:
Starting with the startingRoom
, your game should display a message
describing the player's current environment. This should include three
parts, with a newline separating each:
endingRoom
, print a message telling the player
that they've reached the final room, and exit the program.Once this message is written to the console, your program should read a line of input from the user.
quit
or exit
, then you
should exit the program.go some_direction
, and some_direction
is the name of one of the directions they can travel from that
room, you should move the player in that direction and repeat the
output/input process. If some_direction
does not match any of
the possible directions, you should print I can't go some_direction!
I don't understand
, followed by what the
user typed surrounded in single quotes.Your code should be able to handle any case of input commands, but when echoing back a user's input, it should use the original capitalization.
You are on Matthews, outside the Siebel Center
Your journey begins here
From here, you can go: East
> go EAST
You are in the west entry of Siebel Center. You can see the elevator, the ACM office, and hallways to the north and east.
From here, you can go: West, Northeast, North, or East
> GO NoRtH
You are in the north hallway. You can see Siebel 1105 and Siebel 1112.
From here, you can go: South
> go TO HECK!
I can’t go TO HECK!
You are in the north hallway. You can see Siebel 1105 and Siebel 1112.
From here, you can go: South
> gophers ARE tasty!
I don’t understand ’gophers ARE tasty!’
You are in the north hallway. You can see Siebel 1105 and Siebel 1112.
From here, you can go: South
> go South
You are in the west entry of Siebel Center. You can see the elevator, the ACM office, and hallways to the north and east.
From here, you can go: West, Northeast, North, or East
> EXIT
Process finished with exit code 0
The primary deliverable for this assignment is the game program. The requirements for the program are listed below:
startingRoom
, and end the game
when the endingRoom
is reached.Other things we will take into consideration:
You are still expected to write unit tests for this project. Some parts of this project might be difficult to test; therefore, your challenge is to figure out how to structure your code in such a way that much of it can be tested.
Try to make commits at regular intervals. You want to leave a clear trail of discrete pieces of progress that you've made as you work. Commits are cheap! Make lots of them. Once again, try to involve testing in your code development process; you don't have to write all of your tests before the code, but you should try to develop your code alongside your tests and vice versa.
Use your best judgement to write the cleanest code you can; make sure you can justify any design decisions you make in code review.
This is a test of your problem-solving skills: if you don't know how to do something (for example, setting command line arguments in IntelliJ), you should try your favorite search engine. (Make sure you follow our policy on citing online code.) If you're still stuck, consider Piazza and/or Office Hours.