CS100 M Fall 2001
Project 5
Due Tuesday 11/13 at beginning of lecture

Goals

You will write a simple adventure game. Completing this assignment will help you learn about the following:

Instructions

First skim and then carefully read each section entirely before starting to develop the program! You should not expect to understand the whole project after just one reading. Be patient, read it a second time. This would be a fun and educational project if you follow all the instructions and approach it methodically. If you wait until a day or two before the due date to start, it will turn into a frustrating experience. Make sure you have read the assigned sections in Lewis & Loftus.

Feel free to write an elaborate private version that goes beyond our requirements for additional practice and your own enjoyment, but please submit a program that does not exceed our specifications. Do not use arrays. Use the Keyboard class to handle input.

Submission: Follow the instructions given in CS100M-->Homework-->Project Format. Print and submit all program listings and requested output.

Overview

In this adventure game, the game player moves around in a cave system through doors that connect rooms via tunnels, trying to avoid capture by a monster that moves from room to room on its own. The program will be written in an object-oriented style (using classes, objects, and methods). We provide some code, and you will complete the program by adding new code, some of which involves copying and adapting the supplied code. Modification and reuse of code is an important aspect of programming.

The cave system for the game consists of one or more rooms connected by tunnels that begin and end at doors in the rooms. The player and the monster move around the cave system by going through a door in a room and exiting the door at the end of the tunnel into a (different) room. Each room has at most three doors. Since it gets boring to always play the same cave system, we make the game customizable by having the program read the cave configuration from user input. The format of the input is a sequence of pairs of room ID#s. Each pair of room ID#s signifies that those two rooms are connected. The pair 0 0 terminates user input. For example, after specifying that 8 rooms are to be created, the input sequence

1 3 1 4 1 5 2 4 2 5 2 8 3 6 3 6 4 6 8 8 0 0
would create the cave configuration below:

Since the input is given using room ID#s, a search is required to find the room associated with a given ID#. The supplied code in Part A will clarify the search process.

The game has one player. At each step, the player types in a move to either stay in the current room (move 0) or go through door i (move i, 1<=i<=3). Such frameworks of reading commands (moves) from input and executing them are called command shells.

The game also has a monster. After each (attempted) move by the player, the monster randomly chooses to stay where it is or go through a door. When the monster and player end up in the same room, the monster captures the player and the game ends.

In the following sample session, we create the cave system shown above and move around until captured by the monster. <i> denotes door i.

How many rooms?  8
Type in room-room pairs, terminated by 0 0.
1 3 1 4 1 5 2 4 2 5 2 8 3 6 3 6 4 6 8 8 0 0
You are in Room 2 <1> --> 4 <2> --> 5 <3> --> 8
Monster is in Room 5 <1> --> 1 <2> --> 2
Which door?  1
You are in Room 4 <1> --> 1 <2> --> 2 <3> --> 6
Monster is in Room 5 <1> --> 1 <2> --> 2
Which door?  0
You are in Room 4 <1> --> 1 <2> --> 2 <3> --> 6
Monster is in Room 1 <1> --> 3 <2> --> 4 <3> --> 5
Which door?  3
You are in Room 6 <1> --> 3 <2> --> 3 <3> --> 4
Monster is in Room 4 <1> --> 1 <2> --> 2 <3> --> 6
Which door?  3
You are in Room 4 <1> --> 1 <2> --> 2 <3> --> 6
Monster is in Room 4 <1> --> 1 <2> --> 2 <3> --> 6
You are captured by the monster!  Game over!
This project is broken into Parts. Complete the Parts in order so as to avoid adding complication atop unfinished or buggy earlier Parts. Although it is tempting to do a lot at once, this is not a win because it makes it harder to track down errors since you must deal with more untested code. Careful incremental development will save you time.

Part A: Class Room

Class specification

Room is a class with the following interface (public methods):

Class Room has no other public fields or methods.
Class implementation

Below is a partial implementation of class Room:

/* Rooms connected by up to 3 doors */
import cs1.Keyboard;
public class Room {

   // Each Room r has a unique r.id > 0.  nextID is the ID# of the next Room
   // to be created.
   private static int nextID = 1;
   private int id;

   // For each Room r, r.previous refers to the Room created immediately
   // before r is created or has the value null if r is the first Room
   // created.  The most recent Room to have been created is called last.
   private static Room last;
   private Room previous;

   // Create a new Room with a unique ID# and a reference to the previous Room
   public Room() {
      id = nextID;
      nextID += 1;
      previous = last;
      last = this;
   }

   // Return the reference to Room with ID# targetID or return null if there
   // is no such room.
   public static Room findRoom(int targetID) {
      Room now = last;
      while (now!=null && now.id!=targetID)
         now = now.previous;
      return now;
   }

   // Test harness
   public static void main(String[] args) {
      // test constructor
         Room r1 = new Room();
   }
}
Take some time to study the code above before going on. Note that the expression this in an instance method of object o is a reference to object o itself. Simulate (by hand on paper) several calls of new Room() and draw a diagram showing the objects created. Do this for understanding; do not submit the diagram.
Create a project

Create a CodeWarrior project containing the initial implementation of the class Room given above. Check that it compiles.

Test harness
It is good practice for each class to have its own "test harness," a public method main that exercises the class. When you have completed Part A, you will have written a test harness that demonstrates the correctness of class Room. Do not discard this code; it becomes a permanent part of the class definition. To begin the test harness for class Room, instantiate two more Room objects and assign them to variables r2 and r3. Note that code has been provided already for instantiating one Room object, referred to by variable r1. Run the program and fix any runtime errors that arise.
Method toString
Every object has a public method toString() for converting it to a String. E.g., if r is a variable containing a reference to an object, then the statement System.out.println(r); automatically invokes r.toString() to get the String representation of r to print. If there is no definition of a public method toString(), some default text is printed. The definition of method toString() should have the following format:
  public String toString() {
      return
expression;
  }
It is good practice for each class to define an appropriate version of toString() for displaying its objects. For now, this should just be a String like "Room 5". Once we add doors later, the display should also include the doors and the rooms behind the doors, as shown in the example game session above.

Add code to the test harness to test toString using System.out.println to print each room that you created earlier. Do not call toString explicitly.

Method findRoom
Now verify that method findRoom works. By "compare," we mean that the test harness should compare the two values that are expected to be equal and print an error message if they are not.
Method getID
Field id is declared to be private. The test harness main in class Room can see it, but clients of Room (a different class) can't. Add a method getID to return the id of a room. Use method getID in the test harness as another way to verify the results of findRoom.
Method showRooms
It is useful for a class to be able to print a description of all its objects. Implement method showRooms to perform this action no matter how many Rooms have been created. Invoke showRooms from the test harness.

HINT: Method findRoom can visit every Room, if necessary, to search for the Room requested. Copy the code for findRoom and adapt it for showRooms.

Method connect
How can we represent a tunnel between two Rooms? We need for each Room to know there is a door to the other. In effect, each room has a uni-directional door to the other room, so one tunnel (a bi-directional door) is made by creating two unidirectional doors.

Declare three Room fields (instance variables) door1, door2, and door3, possibly leading to other Rooms. Update toString to print which Rooms, if any, the doors lead to. It is up to you whether to have an explanation like "<3> leads nowhere" or simply to not mention door 3 if it does not lead to a Room. The example game session shown uses the latter approach.

Define class method connect to add a bi-directional door between two Rooms. If method connect is called upon to add a fourth door in either Room, it should print a diagnostic message and make no connection.

There are a number of ways to connect two Rooms, but use good programming style no matter which way you choose. In particular, if you notice that you need some similar code more than once, eliminate this redundancy by pulling out the repeated code into another method and then call that method.

HINTS:

Test your code by creating various tunnels from the test harness. Verify that your code detects situations when the input data would require that a room have four or more doors. After creating the doors, print out descriptions of each room using method showRooms to verify that all appropriate doors were indeed created.
Method farRoom
Implement instance method farRoom according to its specification. Add code to the test harness to test farRoom.
Output of test harness
Congratulations! This is the end of Part A. You now have a test harness that demonstrates the correctness of class Room. Print and submit the output from the test harness.

Part B: Building a cave system

Do not do Part B until Part A is completely working.

Now that class Room has been implemented, it is time to become a client of Room and start writing the application code. The application should be in a separate class called Game that has its own main method. It is up to you whether to put the application, class Game, in the same file as class Room or in a separate file. In either case, change the Java target to class Game which contains the new starting point (main method).

Read a cave configuration from input
In the main method of the application, write code to read the cave configuration from user input. The input format is first a positive integer representing the number of Rooms to create followed by a sequence of pairs of room ID#s in arbitrary order, terminated by the pair 0 0. Each pair of room ID#s signifies that those two rooms are to be connected by a tunnel.

Test your code by supplying appropriate input to create various cave systems. Use method showRooms to produce output so that you can verify the cave configuration.

Part C: Command shell

Do not start Part C until Parts A and B are completely working.

In the main method of the application, after a cave configuration has been read in, write code for a command shell to let a player make moves. Represent the player by a Room variable holder the player's current location (a Room). To move the player, update the variable. Use method farRoom to get the target Room of each move. A move is given by the number of a door to go through or the number 0, meaning to stay in the current Room.

For now, the command shell should loop forever: print a description of the Room the player is in, prompt the player for a move, read a move, perform the move, and repeat forever.

Test your command shell on various caves. To terminate the program, type Ctrl-C.

Part D: Monster

Do not start Part D until Parts A, B, and C are completely working.

Represent the monster by a Room variable holding the monster's location. To move the monster, randomly choose a door number or 0 (to stay) and then move the monster accordingly.

How does the monster make the random choice? It rolls a 4-sided die with numbers 0, 1, 2, and 3 on the faces. The number 0 means staying in the current Room and the other numbers mean trying to go through the corresponding door if it exists. If the door doesn't exist, the monster stays in the current Room. (If you prefer, roll a die with d+1 sides where d is the number of doors in a particular Room.) Use a cast, (int), and the random method in the Math class to obtain random integers.

Make the adventure more exciting by adding the monster described above. Start the player in a random Room and start the monster in a random Room. To randomly start the player and the monster, generate random choices of room ID#s and then use findRoom to find the corresponding Rooms.

Change the command shell so that it prints the monster's location as well as the player's. The command shell should also be modified to print an appropriate message and stop the game when the monster and the player end up in the same Room. Do not use break or System.exit to exit the loop in the command shell.

Test your code on various cave configurations.

Output of Game
Congratulations! This is the end of the project! Start a game session using the given cave configuration and enter moves until the game ends. Print and submit the output.