Problem Set 6: Adventures in Charlottansville |
Out: 14 March Due: Friday, 23 March |
Collaboration Policy - Read Carefully
If you wish to be assigned a partner for this assignment, send me an email by 11:59 pm, Thursday, 15 March. Otherwise, you may work on this assignment alone or with one partner of your choice.
Regardless of whether or not you have a partner for the problem set, for the final question (Question 8), you may combine efforts with as many students as you wish. If you work with additional partners on question 8, one of you should turn in the answer to question 8 for the group that identifies everyone who worked on it, and the rest of the team should include an answer to question 8 that identifies who you worked with and who turned in the answer.
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. You may consult any outside resources you wish including books, papers, web sites and people (except for cs150 problem sets and comments from previous years). If you use resources other than the class materials, indicate what you used along with your answer. Purpose
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 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.
We have provided a constructor procedure called make-class for creating an object of each class. All objects in our fictional world have names (rumors about a "Nameless Field" are as yet unsubstantiated), so each of our constructor procedures will take a name parameter. We use quoted symbols (see Chapter 10) for names. The three constructors are:
To put a new thing or person in our fictional world, we must install it in a place. The procedure install-object takes two parameter, an object and a place, and installs the object in the place.
Once an object is installed, we interact with it using ask to send a message to an object that invokes a method:
Different objects handle different messages. Here is a partial list of messages you can use. The ask procedure is used to implement each of these. Note that some of the messages have parameters.
make two places, Cabal-Hall and Bart-Statue. In set-up-charalatansville, we use can-go-both-ways to connect Cabal-Hall and Bart-Statue:(define Cabal-Hall (make-place 'Cabal-Hall)) (define Bart-Statue (make-place 'Bart-Statue))
(can-go-both-ways Bart-Statue 'south 'north Cabal-Hall)We can experiment with our world by evaluating (set-up-charlottansville) and then asking objects in our world to do things. (The provided file ps6.scm does this when you load it.) For example:
> (set-up-charlottansville)
welcome-to-charlottansville
> (ask Cabal-Hall 'exits)
(down north)
> (ask Cabal-Hall 'name)
cabal-hall
> (ask Cabal-Hall 'neighbor-towards 'down)
#<procedure>
> (ask (ask Cabal-Hall 'neighbor-towards 'down) 'name)
steam-tunnel
We can also make things and add them to our world. For example, let's create a donut and place it in Cabal-Hall (where JT is now). Mr. Thomasson looks around, sees the donut and takes it:> (define JT (make-person 'Jeffus-Thomasson))
> (install-object JT Cabal-Hall)
Installing jeffus-thomasson at cabal-hall
installed
Try playing the adventure game. Load ps6.scm in DrScheme. Then, in the interactions window, start making people and moving them around. Get a feel for how objects work before moving on.> (define donut (make-thing 'donut))
> (install-object donut Cabal-Hall)
Installing donut at cabal-hall
installed
> (ask JT 'look)
At cabal-hall: jeffus-thomasson says -- I see donut
At cabal-hall: jeffus-thomasson says -- I can go down north(donut)
> (ask JT 'take donut)
At cabal-hall : jeffus-thomasson says -- I take donut
#t
(define make-sim-object (lambda (name) (lambda (message) (if (eq? message 'object?) (lambda (self) #t) (if (eq? message 'class) (lambda (self) 'object) (if (eq? message 'name) (lambda (self) name) (if (eq? message 'say) (lambda (self list-of-stuff) (if (not (null? list-of-stuff)) (display-message list-of-stuff)) (void)) (if (eq? message 'install) (lambda (self . args) 'installed) #f))))))))The name make-sim-object refers to a procedure that takes one parameter, name. It evaluates to a procedure that takes one parameter, message.
All those (if (eq? ...)) expressions in make-sim-object get pretty hard to read, so Scheme provides the case syntactic sugar to write this more conveniently (see the DrScheme help for the details on case, but you can probably figure out what you need to know from this example). Here is the sugary version of make-sim-object — it means exactly the same thing as the previous definition:
(define (make-sim-object-sugared name) (lambda (message) (case message ((object?) (lambda (self) #t)) ((class) (lambda (self) 'object)) ((name) (lambda (self) name)) ((say) (lambda (self list-of-stuff) (if (not (null? list-of-stuff)) (display-message list-of-stuff)) (void))) ((install) (lambda (self . args) 'installed)) (else #f)))) ;; no method defined for message
a. (make-sim-object 'book)
b. ((make-sim-object 'book) 'name)
c. ((make-sim-object 'book) 'fly)
d. (((make-sim-object 'book) 'name) (make-sim-object 'donkey))
e. (((make-sim-object 'book) 'say) (make-sim-object 'donkey)
(list 'hello))
Is the book talking or the
donkey?
f. (eq? (make-sim-object 'book) (make-sim-object 'book))
Interacting with objects by calling them is awkward, so we want to define an ask procedure that sends a message to an object. You should be able to figure out these definitions yourself:
(Note: the ask procedure in object.scm is a bit different to produce better error messages.)(define (get-method object message) (object message)) ;;; Send a message to an object (with zero or more arguments) (define (ask object message . args) (apply (get-method object message) object args))
> (define dog (make-sim-object 'spot))You can use (display sym) to output one symbol. The output in this example would be produced by (display 'wuff).
> (ask dog 'utter 'wuff)
wuff
Our basic object (procedures produced by applying make-sim-object) provides a say method:
> (define bill (make-sim-object 'bill))
> (ask bill 'say '(to apply or to eval, that is the question))
(to apply or to eval, that is the question)
What if we have lots of different kinds of speakers and we want to make them speak different ways. For example, a lecturer is a kind of speaker, 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 special kind of object:(define (make-lecturer name) (make-subobject (make-sim-object name) (lambda (message) (if (eq? message 'lecture) (lambda (self stuff) (ask self 'say stuff) (ask self 'say (list "you should be taking notes"))) #f))))See Chapter 10 for an explanation of the make-subobject procedure. When a message is sent to an object created by make-lecturer, the implementation procedure checks if the message is 'lecture. If it is, it evaluates to the procedure that lectures. If it is not, it evaluates to #f, and the superclass method is found.
Your professor should work like this:
Note that the lecture method is inherited from lecturer and the name method is inherited from lecturer, which inherits it from object. Your make-professor procedure should fit on 8 or fewer reasonably short lines.> (define ed (make-professor 'Evan-Davis))
> (ask ed 'profess (list "(lambda (n) ((lambda (f) (f f n)) (lambda (f k) (if (= k 1) 1 (* k (f f (- k 1))))))) is a familiar function"))
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> (ask ed 'lecture (list "smalltalk is cool"))
smalltalk is cool
you should be taking notes> (ask ed 'name)
evan-davis
(define (make-person name) (make-subobject (make-mobile-object name)) (let ;; Instance variables ((possessions '()) ;;; What the person is carrying (a list of Objects that are things (restlessness 0)) ;;; How likely the person is to move randomly (lambda (message) (case message ((person?) (lambda (self) #t)) ((install) (lambda (self where) (ask super 'install where))) ((get-possessions) (lambda (self) possessions)) ... Other methods not shown (else #f))))))
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. Other methods use (set! possessions ...) to change the possesions a person is holding.
Some of the students in Charlottansville have a strange habit of getting undressed and running around the Green, so students have an instance variable dressed that indicates whether or not the student is clothed. Initally, all students are dressed, so the dressed variable is initialized to #t. Your student class should implement three methods:
Your student should work like this:
> (define alyssa (make-student 'alyssa-p-hacker))
> (install-object alyssa Green)
Installing alyssa-p-hacker at green
> (ask alyssa 'is-dressed?)
#t
> (ask alyssa 'name)
alyssa-p-hacker
> (ask alyssa 'get-undressed!)
At green: alyssa-p-hacker says -- brrrrr...its cold!
> (ask alyssa 'is-dressed?)
#f
> (ask alyssa 'get-dressed!)
At green: alyssa-p-hacker says -- I feel much better now.
> (ask alyssa 'is-dressed?)
#t
We do this by creating a list of all the characters to be moved by the computer and by simulating the passage of time with an world-clock object. The make-world-clock procedure (defined in objects.scm) creates a world-clock object. It has two instance variables: global-time, for keeping track of the time, and clock-list for keeping track of the objects that should be sent clock-tick messages when the clock advances.
The methods add and remove that have object parameters and add or remove objects from the clock-list. When the clock receives a clock-tick message it means time has passed. It sends a clock-tick message to each object in its clock-list. This doesn't necessarily do something every time, but for some objects it will lead to an action.
In adventure.scm, we create a clock and add all objects installed using install-object to the clock:
We can advance the clock by doing (ask clock 'tick).(define clock (make-world-clock)) (define (install-object object place) (ask object 'install place) (ask clock 'add object))
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. If restlessness is not #f, 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 not move randomly. A person object has a method make-restless that take a parameter and sets the restlessness instance variable to that value.
The University administration does not condone streaking, and has decided to strategically place police officers on the Green to apprehend streakers.
Try playing the game to see that your police-officer works correctly.
We have provided a procedure
(play-interactively-as character)
that provides a better interface to playing the
game. The play-game procedure (defined in ps6.scm)
installs two students and one restless police officier in our world,
and starts playing interactively as one of the students.
Here's what a typical game might look like:
> (play-game)
At not-yet-installed: alyssa-p-hacker says -- Installing alyssa-p-hacker at green
At not-yet-installed: ben-bitdiddle says -- Installing ben-bitdiddle at cdrs-hill
At not-yet-installed: officer-krumpke says -- Installing officer-krumpke at bart-statue
what now? > name
< Result: alyssa-p-hacker>
[Clock] Tick 1
At bart-statue: officer-krumpke says -- No one to arrest. Must find donuts.
officer-krumpke moves from bart-statue to steam-tunnel
officer-krumpke is no longer at bart-statue
what now? > get-undressed!
At green: alyssa-p-hacker says -- brrrrr...its cold!
[Clock] Tick 2
At steam-tunnel: officer-krumpke says -- No one to arrest. Must find donuts.
ben-bitdiddle moves from cdrs-hill to cricket-street
what now? > look
At green: alyssa-p-hacker says -- I see nothing
At green: alyssa-p-hacker says -- I can go down west north south
< Result: ()>
[Clock] Tick 3
At steam-tunnel: officer-krumpke says -- No one to arrest. Must find donuts.
what now? > go north
alyssa-p-hacker moves from green to recursa
< Result: #t>
[Clock] Tick 4
At steam-tunnel: officer-krumpke says -- No one to arrest. Must find donuts.
officer-krumpke moves from steam-tunnel to bart-statue
ben-bitdiddle moves from cricket-street to cdrs-hill
what now? > go south
alyssa-p-hacker moves from recursa to green
< Result: #t>
[Clock] Tick 5
At bart-statue: officer-krumpke says -- No one to arrest. Must find donuts.
what now? > go south
alyssa-p-hacker moves from green to bart-statue
At bart-statue: alyssa-p-hacker says -- Hi officer-krumpke
< Result: #t>
[Clock] Tick 6
At bart-statue: officer-krumpke says -- alyssa-p-hacker, you are under arrest!
alyssa-p-hacker moves from bart-statue to jail
At bart-statue: officer-krumpke says -- You have the right to remain silent, call methods and mutate instance variables.
what now? > look
At jail: alyssa-p-hacker says -- I see nothing
At jail: alyssa-p-hacker says -- I can go
< Result: ()>
[Clock] Tick 7
At bart-statue: officer-krumpke says -- No one to arrest. Must find donuts.
officer-krumpke moves from bart-statue to green
what now? > quit
Better luck next time. Play again soon!
Input: An initial state consisting of a set places in the Charlottansville world, a student object (as described in Question 5) and a police officer object (whose clock-tick method may contain any code).You should assume the results of random are completely determined (that is, you can always predict what an application of random evaluates to).Output: If there is any sequence of actions the student object can take to streak from the Recursa to the Bart Statue without getting arrested at any time during the game, output true. Otherwise, output false.
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, its 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.
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.
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.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.