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 lists work in Scheme?", 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.
(You may also want to start reading ahead in Chapter 11, which includes an introduction to Python also, but the new content on Interpreters in that section is not needed until Problem Set 7.)
ps6.py — A template for your answers. You should do the problem set by editing this file.adventure.py — Defines classes for representing places, people, and things in our world.charlottansville.py — Sets up our model world and some example games.
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.
To get started using Python, follow the directions in the Schemer's Guide to Python.
2
26
6
16
All of the objects in our game will be instances of subtypes of the SimObject class, defined in adventure.py. The SimObject class defines this constructor:
class SimObject: def __init__(self, name): self.name = 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 adventure.py):
>>> thing = OwnableObject("book")
>>> thing.is_ownable()
A Person object has two addition instance variables, defined in the constructor:
class Person (simobject.MobileObject): def __init__(self, name): simobject.MobileObject.__init__(self, name) self.__possessions = [] # what person is carrying (initially empty) self.__restlessness = 0.0 # propensity to move randomlyThe __init__ method first calls the superclass' __init__ method, and then initializes the new instance variables. We use a __ (two underscores) at the begining of the names to indicate that they cannot be accessed outside the Person class.
The Person class defineds several methods for manipulating people including:
say(self, utterance): Person × String → VoidNote 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 subclass PhysicalObject).Say the words passed in as the parameter (as a string).look(self): Person → VoidReport on the immediate surroundings (only works for installed people).have_fit(self): Person → VoidThrow a temper tantrum.go(self, direct): Person × Direction → BooleanMove in the indicated direction. The Direction is a string representing the direction, such as "north". The result is a Boolean indicating if the move was successful (only works for installed people.)
The Place subclass is defined adventure.py. Its constructor is:
class Place (simobject.SimObject): def __init__(self, name): simobject.SimObject.__init__(self, name) self.__neighbors = {} # dictionary of direction -> place entries self.__things = [] # list of SimObjectA Place object maintains a dictionary of the neighbors of the place. The dictionary type provides a convenient lookup-table. In this case, we use the direction (a string) as the dictionary key. Hence, we can look up the neighboring place in a given direction by looking up the direction in the __neighbors dictionary:
def neighbor_towards(self, direction): if direction in self.__neighbors: return self.__neighbors[direction] else: return False # no neighbor in this directionA 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:
get_things(self): Place → ListReturns 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).add_thing(self, thing): Place × SimObject → VoidModifes the place by adding the thing to the place.has_thing(self, thing): Place × SimObject → BooleanReturns True if the place has the thing; otherwise, returns False.remove_thing(self, thing): Place × SimObject → VoidIf the place does not contain this thing, produces an error. Otherwise, removes this thing from the place.
In the Python Shell, create two Place objects and make them neighbors. Install a Person object in one of your places. Create an OwnableObject and install it in your 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 evaluating
>>> cville = setup_Cville()
and then asking objects in our world to do things.
For example:
Our world needs some people in it, so let's create one, and install him in our world:>>> cville = setup_Cville()
>>> cabal = cville.get_place('Cabal Hall')
>>> cabal.get_exits()
['north', 'south']
>>> 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:>>> JT = Person('Jeffus Thomerson')
>>> cville.install_thing(JT, 'Cdrs Hill')
<simobject: Jeffus Thomerson>: Installing at <place: Cdrs Hill>
Try playing the adventure game. Load ps6.py in a Python Shell. Then, in the interactions window, start making people and moving them around. Get a feel for how objects work before moving on.>>> sandwich = OwnableObject('sandwich')
>>> cville.install_thing(sandwich, 'Cabal Hall')
<simobject: sandwich>: Installing at <place: Cabal Hall>>> 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
True
Our Person object provides a say(self, utterance) method:
>>> bill = Person('Bill')
>>> bill.say('To appy 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 she 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 adventure.py):class Lecturer (Person): def lecture(self, stuff): self.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. If the Lecturer subclass does not define the method, evaluation will the continue to look for the method in the superclass.
Your Professor should work like this:
>>> ev = Professor('Evan Davis')
>>> ev.profess("(lambda (n) ((lambda (f) (f f n)) (lambda (f k) (if (= k 1) 1 (* k (f f (- k 1))))))) is a familiar function")
Evan Davis says -- It is intuitively obvious that (lambda (n) ((lambda (f) (f f n)) (lambda (f k) (if (= k 1) 1 (* k (f f (- k 1))))))) is a familiar function - you should be taking notes
>>> ev.lecture("Python is cool")
Evan Davis says -- Python is cool - you should be taking notes
Your Dean should work like this:
>>> dean = 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, theProfessor class profess method is used. When that method invokes self.lecture, the Lecturer class lecture method is used. When that method invokes self.say, the Dean class 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.
class Person (MobileObject): def __init__(self, name): MobileObject.__init__(self, name) self.__possessions = [] # what person is crrying (initially empty) self.__restlessness = 0.0 # propensity to move randomly def get_possessions(self): return list(self.__possessions) def display_possessions(self): self.say ("I have " + list_to_string(self.__possessions)) ...
A person has an instance variable __possessions that is a list 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 self.__possessions directly, get_possessions returns a new copy of self.__possessions. 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 __init__ constructor, similarly to how the Person class overrides the __init__ constructor of MobileObject.
In addition, your Student class should implement three methods:
Your student should work like this:
>>> aph = Student("Alyssa P. Hacker")
>>> aph.install(Place("The Green"))
<simobject: Alyssa P. Hacker>: Installing at <place: The Green>
>>> 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 more clothes left to remove.
>>> aph.is_dressed()
False
>>> aph.get_dressed()
At The Green: Alyssa P. Hacker says -- I feel much better now.
>>> 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 ps6.py) the beginnings of a PoliceOfficer class. A PoliceOfficer is a special type of Person, with the ability to arrest other people.
The provided __init__ 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:
class PoliceOfficer (Person): def __init__(self, name, jail): Person.__init__(self, name) self.__jail = jail self.set_restlessness(0.5)For example, here is how a PoliceOfficer object is constructed in play_game_as_APH (defined in charlottansville.py):
krumpke = ps6.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).
>>> cville = setup_Cville()
>>> krumpke = PoliceOfficer('Officer Krumpke', cville.get_place('Jail'))
>>> aph = 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 -- Its 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:
class World: def __init__(self, n): self.name = n self.__places = {} self.__things = [] # a list of things in the world ... def install_thing(self, t, p): assert self.has_place(p) self.__things.append(t) t.install(self.get_place(p)) ... def tick(self): for t in self.__things: if has_method(t, "tick"): t.tick()(The has_method procedure is defined in adventure.py, but you don't need to worry about how it is defined. It takes an object and a string as inputs, and outputs True if the object has a method with the given name.)
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(self, value) that sets the __restlessness instance variable to the input value.
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).
>>> cville = setup_Cville()
>>> krumpke = PoliceOfficer('Officer Krumpke', cville.get_place('Jail'))
>>> aph = 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 -- Its 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 -- Its lonely here...
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(self, character) that provides a better interface to playing the game. The play_game_as_APH() procedure (defined in charlottansville.py) 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:
>>> play_game_as_APH()
<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 -- Its lonely here... what next? go north Alyssa P. Hacker moves from The Green to The Recursa At The Recursa: Alyssa P. Hacker says -- Its 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 -- Its lonely here... what next? go south Alyssa P. Hacker moves from The Recursa to The Green At The Green: Alyssa P. Hacker says -- Its 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 -- Its 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 -- Its 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 -- Its 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 -- Its 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 -- Its 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 -- Its 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.