Assigned: 2020-09-16
Due Date: 2020-09-22 by 11:59PM U.S. Central Time
Get your copy of the assignment here: https://classroom.github.com/a/ovabUQ0B
You may encounter "Non-managed pom.xml file found". You should select "Add as Maven Project"
This week's assignment is another adventure game, but we've made it more interesting by creating a website that will allow you to play your very own game visually! You'll be able to return not just text, but videos & images by URL, so you can add some extra visual flair to your game.
Note: We strongly recommend starting on this assignment early. A lot of your work will be refactoring your last week's implementation to work with our new requirements. Our suggested day-to-day plan is to spend the first couple days making those refactors to your implementation, then spend a day or two working on the API, and the last day working on the database/cleaning up your code. This assignment is much more manageable if you work on it a bit each day rather than tackling it all at once :) And don't forget to ask questions & take notes in code review!
There is no autograder for this assignment, but you still must submit your GitHub repository to Gradescope.
After you've cloned your new repo, please move all of the files over from your previous week's adventure into this week's. That includes any other packages you might have made. For example, if your previous week's assignment looked like this:
- src
- main
- java
- student
- adventure
- ClassA.java
- ClassB.java
- cli
- ClassC.java
- resources
- something.json
... then this week's should look like:
- src
- main
- java
- student
- adventure
- ClassA.java
- ClassB.java
- cli
- ClassC.java
- server
- AdventureService.java
...
- resources
- something.json
- client.cert
...
Basically, you're merging the directory structure.
Last week, we asked you to build a text adventure game, but we also asked you to keep the engine as a discrete unit from the console interaction code. As you can imagine, there are tons of ways to play an adventure game. Some adventure games have fully-fledged 3D graphics, some are on 2D maps w/ sprites. While we're not going quite as far as these options, we are going to introduce a new UI to play your game with support for image/audio feedback! The best part: you'll be able to play your game right from the browser! We've created an Adventure website that supports playing a game through an Adventure API.
Wait, what is an API? API stands for Application Programming Interface, that is, an "interface" that allows two different applications to interact programmatically. You have never seen our Adventure website before, and your program currently does not support it. However, if we define an API contract between your game engine and our website, the two can work together to create a really cool experience!
We're not leaving you in the dust when it comes to creating this API: we've set up a local server for you to run that handles requests from the website, and we've written out all the methods you'll have to fill in. Your task is to define the interactions with your game engine.
After code review, you should have received some advice about your game engine's design. We recommend you start by implementing this feedback, as it will very likely help you define the API interactions later on. Things to be paying attention to:
new Scanner(System.in)
, and give back output through System.out.println()
. This won't fly for this assignment, as you're getting user input from the Command
class, and you're returning feedback through the GameStatus
class. Also don't forget to finish your custom feature this week!
After you have moved your student.adventure
package into the new project repo, you may have noticed a student.server
package. This is where the API is defined: it has a server, a resource with URL paths, a service, and some POJOs. Let's go through what each of these are so you're familiar when you see them:
AdventureServer
This class holds the server for running your API locally. You are free to look through it, but it should be 100% set up for you, and any changes might interfere with the normal behavior of the API. The server is hosted on https://0.0.0.0:8080
, which is port 8080
on your local machine. The base URL for the API is https://0.0.0.0:8080/adventure/v1
; that is to say, anytime we talk about an API path, it's always prefixed with this. The website knows how to work with exactly this URL, so please don't change it!
AdventureResource
This class holds methods that allow the API to access your "resource". Resources are to REST APIs as objects are to Object Oriented Programming: they represent a store of data with some methods to operate on said data. Because our API is a REST HTTP API, we invoke these methods through visiting certain URLs. That's how the website can get data from your API without having to run Java: it only needs to know the URL to call, and the server will package a response that the website can handle.
You'll see some methods with documentation explaining what they do, but you'll also see some @
annotations above all of them.
@GET/@POST
are HTTP verbs that respectively mean "access" a resource and "add to/change" a resource. When you see one of these above a method, it means the client needs to use that verb to call the method.
@Path
represents the relative URL path a client needs to visit to call this method. Remember, all of these paths are prefixed with the base path: in our case, https://0.0.0.0:8080/adventure/v1/
. So if you saw a method like
@GET
@Path("/ping")
public String ping() {
return "pong";
}
...then that method would be invoked if the client sent a GET request to https://0.0.0.0:8080/adventure/v1/ping
.
Lastly, you'll see some @Produces
and @Consumes
annotations. These say that the method should expect the request & response, respectively, to be of the specified type. We only use JSON to transfer our data, so you'll only see us using @Produces(MediaType.APPLICATION_JSON)
and/or @Consumes(MediaType.APPLICATION_JSON)
on our API methods.
You will not need to make many changes in here, but we will ask you to fill in the ping()
method with the response "pong"
. You will also see we use AdventureService
here.
AdventureService
This is a Java interface that defines a basic contract for your Adventure API on the Java side of things. The job of an interface class in Java is to force any class that uses it to implement the listed methods. You have certainly seen a very popular interface in Java before: List
! Interfaces allow us to treat different objects the same way when it comes to these methods. That's why I can write
List<String> strings = new ArrayList<>();
// or
List<String> strings = new LinkedList<>();
string.add("Hi there!");
The add
method is part of the List
interface, and if I changed what type of underlying list I was using, it would not make me go back and change the calls to add
.
We didn't force your game engine to conform to a certain class structure, so the biggest task of this assignment is for you to implement
this interface and create your own Adventure service that works with our API.
Just like last week, you are free to implement the API however you can, but because the website is static, you will need to make sure your API implementation follows the API contract. You should start out by implement
ing AdventureService
. Your class should look like
public SomeAdventureService implements AdventureService {
...
}
You may notice that IntelliJ gives you a red squiggly line under the first line. If you recall, a class that implements
an interface must define all the methods in the base interface. You can right click the red line, Show Context Actions > Implement methods
, and it will automatically implement basic versions of the methods for you.
Your API must support multiple instances of the game, such that two clients could play a game at the same time. Lucky for you, web servers handle this very nicely outright, but your service will need a way to store a game instance for each instance ID. The instance ID comes from the URL, and the AdventureResource
extracts it for you, and passes it to your service through methods.
Here are the methods you must implement, with a description:
void reset()
This method should clear out any instances of your adventure game, and reset the instance ID back to zero.
int newGame() throws AdventureException
This method is called when we want to create a new instance of a game. You should instantiate a new instance of the game, assign an id to it, and store it by its id.
GameStatus getGame(int id)
This method returns the current status for game instance with the ID of id
. This includes what should be displayed to the user, what commands are available, whether or not the last action was an error, the instance id, and any additional game state the user needs. The GameStatus
documentation more precisely explains its fields.
boolean destroyGame(int id)
This method destroys a game instance with the ID of id
, clearing it from your service permanently.
void executeCommand(int id, Command command)
This method informs the game instance with the ID of id
that a command has been issued to it. The command should be handled by the game instance, and the state for that game should be updated. The new state will be returned to the client the in the same way that getGame
does.
You must find a way to implement these five methods to work with your game engine in order for the API to properly work.
To check to see if everything is working as planned, you should just go to the src/main/java/student/Main.java
class and run main
. Then, visit https://localhost:8080/adventure/v1/ping. You should see something saying "Your connection is not private. danger blah blah security blah blah risk blah ...". You need to click "Advanced/Show Details" and proceed to "localhost (unsafe)/Accept the risk and continue/Visit this website". Then you'll see a blank screen or some text; this means you are ready to begin testing out the UI. To see more about why your browser is warning you, see the section on "More about servers, ..., SSL, ...".
To test with the UI, visit https://courses.engr.illinois.edu/cs126/fa2020/adventure/. It should automatically recognize your server and start a new instance if you've implemented the service correctly.
At the end of the day, you have created a game; now it's time to reward your players with high scores! The problem is that your current instances are somewhat ephemeral; that is to say, once the game exits/ends normally, you lose all of the data from that game, which is no good for high scores.
Enter databases! Using a database, we can persist high scores and retrieve them to show the client. You've spent some time learning about how SQL databases work, and now you get to apply it.
Your database will be a local, file-based SQLite database to minimize setup. If you follow these steps, you should have no issues getting the database set up:
View > Tool Windows > Database
+
button on the left, and select SQLite.+
button, and create the database file at src/main/resources
with the name adventure.db
.Driver: SQLite
link to download the SQLite driver. If so, do this and come back to the main window.Test Connection
. If all is well, it should show a green checkmark, and you should be able to start working!You will be using JDBC to interact with your database. There are generally two things you will be doing with JDBC: connecting once at the beginning of your service and executing queries throughout the game's runtime.
If you followed the steps above, you should be able to do this without a hitch!
private final static String DATABASE_URL = "jdbc:sqlite:src/main/resources/adventure.db";
private final Connection dbConnection;
...
dbConnection = DriverManager.getConnection(DATABASE_URL);
And that's it! You are connected to the database, and you'll want to use the Connection
object you get throughout your program to work with SQL.
Statement stmt = dbConnection.createStatement();
ResultSet results;
if (stmt.execute("SELECT name, score FROM leaderboard_pjg4")) {
results = stmt.getResultSet();
} else {
// Fail somehow
}
// Now work with the results set, if applicable.
In this case, I executed SELECT * FROM leaderboard
, but this could have been any valid SQL statement. As a result, I get a ResultSet
. Here is a helpful link on how to use a ResultSet
to extract the returned values.
Your adventure game must interact with the database in the following ways:
leaderboard_<yournetid>
table if it does not already exist. For instance, my table would be leaderboard_pjg4
. The table should have a VARCHAR(50)
field named "name", and an INTEGER
field named "score".AdventureService
you must implement that the API will use: Map<String, Integer> fetchLeaderboard()
. Read the Javadoc on this method to learn more.Your leaderboard is global for your game. That means that there isn't one leaderboard per instance; they all play on the same leaderboard.
For the project to be considered complete, your submission must (in the order we suggest you tackle this):
AdventureService
reset
to clear out instances of the adventure gamesnewGame
to create a new instance of your adventure game & store itgetGame
to return the current status of the gamedestroyGame
to purge a game from your storesexecuteCommand
to update your game's state based on the commandfetchLeaderboard
to return a mapping of player names to scores, in "high" order, from the leaderboardAs always, many points on this assignment are made up of aspects of your physical code and your design. Keep using correct style conventions according to the Google Java Style Guide, and think about how you can make a large application like this modular.
We're also paying a lot of attention to Object Decomposition on this assignment, as you will be working with a variety of objects, and you have the chance to improve your designs from last week. Make sure you are placing methods in the classes where they make the most sense, and also make sure data flows through your objects in a clean, extensible manner.
Choice of classes to create
Classes should be coherent units and have clear purposes
Classes should not be too “big” or be responsible for too much functionality
If a group of global/static functions are all operating on the same data/object, they should probably be bundled together into a class
As an example, the information stored in parallel arrays is often closely related, so it’s usually good design to group the information into a class and combine the parallel arrays into a single array
Placement of methods
Member variables stored by each class
Encapsulation
Your grades for each section of the rubric will be weighted as follows: