Regardless of whether you work alone or with a partner, you are encouraged to discuss this assignment with other students in the class and ask and provide help in useful ways.
Remember to follow the pledge you read and signed at the beginning of the semester. For this assignment, you may consult any outside resources, including books, papers, web sites and people, you wish except for materials from previous cs1120, cs150, and cs200 courses. You may consult an outside person (e.g., another friend who is a CS major but is not in this class) who is not a member of the course staff, but that person cannot type anything in for you and all work must remain your own. That is, you can ask general questions such as "can you explain recursion to me?" or "how do arrays work in Java?", but outside sources should never give you specific answers to problem set questions. If you use resources other than the class materials, lectures and course staff, explain what you used in your turn-in.
You are strongly encouraged to take advantage of the scheduled help hours and office hours for this course.
For this problem set, you will create your own adventure game. Our game takes place in a far-off fictional land known as The University in Charlottansville, East Virginia. Set on bucolic grounds of rolling hills, grassy fields, and red-brick buildings, the characters in our adventure game will take us to imaginary places like the Cabal Hall, Cdrs Hill, the Recursa, and Oldbrushe Hall. All of the places and characters in our game are purely fictional (although most are not purely functional, since they use mutation). Any similarity to real persons or places, of course, is purely coincidental.
Programming an adventure game involves modeling objects in a fictional world. Hence, you will build your adventure game using techniques known as object-oriented programming.
HashMap<String, Integer> ages = new HashMap<String, Integer>(); ages.put("Charles", 21); ages.put("Dru", 21); ages.put("Jonathan", 21); String Lenny = "Lenny"; int ageLenny = 20; ages.put(Lenny, ageLenny); ages.put("Mohsin", 25);The first line creates our HashMap. <String, Integer> tells Java that we want our HashMap to have Keys of type String and Values of type Integer. The next few lines uses the .puts(Key, Value) method to put some Key/Value pairs into our HashMap. Note that we can add variables to our HashMap as well, as long as they satisfy the type requirements of our HashMap. Now let's see how we would use this.
System.out.println(ages.get("Charles")); //prints out 21We use the .get(Key) method to get the value for a certain Key. What if we wanted to modify our HashMap?
ages.remove("Charles"); // The Key/Value pair "Charles"/21 is no longer in our HashMap ages.put("Lenny", 22); // The Key "Lenny" now is associated with the value 22We use the .remove(Key) method to remove a key/value pair from our HashMap. The remove method takes the Key you want to remove as its parameter. You cannot have duplicate keys in your HashMap The .put(Key, Value) method, instead, modifies the value of the key that's already in the HashMap.
public static HashMap<String, String> makeHashMap(ArrayList<String> keys, ArrayList<String> values) { HashMap<String, String> return_value = new HashMap<String, String>(); //Your code here... return return_value; }For example, if keys = {"Charles", "Dru", "Jon", "Lenny", "Mohsin"} and values = {"Hearn", "Knox", "Burket", "Li", "Ahmed"}, then makeHashMap would return a HashMap that mapped a TA's first name to his last name. [Hint]
All of the objects in our game will be instances of subtypes of the SimObject class, defined in Resources. The SimObject class defines this constructor:
public SimObject(String name_) { name = name_; // We use the underscore_ as a convention to distinguish // between the passed-in formal parameter (name_) and // the object field (name). }(The SimObject class defines some other methods, not shown.)
All objects in our fictional world have names (rumors about a "Nameless Field" are as yet unsubstantiated), so the constructor takes a name parameter.
We also define several subclasses of SimObject (in Resources):
OwnableObject thing = new OwnableObject("book"); System.out.println(thing.is_ownable());
A Person object has two addition instance variables, which we initialize in the constructor:
public class Person extends MobileObject { ArrayList<OwnableObject> possessions; // what am I carrying? double restlessness; // do I stand still? // The constructor for the Person class public Person(String name) { // Start with the constructor for the superclass super(name); // Initialize the possessions list so that it // starts with no items possessions = new ArrayList<OwnableObject>(); // A person's restlessness is 0 by default restlessness = 0.0; }The constructor first calls the superclass' constructor using the keyword super, and then initializes the new instance variables.
The Person class defineds several methods for manipulating people including:
say(String utterance)Note that many of the provided Person methods only work for objects that have been installed in a place (using the install method provided by its superclass PhysicalObject).Say the words passed in as the parameter (as a string).look()Report on the immediate surroundings (only works for installed people).have_fit()Throw a temper tantrum.go(String direction)Move in the indicated direction. The direction is a string representing the direction, such as "north" (only works for installed people).
The Place subclass is defined in Resources. Its constructor is:
public Place(String name) { // Since Place is a SimObject we must first call // the constructor for SimObject super(name); // Initialize the things and neighbors tables so // that they start empty this.things = new ArrayList<PhysicalObject>(); this.neighbors = new HashMap<String,Place>(); }A Place object maintains a HashMap of the neighbors of the place. In this case, we use the direction (a string) as the HashMap key. Hence, we can look up the neighboring place in a given direction by looking up the direction in the neighbors HashMap:
public Place neighbor_towards(String direction) { if (neighbors.containsKey(direction)) { return neighbors.get(direction); } else { return null; } }The .containsKey(Key) method checks if the neighbors HashMap contains the key, Key.
A Place object also maintains a list of the things in a place (this can include any SimObject object, such as a Person). It provides methods for keeping track of the things in a place:
ArrayList<PhysicalObject> get_things()Returns a list of the things in this place. The list is a new copy of the list of things (so mutating it does not effect the things in the place).
void add_thing(PhysicalObject thing)Modifes the place by adding the thing to the place.
boolean has_thing(PhysicalObject thing)Returns true if the place has the thing; otherwise, returns false.
void remove_thing(PhysicalObject thing)If the place does not contain this thing, produces an error. Otherwise, removes this thing from the place.
In Main.java, create two Place objects and make them neighbors. Install a Person object in one of your places. Create an OwnableObject and install it in the same place. Ask the person to look, then take the thing, then ask the person to go to the neighboring place. Use get_things to inspect the things in both places you created. (Try this yourself first, but if you are stuck see the hints.)
We can experiment with our world by creating a new World object.
World cville = Library.setup_Cville();
and then asking objects in our world to do things. This code is in Main.java.
For example:
Our world needs some people in it, so let's create one, and install him in our world:World cville = Library.setup_Cville(); Place cabal = cville.get_place("Cabal Hall"); System.out.println(cabal.get_exits()); //[south, north] System.out.println(cabal.neighbor_towards("north").name); //Bart Statue
We can also make things and add them to our world. For example, let's create a sandwich and install it where JT is now. Then, Mr. Thomerson looks around, sees the sandwich and takes it:Person JT = new Person("Jeffus Thomerson"); //<simobject: Jeffus Thomerson>: Installing at <place: Cdrs Hill> cville.install_thing(JT, "Cdrs Hill"); //<simobject: sandwich>: Installing at <place: Cdrs Hill>
Try playing the adventure game. In Main.java, create people and make them move around. Get a feel for how objects work before moving on.OwnableObject sandwich = new OwnableObject("sandwich"); cville.install_thing(sandwich, "Cdrs Hill"); JT.look(); //At Cdrs Hill: Jeffus Thomerson says -- I see sandwich //At Cdrs Hill: Jeffus Thomerson says -- I can go north, south JT.take(sandwich); //At Cdrs Hill: Jeffus Thomerson says -- I take sandwich
Our Person object provides a say(String utterance) method:
Person bill = new Person("Bill"); bill.say("To apply or to eval, that is the question"); //Bill says -- To appy or to eval, that is the question.
What if we have lots of different kinds of people and we want to make them speak different ways. For example, a Lecturer is a kind of Person, except that he can lecture as well as say. When lecturing, the lecturer follows every comment with "you should be taking notes".
We can make a Lecturer a subclass of Person that defines a new method (this is defined in People):// Lecture is just like "say", but with an extra // comment at the end public void lecture(String stuff) { say(stuff + " - you should be taking notes"); }When a method is invoked on a Lecturer object, first it will apply the subclass method if there is one. In this case, say is not definied in the Lecturer class. If the Lecturer subclass does not define the method, evaluation will the continue to look for the method in the superclass. The next superclass is Person, which does define a say method, so it will invoke that.
Use the commented code in Main.java to test question 3.
Professor ev = new Professor("Evan Davis"); v.profess("there exist finitely-describable numbers that are not computable"); // Evan Davis says -- It is intuitively obvious that there exist finitely describable numbers that are not computable - you should be taking notes ev.lecture("There exist numbers that have no finite description"); // Evan Davis says -- There exist numbers that have no finite description - you should be taking notes
Use the commented code in Main.java to test question 4.
Dean dean = new Dean("Deany Duck"); dean.say("Hello"); // Deany Duck says -- Hello - We need your help, please send money now. dean.profess("You should study all the liberal arts."); //Deany Duck says -- It is intuitively obvious that You should study all the liberal arts. - you should be taking notes - We need your help, please send money now.
Note that when we invoke the profess method, the Professor class's profess method is used. When that method invokes lecture, the Lecturer class's lecture method is used. When that method invokes say, the Dean class's say method is used. In each case, searching for a method always begins with the object's most specific subclass (that is, the class used to create the object), and searches up the superclasses until the appropriate method is found.
public class Person extends MobileObject { ArrayList<OwnableObject> possessions; // A list of the Person's belongings // How inclined a person is to move in a given time step. // If set to 1.0, the person will always move. If set to 0.0, // the person never moves, and if set to 0.3, the person will // move 3 times out of 10 double restlessness; // The constructor for the Person class public Person(String name) { // As usual, we start with the constructor for // the superclass super(name); // Initialize the possessions list so that it // starts with no items possessions = new ArrayList<OwnableObject>(); // A person's restlessness is 0 by default restlessness = 0.0; } // Return a copy of the list of the Person's possessions. // Why a copy? We may want to perform calculations on the // possession list once we have it. If we didn't return a copy, // any changes would affect the original list public ArrayList<SimObject> get_possessions() { return (ArrayList<SimObject>) possessions.clone(); } // Tell the Person to declare what possessions they have public void display_possessions() { say("I have " + possessions.toString()); } ...
A person has an instance variable possessions that is an ArrayList of objects the person is carrying (we'll get to the restlessness instance variable later). The method get_possessions can be used to see what a person is holding. Note that instead of returning possessions directly, get_possessions returns a new copy of possessions by calling the clone method (don't worry too much about the syntax here). This means if the caller of Person.get_possessions mutates the resulting list, it does not change the actual state of the Person object. This preserves encapsulation, since it means that the only way a Person object's possessions can be modified is by using the Person methods.
Some of the students in Charlottansville have a strange habit of getting undressed and running around the Green, so the Student class needs an instance variable dressed that indicates whether or not the student is clothed. Initially, all students are dressed, so the dressed variable is initialized to true. So, your Student class needs to override the constructor for Person, similarly to how the Person class overrides the constructor of MobileObject.
In addition, your Student class should implement three methods:
Use the commented code in Main.java to test question 5.
Student aph = new Student("Alyssa P. Hacker"); aph.install(new Place("The Green")); System.out.println(aph.is_dressed()); //true aph.get_undressed(); //At The Green: Alyssa P. Hacker says --- Brr! It's cold! aph.get_undressed(); //At The Green: Alyssa P. Hacker says --- I have no clothes left to remove. System.out.println(aph.is_dressed()); //false aph.get_dressed(); //At The Green: Alyssa P. Hacker says --- I feel much better now. System.out.println(aph.is_dressed()); //true
The University administration does not condone streaking, and has decided to strategically place police officers on The Green to apprehend streakers.
We have provided (in the People package) the beginnings of a PoliceOfficer class. A PoliceOfficer is a special type of Person, with the ability to arrest other people.
The provided constructor takes an additional parameter jail, which is a Place that arrestees are sent to, and stores the value passed in as jail in an instance variable:
public class PoliceOfficer extends Person { public Place jail; public PoliceOfficer(String name, Place jail_) { super(name); set_restlessness(0.5); jail = jail_; } ...For example, here is how a PoliceOfficer object is constructed in play_game_as_APH (defined in Library.java):
PoliceOfficer krumpke = new PoliceOfficer("Officer Krumpke", cville.get_place("Jail"));This works since cville is initalized as setup_Cville() which produces a world that includes a Jail place.
If the criminal is not in the same location as the PoliceOfficer, the arrest method should not perform an arrest, but instead the officer should say something about not being able to arrest someone who is not at the same location.
If the criminal is at the same location, the officer should say that the criminal is under arrest, and read them their Miranda rights, and move the criminal to the jail (use the Person method move that takes a location as its input).
World cville = Library.setup_Cville(); PoliceOfficer krumpke = new PoliceOfficer("Officer Krumpke", cville.get_place("Jail")); Student aph = new Student("Alyssa P. Hacker"); cville.install_thing(aph, "The Green"); //<simobject: Alyssa P. Hacker>: Installing at <place: The Green> cville.install_thing(krumpke, "The Green"); //<simobject: Officer Krumpke>: Installing at <place: The Green> krumpke.arrest(aph); //At The Green: Officer Krumpke says --- Alyssa P. Hacker, you are under arrest! //At The Green: Officer Krumpke says --- You have the right to remain silent, call methods and mutate instance variables. //Alyssa P. Hacker moves from The Green to Jail //At Jail: Alyssa P. Hacker says --- It's lonely here... krumpke.arrest(aph); //At The Green: Officer Krumpke says --- Alyssa P. Hacker is not here
We do this by creating a list of all the characters to be moved by the computer and by simulating the passage of time in our World class. The World includes a things instance variable that maintains a list of all the things in our world. The tick method invokes the tick method on each object that provides a tick method:
public class World { public String name; public HashMap<String,Place> places; // "places" maps place names to their corresponding Place objects public HashMap<String,PhysicalObject> things; // "things" maps thing names to their corresponding Thing objects // Constructor for World class (takes only the name of the world) public World(String name_) { name = name_; // Initialize the places and things maps so that they are initially empty places = new HashMap<String,Place>(); things = new HashMap<String,PhysicalObject>(); } ... // Put an object at a certain location public void install_thing(PhysicalObject thing, String pname) { assert has_place(pname); // Check that the desired location exits in this World // Don't want to have too objects with the same name if (has_thing(thing.name)) { throw new Error("Attempt to add thing with duplicate name."); } // Add the object to the "things" table things.put(thing.name, thing); // Call the object's "install" method thing.install(get_place(pname)); } ... // Run a time step in our simulation. This amounts to simply // running the tick() function of every object in our World. public void tick() { // Go through every thing in the "things" list and call // tick() on each one for (int i=0; i<things.size(); i++) { // Get the i-th element from the set of values in the HashMap PhysicalObject obj = Library.HashMapGet(things,i); obj.tick(); } }
People hang about idly until they get bored enough to do something. To account for this, we give people a restlessness instance variable that indicates how likely they are to get bored enough to move randomly. A person will move in a random direction with restlessness probability with each clock tick. For example, if restlessness is 1.0, the person will move randomly every clock tick. If restlessness is 0.5, the person will move half the time (but not necessarily every other tick, since the decision whether to move or not is random). If restlessness is 0.0, the person will never move randomly. The Person class defines a method set_restlessness(double value) that sets the restlessness instance variable to the input value.
Before tackling the next question, we need to learn a little bit more about the type rules in Java. Suppose we have the following line of code:
PhysicalObject obj = new MobileObject("widget");Since obj is a MobileObject, we might want to call the change_location method defined in the MobileObject class. Because we defined obj as a PhysicalObject, however, we can only call methods that work on all PhysicalObjects:
PhysicalObject obj = new MobileObject("widget"); obj.install(new Place("The Green")); obj.change_location(new Place("The Recursa")); /* Exception in thread "main" java.lang.Error ... The method change_location(Place) is undefined for the type PhysicalObject */To work around this issue, we use casting to tell Java that obj is indeed a MobileObject. In order to cast obj to a MobileObject, we simply put (MobileObject) before we refer to obj:
((MobileObject) obj).change_location(new Place("The Recursa")); // <simobject: widget>: Installing at <place: The Green>We can only cast an object to a specific type if that object is indeed an instance of the corresponding class. We can check whether an object is an instance of a specific class using instanceof:
if (obj instanceof MobileObject) { ((MobileObject) obj).change_location(new Place("The Recursa")); }You may need to use both casting and instanceof in your answer to the next question.
Here are some sample interactions, but note that your results will be different since the Person.tick method makes the person move randomly (for more controlled testing, you might want to invoke krumpke.set_restlessness(0)).
PoliceOfficer krumpke = new PoliceOfficer("Officer Krumpke", cville.get_place("Jail")); Student aph = new Student("Alyssa P. Hacker"); cville.install_thing(aph, "The Green"); //<simobject: Alyssa P. Hacker>: Installing at <place: The Green> cville.install_thing(krumpke, "The Green"); //<simobject: Officer Krumpke>: Installing at <place: The Green> cville.tick(); //At The Green: Officer Krumpke says --- No one to arrest. Must find donuts. //Officer Krumpke moves from The Green to The Recursa //At The Recursa: Officer Krumpke says -- It's lonely here... aph.get_undressed(); //At The Green: Alyssa P. Hacker says -- Brr! It's cold! cville.tick(); //At The Recursa: Officer Krumpke says -- No one to arrest. Must find donuts. //Officer Krumpke moves from The Recursa to The Green //At The Green: Officer Krumpke says -- Hi Alyssa P. Hacker cville.tick(); //At The Green: Officer Krumpke says -- Alyssa P. Hacker, you are under arrest! //At The Green: Officer Krumpke says -- You have the right to remain silent, call methods, and mutate instance variables. //Alyssa P. Hacker moves from The Green to Jail //At Jail: Alyssa P. Hacker says -- It's lonely here... cville.tick();
Note that Alyssa is not arrested on the tick() where Officer Krumpke moves to The Green and greets Alyssa. The Officer only makes an arrest if there is a streaking student at its location at the beginning of the tick. (Our PoliceOfficer is friendly, but a bit slow to observe her surroundings.)
The World class defines a method play_interactively(Person character) that provides a better interface to playing the game. The play_game_as_APH() procedure (defined in Library.java) sets up Cville, installs two students and one restless police officer in our world, and starts playing interactively as Alyssa P. Hacker.
Here's what a typical game might look like:
Library.play_game_as_APH();Output:
<simobject: Alyssa P. Hacker>: Installing at <place: The Green> <simobject: Ben Bitdiddle>: Installing at <place: Cdrs Hill> <simobject: Officer Krumpke>: Installing at <place: Bart Statue> what next? look At The Green: Alyssa P. Hacker says -- I see nothing At The Green: Alyssa P. Hacker says -- I can go west, north, south At Bart Statue: Officer Krumpke says -- No one to arrest. Must find donuts. Officer Krumpke moves from Bart Statue to Cabal Hall At Cabal Hall: Officer Krumpke says -- It's lonely here... what next? go north Alyssa P. Hacker moves from The Green to The Recursa At The Recursa: Alyssa P. Hacker says -- It's lonely here... At Cabal Hall: Officer Krumpke says -- No one to arrest. Must find donuts. what next? get undressed At The Recursa: Alyssa P. Hacker says -- Brr! It's cold! At Cabal Hall: Officer Krumpke says -- No one to arrest. Must find donuts. Officer Krumpke moves from Cabal Hall to South Green At South Green: Officer Krumpke says -- It's lonely here... what next? go south Alyssa P. Hacker moves from The Recursa to The Green At The Green: Alyssa P. Hacker says -- It's lonely here... At South Green: Officer Krumpke says -- No one to arrest. Must find donuts. what next? go south Alyssa P. Hacker moves from The Green to Bart Statue At Bart Statue: Alyssa P. Hacker says -- It's lonely here... At South Green: Officer Krumpke says -- No one to arrest. Must find donuts. what next? look At Bart Statue: Alyssa P. Hacker says -- I see nothing At Bart Statue: Alyssa P. Hacker says -- I can go north, south At South Green: Officer Krumpke says -- No one to arrest. Must find donuts. what next? look At Bart Statue: Alyssa P. Hacker says -- I see nothing At Bart Statue: Alyssa P. Hacker says -- I can go north, south At South Green: Officer Krumpke says -- No one to arrest. Must find donuts. Officer Krumpke moves from South Green to Cabal Hall At Cabal Hall: Officer Krumpke says -- It's lonely here... what next? look At Bart Statue: Alyssa P. Hacker says -- I see nothing At Bart Statue: Alyssa P. Hacker says -- I can go north, south At Cabal Hall: Officer Krumpke says -- No one to arrest. Must find donuts. Officer Krumpke moves from Cabal Hall to Bart Statue At Bart Statue: Officer Krumpke says -- Hi Alyssa P. Hacker what next? look At Bart Statue: Alyssa P. Hacker says -- I see Officer Krumpke At Bart Statue: Alyssa P. Hacker says -- I can go north, south At Bart Statue: Officer Krumpke says -- Alyssa P. Hacker, you are under arrest! At Bart Statue: Officer Krumpke says -- You have the right to remain silent, call methods, and mutate instance variables. Alyssa P. Hacker moves from Bart Statue to Jail At Jail: Alyssa P. Hacker says -- It's lonely here... what next? go north You cannot go north from <place: Jail> At Bart Statue: Officer Krumpke says -- No one to arrest. Must find donuts. what next? go south You cannot go south from <place: Jail> Ben Bitdiddle moves from Cdrs Hill to University Ave At University Ave: Ben Bitdiddle says -- It's lonely here... At Bart Statue: Officer Krumpke says -- No one to arrest. Must find donuts. what next? look At Jail: Alyssa P. Hacker says -- I see nothing At Jail: Alyssa P. Hacker says -- I can go nothing Ben Bitdiddle moves from University Ave to Cdrs Hill At Cdrs Hill: Ben Bitdiddle says -- It's lonely here... At Bart Statue: Officer Krumpke says -- No one to arrest. Must find donuts. Officer Krumpke moves from Bart Statue to Cabal Hall At Cabal Hall: Officer Krumpke says -- It's lonely here... what next? quit Better luck next time. Play again soon!
You can do what you want (so long as it is in good taste, of course). (As you may have noticed, the course staff has a fairly liberal notion of "good taste", but if you aren't sure, it's best to ask.)
A good extension will use inheritance to create new types of objects in your game. For example, you may want to create a new types of people, places and things that have new behaviors. A strong answer to this question should demonstrate at least five of the following challenges:
Your answer to this question should include:
Now invite a friend over and teach them how to play. But, remember that this is a fictional world. Don't try anything from the game at home (or on the green). If you do get a chance to visit Charlottansville, make sure to see Monty's Viola, the favorite instrument of the founder of the University and author of the influential 1976 treatise, The Ultimate Declarative, which led many Schemers to revolt.