You should begin working on the assignment once you receive it. It is to your advantage to get work done early, rather than waiting until the night before it is due. You should also read over and think through each part of the assignment (as well as any project code) before you sit down at the computer. It is generally much more efficient to test, debug, and run a program that you have thought about beforehand, rather than doing the planning "online." Diving into program development without a clear idea of what you plan to do generally ensures that the assignments will take much longer than necessary.
Word to the wise: This project is difficult. The trick lies in knowing which code to write, and for that you must understand the project code, which is considerable. You'll need to understand the general ideas of object-oriented programming and the implementation provided of an object-oriented programming system (in objsys.scm). Then you'll need to understand the particular classes (in objtypes.scm) and the world (in setup.scm) that we've constructed for you. In truth, this assignment in much more an exercise in reading and understanding a software system than in writing programs, because reading significant amounts of code is an important skill that you must master. The warmup exercises will require you to do considerable digesting of code before you can start on them. And we strongly urge you to study the code before you try the programming exercises themselves. Starting to program without understanding the code is a good way to get lost, and will virtually guarantee that you will spend more time on this assignment than necessary.
In this project we will develop a powerful strategy for building simulations of possible worlds. The strategy will enable us to make modular simulations with enough flexibility to allow us to expand and elaborate the simulation as our conception of the world expands and becomes more detailed.
One way to organize our thoughts about a possible world is to divide it up into discrete objects, where each object will have a behavior by itself, and it will interact with other objects in some lawful way. If it is useful to decompose a problem in this way then we can construct a computational world, analogous to the "real" world, with a computational object for each real object.
Each of our computational objects has some independent local state, and some rules (or code) that determine its behavior. One computational object may influence another by sending it messages and invoking methods in the other. The program associated with an object describes how the object reacts to messages and how its state changes as a consequence.
You may have heard of this idea in the guise of "Object-Oriented Programming systems"(OOPs!). Languages such as C++ and Java are organized around OOP. While OOP has received a lot of attention recently, it is only one of several powerful programming styles. What we will try to understand here is the essence of the idea, rather than the incidental details of their expression in particular languages.
Consider the problem of simulating the activity of a few interacting agents wandering around different places in a simple world. Real people are very complicated; we do not know enough to simulate their behavior in any detail. But for some purposes (for example, to make an adventure game) we may simplify and abstract this behavior.
Let's start with the fundamental stuff first. We can think of our object oriented paradigm as consisting of classes and instances. Classes can be thought of as the "template" for how we want different kinds of objects to behave. The way we define the class of an object is with a basic "make handler" procedure; this procedure is used with a "create instance" procedure which builds for us a particular instance.
Our object instances are themselves procedures which accept messages. An object will give you a method if you send it a message; you can then invoke that method on the object (and possibly some arguments) to cause some action, state update, or other computation to occur.
As in lecture (object-oriented systems, part II), here is the template for a class definition in our object system:
(define (make-type self arg1 arg2 ... argn )
(let ((super1-part (make-super1 self args)
(super2-part (make-super2 self args)
other superclasses
other local state )
(lambda (message)
(case message
((TYPE)
(lambda ()
(type-extend 'type super1-part
super2-part ...)) )
other messages and methods
(else (get-method message super1-part
super2-part ...))))))
That form is a little mystifying, so let's examine an example. In our simulation, almost everything is going to have a name, thus let's create a named-object class:
(define (make-named-object self name)
(let ((root-part (make-root-object self)))
(lambda (message)
(case message
((TYPE) (lambda () (type-extend 'named-object root-part)))
((NAME) (lambda () name))
((INSTALL) (lambda () 'INSTALLED))
((DESTROY) (lambda () 'DESTROYED))
(else (get-method message root-part))))))
By comparison with the above template and reference to lecture, we can decode the above definition. Every make-foo procedure takes self as the first argument. This indicates which instance the class handler is part of. The second argument to make-named-object is name, which is part of the state of the named-object.
The let statement which binds root-part to result of making a root-object, together with the type-extend usage inside the type method, and the get-method at the end of the definiton, all together tell us that named-objects are a subclass of root-object.
(define (make-root-object self)
(lambda (message)
(case message
((TYPE)
(lambda () '(root)))
((IS-A)
(lambda (type)
(memq type (ask self 'TYPE))))
(else
(no-method)))))
The root object provides a basis for providing common behaviors to all classes. Specifically, it provides the basic TYPE method, and a convenient method (IS-A) to see if a type descriptor is in the TYPE list. We will by convention use this class as the root for all other classes.
Named-objects have no other local state than the name variable. They do have four methods: TYPE, NAME, INSTALL, and DESTROY. The TYPE method is required and it indicates that named-objects have the type named-object in addition to any type descriptors that the root-part has. The INSTALL method is not required, but if it exists, it is called when an instance is created. In the case of named-object, there is nothing to do at creation time, but we'll later see examples where this method is non-trivial. The NAME is a selector in that it returns the name that the object was created with.
However, the make-named-object procedure only builds a handler for an object of type named-object. In order to get an instance, we need a create-named-object procedure:
(define (create-named-object name) ; symbol -> named-object (create-instance make-named-object name))
Here, an instance is created using the make-named-object procedure. The create-instance procedure builds a handler procedure that serves as a container for the real handler for the instance. We need this extra complexity because each make-foo procedure expects self as an argument, so we build an instance object, then create the handler, passing the instance object in for self. You'll explore more of this system in the questions below.
Once you have an instance, you can call the methods on it using the ask procedure:
(define book (create-named-object 'sicp)) (ask book 'NAME) ;Value: sicp (ask book 'TYPE) ;Value: (named-object root)
The ask procedure retrieves the method of the given name from the instance, then calls it. Retrieving a method from a handler is done with get-method, which ends up calling the handler procedure with the method name as the message. The specifics of the ask procedure and related procedures can be found in objsys.scm.
We've already built a class, named-object, that inherited from its parent class, root-object. If the handler for a named-object is sent a message that it doesn't recognize, it attempts to get a method from its parent (last line of make-named-object procedure). Each handler creates a private handler for its parent to pass these messages to (the let statement in make-named-object). Because this parent handler is part of the same instance as the overall handler, the self value is the same in both.
However, let's move on to a subclass of named-object called a thing. A thing is an object that will have a location in addition to a name. Thus, we may think of a thing as a kind of named object except that it also handles the messages that are special to things. This arrangement is described in various ways in object-oriented jargon, e.g., "the thing class inherits from the named-object class," or "thing is a subclass of named-object," or "named-object is a superclass of thing."
(define (create-thing name location) ; symbol, location -> thing
(create-instance make-thing name location))
(define (make-thing self name location)
(let ((named-object-part (make-named-object self name)))
(lambda (message)
(case message
((TYPE) (lambda () (type-extend 'thing named-object-part)))
((INSTALL)
(lambda () ; Install: synchronize thing and place
(ask named-object-part 'INSTALL)
(ask (ask self 'LOCATION) 'ADD-THING self)))
((LOCATION) (lambda () location))
((DESTROY)
(lambda () ; Destroy: remove from place
(ask (ask self 'LOCATION) 'DEL-THING self)
(ask named-object-part 'DESTROY)))
((EMIT)
(lambda (text) ; Output some text
(ask screen 'TELL-ROOM (ask self 'LOCATION)
(append (list "At" (ask (ask self 'LOCATION) 'NAME))
text))))
(else (get-method message named-object-part))))))
A very interesting (and confusing!) property of object-oriented systems is that subclasses can specialize or override methods of their superclasses. In lecture, you saw this with lecturers SAYing things differently than students. A subclass overrides a method on the superclass by supplying a method of the same name. For example, thing overrides the INSTALL method of named-object. When the user of the object tries to get the method named INSTALL, it will be found in thing and thing's version of the method willb e returned (because it never reaches the else clause which checks the parent named-object-part). The thing class overrides three methods on named-object; can you point out which they are?
One of the methods which thing overrides is the TYPE method. This is the one method that every class is supposed to override, as it allows the class to include its type descriptor in the list of types that the object has. This allows the class of an instance to be discovered at run time:
(define building (create-thing 'stata-center MIT)) (ask building 'TYPE) ;Value: (thing named-object root)
There is a handy method on the root-object called IS-A that uses the TYPE method to determine if an object has a certain type:
(ask building 'IS-A 'thing) ;Value: #t (ask building 'IS-A 'named-object) ;Value: #t (ask book 'IS-A 'thing) ;Value: #f (ask book 'IS-A 'named-object) ;Value: #t
You'll note that building is considered to be both a thing and a named-object, because even though it was built as a thing, things inherit from named-object.
In the thing code, the DESTROY method uses (ask self 'LOCATION) in order to figure out where to remove itself from. However, it could have just referenced the location variable. It doesn't because one of the tenets of object-oriented programming is "if there's a method that does what you need, use it." The idea is to re-use code as much as is reasonable. (It turns out just using location would be a bug in this case; you'll be able to see why after doing the warm-up exercises!)
Some of the time, when you specialize a method, you want the subclass'' method to do something completely different than the superclass. For example, the way massive-stars DIE (supernova!) is very different than the way stars DIE. However, the rest of the time, you may want to specify some additional behavior to the original. This presents a problem: how to call your superclass' method from within the overriding method. Following the usual pattern of (ask self 'METHOD) will give rise to an infinite loop! Thus, instead of asking ourself, we ask our superclass-part. An example of this is in the second line of the DESTROY method of the thing. This is the only situation in which you should be asking your superclass-part!
When you read the code in objtypes.scm, you will see definitions of several different classes of objects that define a host of interesting behaviors and capabilities using the OOP style discussed in the previous section. Here we give a brief "tour" of some of the important classes in our simulated world.
(define (make-container self)
(let ((root-part (make-root-object self))
(things '())) ; a list of THING objects in container
(lambda (message)
(case message
((TYPE) (lambda () (type-extend 'container root-part)))
((THINGS) (lambda () things))
((HAVE-THING?)
(lambda (thing) ; container, thing -> boolean
(not (null? (memq thing things)))))
((ADD-THING)
(lambda (new-thing)
(if (not (ask self 'HAVE-THING? new-thing))
(set! things (cons new-thing things)))
'DONE))
((DEL-THING)
(lambda (thing)
(set! things (delq thing things))
'DONE))
(else (get-method message root-part))))))
Note that a container does not inherit from named-object, so it does not support messages such as NAME or INSTALL. Containers are not meant to be stand-alone objects (there's no create-container procedure); rather, they are only meant to be used internally by other objects to gain the capability of adding things, deleting things, and checking if one has something.
(define (create-place name) ; symbol -> place
(create-instance make-place name))
(define (make-place self name)
(let ((named-obj-part (make-named-object self name))
(container-part (make-container self))
(exits '())) ; a list of exits
(lambda (message)
(case message
((TYPE) (lambda () (type-extend 'place named-obj-part container-part)))
((EXITS) (lambda () exits))
((EXIT-TOWARDS)
(lambda (direction) ; symbol -> exit
(find-exit-in-direction exits direction)))
((ADD-EXIT)
(lambda (exit) ; exit -> symbol
(let ((direction (ask exit 'DIRECTION)))
(cond ((ask self 'EXIT-TOWARDS direction)
(error (list name "already has exit" direction)))
(else
(set! exits (cons exit exits))
'DONE)))))
(else
(get-method message container-part named-obj-part))))))
If we look at the first and last lines of
(define stata (create-place 'stata-center)) (ask stata 'TYPE) ;Value: (place named-object root container)
The type-extend procedure gets the types of both parents and removes the duplicate type descriptors. Thus, you aren't guaranteed anything about the order of the type-descriptors except that the first descriptor in the list is the class that you instantiated to create the instance. You can also see that our place instances will each have their own internal variable exits, which will be a list of exit instances which lead from one place to another place. In our object-oriented terminology, we can say the place class establishes a "has-a" relationship with the exit class (as opposed to the "is-a" relationship denoting inheritance). You should examine the objtypes.scm file to understand the definition for exits.
(define (create-mobile-thing name location)
; symbol, location -> mobile-thing
(create-instance make-mobile-thing name location))
(define (make-mobile-thing self name location)
(let ((thing-part (make-thing self name location)))
(lambda (message)
(case message
((TYPE) (lambda () (type-extend 'mobile-thing thing-part)))
((LOCATION) ; This shadows message to thing-part!
(lambda () location))
((CHANGE-LOCATION)
(lambda (new-location)
(ask location 'DEL-THING self)
(ask new-location 'ADD-THING self)
(set! location new-location)))
((ENTER-ROOM)
(lambda () #t))
((LEAVE-ROOM)
(lambda () #t))
((CREATION-SITE)
(lambda ()
(ask thing-part 'location)))
(else (get-method message thing-part))))))
When a mobile thing moves from one location to another it has to tell the old location to DEL-THING from its memory, and tell the new location to ADD-THING. You'll note that the CHANGE-LOCATION method adds and removes the self from locations, thus the location contains a reference to the instance not the handler!
A person is a kind of mobile thing that is also a container. The objective of the multiple inheritance is that people can "contain things" which they carry around with them when they move.
A person can SAY a list of phrases. A person can TAKE and DROP things. People also have a health meter which is reduced by SUFFERing. If a person's health reaches zero, they DIE and leave behind a body. Some of the other messages a person can handle are briefly shown below; you should consult the full definition of the person class in objtypes.scm to understand the full set of capabilities a person instance has.
(define (create-person name birthplace) ; symbol, place -> person
(create-instance make-person name birthplace))
(define (make-person self name birthplace)
(let ((mobile-thing-part (make-mobile-thing self name birthplace))
(container-part (make-container self))
(health 3)
(strength 1))
(lambda (message)
(case message
((TYPE)
(lambda ()
(type-extend 'person mobile-thing-part container-part)))
((STRENGTH) (lambda () strength))
((HEALTH) (lambda () health))
((SAY)
(lambda (list-of-stuff)
(ask screen 'TELL-ROOM (ask self 'location)
(append (list "At" (ask (ask self 'LOCATION) 'NAME)
(ask self 'NAME) "says --")
list-of-stuff))
'SAID-AND-HEARD))
((HAVE-FIT)
(lambda ()
(ask self 'SAY '("Yaaaah! I am upset!"))
'I-feel-better-now))
...
((TAKE)
(lambda (thing)
...))
((LOSE)
(lambda (thing lose-to)
(ask self 'SAY (list "I lose" (ask thing 'NAME)))
(ask self 'HAVE-FIT)
(ask thing 'CHANGE-LOCATION lose-to)))
((DROP)
(lambda (thing)
(ask self 'SAY (list "I drop" (ask thing 'NAME)
"at" (ask (ask self 'LOCATION) 'NAME)))
(ask thing 'CHANGE-LOCATION (ask self 'LOCATION))))
...
(else (get-method message mobile-thing-part container-part))))))
(define (create-avatar name birthplace)
; symbol, place -> avatar
(create-instance make-avatar name birthplace))
(define (make-avatar self name birthplace)
(let ((person-part (make-person self name birthplace)))
(lambda (message)
(case message
((TYPE) (lambda () (type-extend 'avatar person-part)))
((LOOK-AROUND) ; report on world around you
(lambda ()
...))
((GO)
(lambda (direction) ; Shadows person's GO
(let ((success? (ask person-part 'GO direction)))
(if success? (ask clock 'TICK))
success?)))
((DIE)
(lambda (perp)
(ask self 'SAY (list "I am slain!"))
(ask person-part 'DIE perp)))
(else (get-method message person-part))))))
The avatar also implements an additional message, LOOK-AROUND, that you will find very useful when running simulations to get a picture of what the world looks like around the avatar.
In order to provide for the passage of time on our system, we have a global clock object, whose implementation may be found in objsys.scm. This class has exactly one instance which is created when objsys.scm is loaded and bound to the globally accessible variable clock. Unlike the real world, time passes only when we want it to, by asking the clock to TICK. The rest of the system finds out that time has passed because the clock informs them by sending them a message. However, not every object cares about time, so the clock only informs objects that have indicated to the clock that they care.
In order to hear about the passage of time, an object registers a callback with the clock. A callback is the promise to send a particular message to a particular object when the callback is activated. As with everything else in our system, a callback is an instance, in this case of the class clock-callback. clock-callbacks are created with a name, an object, and a message. When a clock-callback is ACTIVATEd, it sends the object the message (e.g. it does (ask object message)).
To register a callback with the clock, use ADD-CALLBACK to add your callback to the clock's list of callbacks. When the clock TICKs, it ACTIVATEs every callback on its list. An example of the process, which registers a callback named do-thingy to invoke the THINGY method on the current object:
(ask clock 'ADD-CALLBACK
(create-clock-callback 'do-thingy self 'THINGY))
Remember to remove callbacks (with REMOVE-CALLBACK) when the object should no longer be responding to time.
Our world would be a rather lifeless place unless we had objects that could somehow "act" on their own. We achieve this by further specializing the person class. An autonomous-person is a person who can move or take actions at regular intervals, as governed by the clock through a callback. As described above, the instance indicates that it wants to know when the clock ticks by registering a callback with the clock. It does this upon creation by placing the code to add the callback in the INSTALL method. Once again, the INSTALL method wants to specify additional behavior, so it calls the superclass' method by asking the person-part. Also note how, when an autonomous person dies, we send a "remove-callback" message to the clock, so that we stop asking this character to act.
(define (create-autonomous-person name birthplace activity miserly)
(create-instance make-autonomous-person name birthplace activity miserly))
(define (make-autonomous-person self name birthplace activity miserly)
(let ((person-part (make-person self name birthplace)))
(lambda (message)
(case message
((TYPE) (lambda () (type-extend 'autonomous-person person-part)))
((INSTALL)
(lambda ()
(ask person-part 'INSTALL)
(ask clock 'ADD-CALLBACK
(create-clock-callback 'move-and-take-stuff self
'MOVE-AND-TAKE-STUFF))))
((MOVE-AND-TAKE-STUFF)
(lambda ()
;; first move
(let loop ((moves (random-number activity)))
(if (= moves 0)
'done-moving
(begin
(ask self 'MOVE-SOMEWHERE)
(loop (- moves 1)))))
;; then take stuff
(if (= (random miserly) 0)
(ask self 'TAKE-SOMETHING))
'done-for-this-tick))
((DIE)
(lambda (perp)
(ask clock 'REMOVE-CALLBACK self 'move-and-take-stuff)
(ask self 'SAY '("SHREEEEK! I, uh, suddenly feel very faint..."))
(ask person-part 'DIE perp)))
((MOVE-SOMEWHERE)
(lambda ()
(let ((exit (random-exit (ask self 'LOCATION))))
(if (not (null? exit)) (ask self 'GO-EXIT exit)))))
((TAKE-SOMETHING)
(lambda ()
(let* ((stuff-in-room (ask self 'STUFF-AROUND))
(other-peoples-stuff (ask self 'PEEK-AROUND))
(pick-from (append stuff-in-room other-peoples-stuff)))
(if (not (null? pick-from))
(ask self 'TAKE (pick-random pick-from))
#F))))
(else (get-method message person-part))))))
Our world is built by the setup procedure that you will find in the file setup.scm. You are the deity of this world. When you call setup with your name, you create the world. It has rooms, objects, and people based on a minor technical college on the banks of the Mighty Chuck River; and it has an avatar (a manifestation of you, the deity, as a person in the world). The avatar is under your control. It goes under your name and is also the value of the globally-accessible variable me. Each time the avatar moves, simulated time passes in the world, and the various other creatures in the world take a time step. The way this works is that there is a clock that sends an activate message to all callbacks that have been created. This causes certain objects to perform specific actions. In addition, you can cause time to pass by explicitly calling the clock, e.g. using (run-clock 20).
If you want to see everything that is happening in the world, do
(ask screen 'DEITY-MODE #t)which causes the system to let you act as an all-seeing god. To turn this mode off, do
(ask screen 'DEITY-MODE #f)in which case you will only see or hear those things that take place in the same place as your avatar is. To check the status of this mode, do
(ask screen 'DEITY-MODE?)
To make it easier to use the simulation we have included a convenient procedure, thing-named for referring to an object at the location of the avatar. This procedure is defined in the file objsys.scm.
When you start the simulation, you will find yourself (the avatar) in one of the locations of the world. There are various other characters present somewhere in the world. You can explore this world, but the real goal is to survive the onslaught of the denizens of the darkness.
Here is a sample run of a variant of the system (we have added a few new objects to this version but it gives you an idea of what will happen). Rather than describing what's happening, we'll leave it to you to examine the code that defines the behavior of this world and interpret what is going on.
(setup 'ben) ;Value: ready (ask (ask me 'location) 'name) ;Value: stata-center (ask me 'look-around) You are in stata-center You are not holding anything. You see stuff in the room: sicp There are no other people around you. The exits are in directions: down up south north west ;Value: ok (ask me 'take (thing-named 'sicp)) At stata-center ben says -- I take sicp from stata-center ;Value: #[compound-procedure 5] (ask me 'go 'west) ben moves from stata-center to 34-301 --- Tick 0 --- grumpy-grad-student moves from barker-library to 10-250 At 10-250 grumpy-grad-student says -- Hi course-6-frosh At 10-250 grumpy-grad-student bites course-6-frosh ! At 10-250 course-6-frosh says -- Ouch! 7 hits is more than I want! At 10-250 course-6-frosh says -- SHREEEEK! I, uh, suddenly feel very faint... An earth-shattering, soul-piercing scream is heard... ben-bitdiddle moves from lobby-10 to lobby-7 alyssa-hacker moves from great-court to legal-seafood chuck-vest moves from bexley to student-center lambda-man moves from lobby-10 to lobby-7 At lobby-7 lambda-man says -- Hi ben-bitdiddle lambda-man moves from lobby-7 to lobby-10 ;Value: #t (run-clock 5) --- Tick 1 --- ben-bitdiddle moves from lobby-7 to lobby-10 At lobby-10 ben-bitdiddle says -- Hi lambda-man ben-bitdiddle moves from lobby-10 to 10-250 At 10-250 ben-bitdiddle says -- Hi grumpy-grad-student ben-bitdiddle moves from 10-250 to barker-library At barker-library ben-bitdiddle says -- I take engineering-book from barker-library alyssa-hacker moves from legal-seafood to edgerton-hall chuck-vest moves from student-center to bexley lambda-man moves from lobby-10 to lobby-7 lambda-man moves from lobby-7 to lobby-10 lambda-man moves from lobby-10 to lobby-7 --- Tick 2 --- ben-bitdiddle moves from barker-library to 10-250 At 10-250 ben-bitdiddle says -- Hi grumpy-grad-student At 10-250 ben-bitdiddle says -- I try but cannot take blackboard alyssa-hacker moves from edgerton-hall to 34-301 At 34-301 alyssa-hacker says -- Hi ben alyssa-hacker moves from 34-301 to eecs-hq chuck-vest moves from bexley to baker At baker chuck-vest says -- I take tons-of-code from baker lambda-man moves from lobby-7 to lobby-10 --- Tick 3 --- grumpy-grad-student moves from 10-250 to lobby-10 At lobby-10 grumpy-grad-student says -- Hi lambda-man At lobby-10 grumpy-grad-student bites lambda-man ! At lobby-10 lambda-man says -- Ouch! 8 hits is more than I want! At lobby-10 lambda-man says -- SHREEEEK! I, uh, suddenly feel very faint... An earth-shattering, soul-piercing scream is heard... ben-bitdiddle moves from 10-250 to barker-library ben-bitdiddle moves from barker-library to 10-250 At 10-250 ben-bitdiddle says -- I take recitation-problem from 10-250 alyssa-hacker moves from eecs-hq to 34-301 At 34-301 alyssa-hacker says -- Hi ben chuck-vest moves from baker to bexley --- Tick 4 --- ben-bitdiddle moves from 10-250 to lobby-10 At lobby-10 ben-bitdiddle says -- Hi grumpy-grad-student ben-bitdiddle moves from lobby-10 to great-court ben-bitdiddle moves from great-court to legal-seafood alyssa-hacker moves from 34-301 to edgerton-hall alyssa-hacker moves from edgerton-hall to 34-301 At 34-301 alyssa-hacker says -- Hi ben At 34-301 alyssa-hacker says -- I take sicp from ben At 34-301 ben says -- I lose sicp At 34-301 ben says -- Yaaaah! I am upset! chuck-vest moves from bexley to baker At 10-250 body-of-course-6-frosh rises as a vampire! --- Tick 5 --- grumpy-grad-student moves from lobby-10 to grendels-den ben-bitdiddle moves from legal-seafood to edgerton-hall ben-bitdiddle moves from edgerton-hall to 34-301 At 34-301 ben-bitdiddle says -- Hi alyssa-hacker ben At 34-301 ben-bitdiddle says -- I take sicp from alyssa-hacker At 34-301 alyssa-hacker says -- I lose sicp At 34-301 alyssa-hacker says -- Yaaaah! I am upset! alyssa-hacker moves from 34-301 to edgerton-hall chuck-vest moves from baker to bexley ;Value: done (ask screen 'deity-mode #f) ;Value: #t (run-clock 3) --- Tick 6 --- ben-bitdiddle moves from 34-301 to stata-center --- Tick 7 --- An earth-shattering, soul-piercing scream is heard... --- Tick 8 --- ;Value: done
In parts of this project, you will be asked to elaborate or enhance the world (e.h. add things in setup.scm), as well as add to the behaviors or kinds of objects in the system (e.g. modify objtypes.scm). If you do make such changes, you must remember to re-evaluate all definitions and re-run (setup 'your-name) if you change anything, just to make sure that all your definitions are up to date. An easy way to do this is to reload all the files (be sure to save your files to disk before reloading), and then re-evaluate (setup 'your-name).
Warmup Exercise 1
In the transcript above there is a line: (ask (ask me 'location) 'name). What kind of value does (ask me 'location) return here? What other messages, besides name, can you send to this value?Warmup Exercise 2
Look through the code in objtypes.scm to discover which classes are defined in this system and how the classes are related. For example, place is a subclass of named-object. Also look through the code in setup.scm to see what the world looks like. Draw a class diagram like the ones presented in lecture. You will find such a diagram helpful (maybe indispensable) in doing the programming assignment.Warmup Exercise 3
Look at the contents of the file setup.scm. What places are defined? How are they interconnected? Draw a map. You must be able to show the places and the exits that allow one to go from one place to a neighboring place.Warmup Exercise 4
Aside from you, the avatar, what other characters roam this world? What sorts of things are around? How is it determined which room each person and thing starts out in?Warmup Exercise 5
To warm up, load the three files objsys.scm, objtypes.scm and setup.scm and start the simulation by typing (setup '<your name>). Walk the avatar to a room that has an unowned object. Have the avatar take this object, only to drop it somewhere else. Show a transcript of this session.Warmup Exercise 6
Draw the environment diagram that results from the define book expression below. (Assume that the-floor is bound to some container object, which you may draw as a fuzzy cloud.) You may want to take inspiration from the environment diagrams shown in lecture 15.(define book (create-mobile-thing 'book the-floor)) ; Note: corrected from create-thing in original project releaseThen assume that the ask expression below is evaluated. (Once again, my-desk points to a fuzzy cloud)
(ask book 'change-location my-desk)Show only what changes in the frames you drew for the define above.
You're walking through the tunnels in the underside of the institute, avoiding the rain. As usual, you haven't seen anyone else down here other than that grad student who looked a little spooked when you came around the corner. Pausing for a moment, you try to remember whether you turn here, or at the next intersection, when you think you hear footsteps coming down the hall towards you. The footsteps get louder as the unseen person approaches. They sound... confident. Around the nearest corner swings a young man, slightly scruffy with long hair; his black leather coat lifted out behind him. He's kinda pasty white, but aren't most course 6 people that way? You've seen him before somewhere... in lab? Maybe in a cluster?
As he approaches you, you start to get this funny feeling in your stomach. He smiles at you... wait! His incisors seem way overdeveloped, almost fang-like. With a sudden roar, he leaps at you, opens his mouth, and attempts to bite you on the neck! Screaming, you push him off you and turn to run away. Running down the corridor towards you is a blond woman wearing a "six hertz" T-shirt, with a stake in her hand. "Duck!" she yells, and vaults over you swinging the stake. A screeching noise comes from behind you and as you stumble around, the young man turns to dust impaled on the stake. The young woman turns to you and says "Hi, I'm Buffer the Vampire Slayer."
Computer Exercise 1: An adventure of self-discovery
We have provided you a powerful tool for exploring the structure of the object system in the form of two procedures: show-instance and show-handler. The show-instance procedure works on things built with a create-foo procedure. The show-handler procedure works on things built with a make-foo procedure. For example, after doing a (setup 'your-name), we can examine the avatar instance with:
(show-instance me)
INSTANCE #[compound-procedure 6]
TYPE: (avatar person mobile-thing thing named-object root container)
HANDLER FRAME: #[environment 7]
Parent frame: #[environment 8]
person-part: #[compound-procedure 9]
Parent frame: global-environment
self: #[compound-procedure 6]
name: ben
birthplace: #[compound-procedure 10]
HANDLER PROCEDURE:
(lambda (message)
(cond ((eq? message (quote type)) (lambda #f (type-extend ... person-part)))
((eq? message (quote look-around)) (lambda #f (let ... ...)))
((eq? message (quote go)) (lambda (direction) (let ... ... success?)))
((eq? message (quote die)) (lambda (perp) (ask self ... ...) (ask person-part ... perp)))
(else (get-method message person-part))))
;Value: instance
Your numbers may differ, as they are assigned based on when the procedures are created, but the form should be the same. The printout includes the type and state of the object, including the handlers for the parents of the object. From here, you can continue to explore the world by looking at any of the procedures displayed. For example, to look at the birthplace (which was #[compound-procedure 10]):
(show-instance #[compound-procedure 10])
INSTANCE #[compound-procedure 10]
TYPE: (place named-object root container)
HANDLER FRAME: #[environment 11]
Parent frame: #[environment 12]
named-obj-part: #[compound-procedure 13]
container-part: #[compound-procedure 14]
exits: (#[compound-procedure 19] #[compound-procedure 18]
#[compound-procedure 17] #[compound-procedure 16]
#[compound-procedure 15])
Parent frame: global-environment
self: #[compound-procedure 10]
name: stata-center
HANDLER PROCEDURE:
(lambda (message)
(cond ((eq? message (quote type)) (lambda #f (type-extend ... named-obj-part container-part)))
((eq? message (quote exits)) (lambda #f exits))
((eq? message (quote exit-towards)) (lambda (direction) (find-exit-in-direction exits direction)))
((eq? message (quote add-exit)) (lambda (exit) (let ... ...)))
(else (get-method message container-part named-obj-part))))
;Value: instance
In this manner, you can explore the any object in the system. You can use #@10 as a shortcut for #[compound-procedure 10]. Some of the procedures are instances and some are handlers; you'll have to figure out which procedure is which in order to apply the correct procedure. For example, if you use show-handler on an instance, you'll get:
(show-handler #@10)
HANDLER FRAME: #[environment 62]
Parent frame: #[environment 63]
handler: #[compound-procedure 64]
Parent frame: global-environment
HANDLER PROCEDURE:
(lambda (message)
(if (eq? message (quote set-handler!))
(lambda (handler-proc)
(set! handler handler-proc))
(get-method message handler)))
;Value: handler
Needless to say, this isn't very informative!
Computer Exercise 2: Just lookin' around
Change the behavior of the avatar, to LOOK-AROUND whenever it successfully moves to a new location. Shows the change to your code, and demonstrate it working in an example scenario.Computer Exercise 3: It's just like "off", but for vampires
Implement a new type of mobile-thing, a holy object. Holy objects do not accept any new types of messages, being the simplest extension of a mobile-object. Then change vampires so that if they attempt to bite a person carrying a holy object they will be repulsed and unable to cause any SUFFERing. When thwarted, vampires should express their displeasure verbally. Modify the code in setup.scm to populate the world with some holy-objects.
Turn in the entire make-holy-object code, but only the methods changed in make-vampire. Demonstrate the effectiveness of your holy objects with test cases.
Computer Exercise 4: Agents of pest control
By now, you've discovered that after wandering around for a while, the institute is filled with pesky vampires that would like to induct you into their club. So it might help if you had a way to defend yourself.
Implement a new type of object, a weapon. Since a weapon should be something that can be transported from place to place, you should think about the class of objects from which it should inherit. The procedure that makes weapons should take as inputs a name, a location and a maximal amount of damage that it can inflict (see setup.scm for the order of these arguments). A weapon should support these methods:
Computer Exercise 5: We're all doomed
The institute has a number of people who walk the halls muttering gloomy predictions of the future:
Add a new class, called gloomy-oracle, that wanders around the world muttering pronouncements of doom. It should have a set of sayings that it picks from (possibly including the results of calling the procedure (time-left), which returns a string that indicates the amount of time remaining until the apocalypse!). Remember that when the oracle DIEs, it should stop talking!
Turn in a listing of the gloomy-oracle code and a test run that shows it working.
Computer Exercise 6: Writing a buffer implementation
Rather than wandering around and smiting all the vampires yourself, there's someone who should be doing this job for you: Buffer the Vampire Slayer. Highly skilled in armed and unarmed combat, she seeks out vampires and reduces them to dust.
Add a new class, called vampire-slayer, that wanders around hunting vampires. Slayers should wander around picking up weapons and killing any vampires they come across. They should under no circumstances attack normal people. If they don't have a weapon handy, they can punch the vampires for 3 damage.
Turn in a listing of the vampire-slayer code and a test run that shows it working.
Computer Exercise 7: Preventing Buffer-Overflow
In order to lengthen the lifespan of the Slayer, Slayers should only take 1 point of SUFFERing from vampires, regardless of how hard they get hit. Damage from anyone else should be dealt with normally.
Additionally, the vampire-slayer should attempt to carry around one holy-object. Thus, it should try to get one if it doesn't already have one.
Finally, when the vampire-slayer dies, a new slayer should pop up somewhere else in the simulation.
Turn in the changes you made to the vampire-slayer code and a test run that demonstrates the new functionality.
Computer Exercise 8: Your turn
Now that you have had an opportunity to play with our "world" of characters, places, and things, we want you to extend this world in some substantial way. The last part of this project gives you an opportunity to do this. As you haven't had much freedom to design your own code up until now, this exercise gives you the opportunity to demonstrate your knowledge of design principles. NOTE: this exercise is worth significantly more points than the others; give it the time it deserves!
This is your opportunity to have fun with the game! There's only one hard requirement: you must add at least one new class to the system. Other than that, you are free to take the simulation in any direction that interests and excites you. You don't have to stick with the buffer-the-vampire-slayer theme if you don't want to. Here a couple of ideas we've come up with for extensions, but you certainly aren't limited to these:
Here, we want you to plan out the design for some extensions to your world. You will submit a brief description of your plan by email to your TA on Wednesday, April 7th at the latest (send earlier if possible!).
We want you to design some new elements to our world. The first thing we want you to do is design a new class of objects to incorporate into the world. To do this, you should plan each of the following elements:
Attempt to follow your plan. Remember to test your code in a number of ways to ensure that it does what you intended it to do.
Your submission should have a couple of components:
Write up a BRIEF description of your design, addressing each of the issues raised above.
For this part of the project, using the online tutor submit your code and a transcript of your system in action. Do not just submit the entire file of objects, rather submit only those changes (if any) that you have made to the existing system, and the new code that you have written. Be sure to document appropriately!
We will award prizes for the most interesting modifications combined with the cleverest technical ideas. Note that it is more impressive to implement a simple, elegant idea than to amass a pile of characters and places.
Please respond to the following question as part of you answers to the questions in the project set:
We encourage you to work with others on problem sets as long as you acknowledge it (see the 6.001 General Information handout) and so long as you observe the rules on collaboration and using "bibles". If you cooperated with other students, LA's, or others, please indicate your consultants' names and how they collaborated. Be sure that your actions are consistent with the posted course policy on collaboration.