Turn-in Checklist:
  1. Bring to class a stapled turn-in containing your written answers to all questions and all your code by 3:30pm on Wednesday, 27 October. (There is no automatic adjudication for this problem set.)
  2. If you work with a partner, your team should submit a single writeup with both of your names and UVa IDs on it.
Note: You have twelve days for this problem set. It introduces a new programming language, and involves more code than previous problem sets, and the last questions provides an opportunity for creative work, so do not delay in getting started on it. (This is in direct contrast to all of those other assignments where we encouraged you to slack off. But still.) It is not necessary to understand all of the provided code for this problem set, but you will need to understand parts of it well to answer the questions.

Collaboration Policy - Read Very Carefully

You may work on this assignment alone or with one partner of your choice so long as it is not someone with whom you have partnered on two or more previous problem sets.

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.

Purpose

Read Chapter 10 (Objects) and the Schemer's Guide to Python.

(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.)

The lab computers should already have IDLE, the Python interface installed. IDLE is like DrScheme and comes with Python.

Download: Download ps6.zip to your machine and unzip it into your home directory J:\cs1120\ps6. This file contains:
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.

Background

In the 1961, Digital Equipment Corporation (later part of Compaq, which is now part of Hewlett-Packard) donated a PDP-1 mini-computer to MIT, hoping they would use it to solve important scientific problems. Instead, Steve Russell invented the first computer game: Spacewar!. Ten years later, Will Crowther produced the first interactive fiction game, Colossal Cave Adventure (or Adventure for short). Inspired by Adventure, a group of MIT students created Zork and released a version for the Apple II. The game became wildly popular, and was the cause of many students failing out of school.

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.

Q: So some of the locations in the game are based on real places?
A: Except for a few. As far as I know, there is no eldricth altar at which students are sacrificed to nameless gods. But then, I was never a professor, so I can't be sure.
Dave Lebling, on The Lurking Horror adventure game

Python Warmup

For this assignment (as well as Problem Set 7 and most of the rest of this course), we will use a programming language called Python. We'll talk more in class about the similarities and differences between Python, but one important difference for this problem set is that Python provides built-in support for defining classes and subclasses that use inheritance. This will make our code simpler than the object system defined in Chapter 10 since the details of managing objects and inheriting methods are taken care of directly by Python.

To get started using Python, follow the directions in the Schemer's Guide to Python.

Question 1: For each Scheme fragment below, write an analogous Python fragment. Evaluating your expression in the Python Shell should produce the results shown.

  1. (+ 1 1)

    2

  2. (+ (* 2 3) (* 4 5))

    26

  3. (if (> 3 4) 5 6)

    6

  4. (define (square x) (* x x))
  5. (square 4)

    16

Objects

For our game, we need to represent three basic kinds of things: people, places and things. Every object we make will be a person, a place or a thing, so we will need classes that implement each kind of object.

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):

Question 2: By examining the code in adventure.py and understanding what it means to inherit methods in subclasses, explain what happens when:
  1. The is_ownable method is invoked on a OwnableObject.
  2. The is_ownable method is invokved on a MobileObject.
You can check your answers by creating objects an invoking the methods. For example,

>>> thing = OwnableObject("book")

>>> thing.is_ownable()

If ps6.zip seems to fail out of the box (e.g., complaining about true or StandardError or raw_input), your version of Python is too new. Use a lab machine or install Python 2.6 instead. The code does not work with Python 3.0+ because Python is not backwards compatible.

People

Our world also contains people, defined by the Person class in adventure.py. Person is a subclass of simobject.MobileObject (note we need the simboject. here since MobileObject is defined in a different module).

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 randomly
The __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 → Void
Say the words passed in as the parameter (as a string).
look(self): Person → Void
Report on the immediate surroundings (only works for installed people).
have_fit(self): Person → Void
Throw a temper tantrum.
go(self, direct): Person × Direction → Boolean
Move 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.)
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 subclass PhysicalObject).

Exercise A: (you do not need to turn anything in for this) Open adventure.py in IDLE and select Run Module to load it into a Python Shell. Then, create a Person object named "Alyssa", and ask her to say something. (Try this yourself first, but if you are stuck see the hints.)

Places

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 SimObject
A 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 direction
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:
get_things(self): Place → List
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).
add_thing(self, thing): Place × SimObject → Void
Modifes the place by adding the thing to the place.
has_thing(self, thing): Place × SimObject → Boolean
Returns True if the place has the thing; otherwise, returns False.
remove_thing(self, thing): Place × SimObject → Void
If the place does not contain this thing, produces an error. Otherwise, removes this thing from the place.
Exercise B: (you do not need to turn anything in for this) Examine the code in adventure.py to see how the SimObject, PhysicalObject, MobileObject, OwnableObject and Place classes are defined. Draw an inheritance diagram showing the relationships between these clasees.

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.)

Visiting Charlottansville

In charlottansville.py we define some places in our imaginary world. The procedure setup_Cville() creates some places and sets up connections between them.

We can experiment with our world by evaluating

>>> cville = setup_Cville()

and then asking objects in our world to do things.

For example:

>>> cville = setup_Cville()

>>> cabal = cville.get_place('Cabal Hall')

>>> cabal.get_exits()

['north', 'south']

>>> cabal.neighbor_towards('north').name

'Bart Statue'

Our world needs some people in it, so let's create one, and install him in our world:

>>> JT = Person('Jeffus Thomerson')

>>> cville.install_thing(JT, 'Cdrs Hill')

<simobject: Jeffus Thomerson>: Installing at <place: Cdrs Hill>

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:

>>> sandwich = OwnableObject('sandwich')

>>> cville.install_thing(sandwich, 'Cdrs Hill')

<simobject: sandwich>: Installing at <place: 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

True

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.

Inheritance

Generic people are okay, but for an interesting game we need to have people who can do special things.

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.

Question 3: A Professor is even more arrogant than a Lecturer. Define a Professor class that is a subclass of Lecturer. Define a method profess in your Professor class that is like lecturing, but precedes every statement with "It is intuitively obvious that". (Your Professor class definition should not be more than about 3 lines long.)

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

Question 4: A Dean is like a Professor, but follows every statement with a request for donations. Define a Dean class that is a subclass of Professor. Your Dean class should override the say method so that after every utterance it makes a request for money. (Your Dean class definition should not be more than about 3 lines long.) [Syntax hint: to access the superclass say method, use Professor.say(self, stuff).]

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.

State

More interesting objects in our game will need to use state to keep track of things that might change during an execution. Consider the Person class defined in adventure.py (and described above). It is pretty long because a Person has many methods. Here we show some of the code, but leave out some methods:
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.

Question 5: A Student is a special kind of Person (this doesn't necessarily mean all students are special or kind, just that they are all persons). Define a Student class that is a subtype of Person.

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:

  • get_undressed(self) — If the object is already undressed, it should say something about having no more clothes to remove. Otherwise, change the state of dressed to False. (In Scheme, we would name this method get-undressed! with an exclamation point to indicate that the method is a mutator since it changes the state of the self object. Python does not allow ! to be used in names, so we cannot include one in the method name, event though depending on the current location of the student, it may be appropriate to have more than one exclamation point here.) The undressed student should also say "Brr! It's cold!" (or something more creative, but not less tasteful).
  • get_dressed(self) — changes the state of dressed to True. The student should also say "I feel much better now.".
  • is_dressed(self) — evaluates to True if the student is dressed and False otherwise.

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.
Question 6: Define a method in your PoliceOfficer class that makes an arrest. Your method should be called arrest and take a Person as its parameter (in addition to the self parameter).

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).

Here are some sample interactions that should work with your PoliceOfficer class:

>>> 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

Automating Objects

This kind of world doesn't make for a very challenging or interesting game. All the people and things only do what the player tells them to. For a more interesting game, we need to have people and things that act autonomously.

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.

Question 7: Define a tick method for your PoliceOfficer class. The tick method should automatically arrest everyone streaking in the same place as the police officer. (That is, any Student objects that are not dressed should be arrested.) If no one is streaking in the officer's location, the police officer should act like a normal person — that is, it should then invoke the superclass (Person) tick() method. [Hint: you can use isinstance(t, Student) to determine if t is a Student object.]

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!

Extensions

With a full command of Python procedures, object-oriented programming, and inheritance, you are now ready to start making an addictive game. Keep in mind that, historically, computer games have been a colossal waste of time for humankind. As simple as this game is, it's easy to get carried away. Spend your time on the problems, not the game.

Question 8: Design a non-trivial extension to this simulated world. Use your imagination!

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:

  1. A subclass overriding its parent's response to a message (i.e., not invoking the parent behavior and doing something else instead).
  2. A subclass extending its parent's response to a message (i.e., invoking the parent behavior but also doing something else).
  3. A subclass that does something interesting on tick().
  4. Behavior that depends on the state of the world (e.g., different responses based on the current location, the number of exits, the name of the current location, etc.).
  5. An object that creates other objects (careful!).
  6. A non-trivial use of a possession (taking and losing things). The current take interactive command is intentionally broken in the default code from ps6.zip. You might start by fixing that bug, and then go on to make use of an inventory system.
  7. Pursuit or evasion (e.g., adding something like an expert police officer that, rather than moving randomly, moves to an adjacent location if it contains a streaker; adding a squirrel that moves away from persons — or not!).
To add new behaviors for interactive games, you will also need to modify the play_interactively method in the World class to call the new methods.

Your answer to this question should include:

  1. A short explanation of your extensions that describes the objects you added.
  2. An inheritance diagram showing your new classes and the classes they inherit from.
  3. Code for all new procedures you write, or old procedures that you modify. You should only modify provided code if there is no way to provide the behavior you want by defining a new subclass instead. If you modify an existing method, you should print out the entire method but highlight the changed code (e.g., with a physical highlighter, or underlining, or somesuch).
  4. A transcript that shows your game in action.
  5. Explicit indication of which of the challenges above you are addressing. For example, put a big 1 in a circle (or in a comment, or whatnot) in the writeup where you are addressing challenge 1.

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.

Credits: This problem set was developed by Portman Wills and David Evans for CS200 Spring 2002 and slightly revised for CS200 Spring 2003 and CS150 Fall 2005 by David Evans, and then revised again for Spring 2009 by Westley Weimer. It was originally done using Scheme, but translated to use Python for cs1120 Fall 2009. Portman is solely responsible for all the streaking references, however. It is based on a problem set first used in MIT 6.001 Fall 1989, and subsequently in many other years including 1997, on which we based some of our code. The MIT version of the adventure game involved Deans smashing beer and party-troll eating Deans. A course at UC Berkeley also had an adventure game problem set. Their acknowledgment was, "This assignment is loosely based on an MIT homework assignment in their version of this course. But since this is Berkeley we've changed it to be politically correct; instead of killing each other, the characters go around eating gourmet food all the time. N.B.: Unless you are a diehard yuppie you may feel that eating gourmet food does not express appropriate sensitivity to the plight of the homeless. But it's a start." Despite Question 4, we like our Deans much more than they do at MIT, and UVA students don't eat much gourmet food. We thought about making our adventure game about eating Berkeley students, but were worried that might improve their student-faculty ratio and ruin our chances of ever surpassing them in the US News rankings.