Project 2: Adventure Game (or The Marvelous Misadventure of Michael Matloob) We'll provide most of the program. You will mostly make modification and some additions. PART I DUE: 7/20 PART II DUE: 7/27 This project is designed to be done by two people working in parallel, then combining your results into one finished product. (Hereafter the two partners are called Person A and Person B.) But you will combine your work to hand in a single report for your group. The project begins with two exercises that everyone should do; these exercises do not require new programming, but rather familiarize you with the overall structure of the program as we've provided it. After that, each person has separate exercises. This is a two-week project to be released in two stages. You and your partners will need to combine your work at the end of each stage. You will make a submission at the end of each stage. Also, at the end of each stage, you can load one chapter of a story told through the adventure game. (Acknowledgement: This assignment is loosely based on an MIT homework assignment in their version of this course.) In this assignment, we will be exploring two key ideas: the simulation of a world in which objects are characterized by a set of state variables, and the use of message passing as a programming technique for modularizing worlds in which objects interact. OBJECT-ORIENTED PROGRAMMING is becoming an extremely popular methodology for any application that involves interactions among computational entities. Examples: -- operating systems (processes as objects) -- window systems (windows as objects) -- games (asteroids, spaceships, gorillas as objects) -- drawing programs (shapes as objects) == PART 1 == (DUE: 7/20) GETTING STARTED To start, copy the following five files into your directory: ~cs61a/lib/obj.scm The object-oriented system* ~cs61a/lib/tables.scm An ADT you'll need for some parts* ~cs61a/lib/adv/adv.scm The adventure game program ~cs61a/lib/adv/story1.scm The story program chapter 1* ~cs61a/lib/adv/adv-world.scm A world you can use for debugging * -- You do not need to read/understand these programs To work on this project, you must have all the files in the same directory. You can then start your world by loading adv-world.scm or story1.scm although the story will likely error until you have finished the corresponding part. To get started, you should also have grazed through the OOP: Above the Line notes and the OOP: Reference Manual to familiarize yourself with OOP syntax. If you see a strange syntax, feel free to ask about it on the newsgroup. **** Side Note: GUI ****** We have also built a prototype Graphical User Interface for the adventure game that you can use to see what your world looks like. You can use GUI by copying the file ~cs61a/lib/adv/gui.scm to your project directory and load the file gui.scm. The GUI currently displays only the places, the people, and the things in the places that don't belong to anyone. You can move the camera by pressing the direction buttons. You can also move the people by pressing on a person and then pressing one of the direction buttons. You can have the person take something by pressing on the person and then pressing on the item. Alternatively, you can also type any command in the STk prompt and then click the reload button to have the change show up in the GUI. ************** In this program there are three classes: THING, PLACE, and PERSON. Here are some examples selected from adv-world.scm: ;;; construct the places in the world (define Soda (instantiate place 'Soda)) (define BH-Office (instantiate place 'BH-Office)) (define Sproul-Plaza (instantiate place 'Sproul-Plaza)) (define Telegraph-Ave (instantiate place 'Telegraph-Ave)) (define Noahs (instantiate place 'Noahs)) (define Intermezzo (instantiate place 'Intermezzo)) (define s-h (instantiate place 'sproul-hall)) ;;; make some things and put them at places (define bagel (instantiate thing 'bagel)) (ask Noahs 'appear bagel) (define coffee (instantiate thing 'coffee)) (ask Intermezzo 'appear coffee) ;;; make some people (define Brian (instantiate person 'Brian BH-Office)) (define hacker (instantiate person 'hacker Soda)) ;;; connect places in the world (can-go Soda 'south sproul-plaza) (can-go sproul-plaza 'north soda) Having constructed this world, we can now interact with it by sending messages to objects. Here is a short example. ; We start with the hacker in Soda. > (ask Soda 'exits) (NORTH SOUTH) > (ask hacker 'go 'north) HACKER moved from Sproul Plaza to SODA We can put objects in the different places, and the people can then take the objects: > (define Jolt (instantiate thing 'Jolt)) JOLT > (ask Soda 'appear Jolt) APPEARED > (ask hacker 'take Jolt) HACKER took JOLT TAKEN You cannot take objects away from people yet. The first two exercises in this part should be done by everyone -- that is, everyone should actually sit in front of a terminal and do it! It's okay to work in pairs as long as you all really know what's going on by the time you're finished. (Nevertheless, you should only hand in one solution, that both agree about.) The remaining exercises have numbers like "A3" which means exercise 3 for Person A. After you've done the work separately, you should meet together to make sure that you each understands what the other person did, because the second week's work depends on all of the first week's work. You can do the explaining while you're merging the two sets of modifications into one adv.scm file to hand in. You should also try out your program on the story file by just loading story1.scm. Note though that just because the story can run until completion does not mean that your program is completely correct. YOU DO NOT NEED TO TURN IN THIS EXERCISE: 1. Create a new person to represent yourself. Put yourself in a new place called Dormitory (or wherever you live) and connect it to campus so that you can get there from here. Create a place called Kirin, north of Soda. (It's actually on Solano Avenue.) Put a thing called Potstickers there. Then give the necessary commands to move your character to Kirin, take the Potstickers, then move yourself to where Brian is, put down the Potstickers, and have Brian take them. LIST ALL THE MESSAGES THAT ARE SENT DURING THIS EPISODE. It's a good idea to see if you can work this out in your head, at least for some of the actions that take place, but you can also trace the ASK procedure to get a complete list. You don't have to hand in this listing of messages. (Do hand in a transcript of the episode without the tracing.) The point is that you should have a good sense of the ways in which the different objects send messages back and forth as they do their work. [Tip: we have provided a MOVE-LOOP procedure that you may find useful as an aid in debugging your work. You can use it to move a person repeatedly.] YOU DO NOT NEED TO TURN IN THIS EXERCISE: 2. It is very important that you think about and understand the kinds of objects involved in the adventure game. Please answer the following questions: 2A. What kind of thing is the value of variable BRIAN? Hint: What is returned by scheme in the following situation: You type: > BRIAN 2B. List all the messages that a PLACE understands. (You might want to maintain such a list for your own use, for every type of object, to help in the debugging effort.) 2C. We have been defining a variable to hold each object in our world. For example, we defined bagel by saying: (define bagel (instantiate thing 'bagel)) This is just for convenience. Every object does not have to have a top-level definition. Every object DOES have to be constructed and connected to the world. For instance, suppose we did this: > (can-go Telegraph-Ave 'east (instantiate place 'Peoples-Park)) ;;; assume BRIAN is at Telegraph > (ask Brian 'go 'east) What is returned by the following expressions and WHY? > (ask Brian 'place) > (let ((where (ask Brian 'place))) (ask where 'name)) > (ask Peoples-park 'appear bagel) 2D. The implication of all this is that there can be multiple names for objects. One name is the value of the object's internal NAME variable. In addition, we can define a variable at the top-level to refer to an object. Moreover, one object can have a private name for another object. For example, BRIAN has a variable PLACE which is currently bound to the object that represents People's Park. Some examples to think about: > (eq? (ask Telegraph-Ave 'look-in 'east) (ask Brian 'place)) > (eq? (ask Brian 'place) 'Peoples-Park) > (eq? (ask (ask Brian 'place) 'name) 'Peoples-Park) OK. Suppose we type the following into scheme: > (define computer (instantiate thing 'Durer)) Which of the following is correct? Why? (ask 61a-lab 'appear computer) or (ask 61a-lab 'appear Durer) or (ask 61a-lab 'appear 'Durer) What is returned by (computer 'name)? Why? 2F. Sometimes it's inconvenient to debug an object interactively because its methods return objects and we want to see the names of the objects. You can create auxiliary procedures for interactive use (as opposed to use inside object methods) that provide the desired information in printable form. For example: (define (name obj) (ask obj 'name)) (define (inventory obj) (if (person? obj) (map name (ask obj 'possessions)) (map name (ask obj 'things)))) Write a procedure WHEREIS that takes a person as its argument and returns the name of the place where that person is. Write a procedure OWNER that takes a thing as its argument and returns the name of the person who owns it. (Make sure it works for things that aren't owned by anyone.) Procedures like this can be very helpful in debugging the later parts of the project, so feel free to write more of them for your own use. Now it's time for you to make your first modifications to the adventure game. This is where you split the work individually. *[NOTE: although person B has more problems to work through, person A's problems are trickier and harder to debug.]* PERSON A: A3. We have already partially implemented a thief class for you A thief is a kind of a person who can take things away from other people. Fill in the method (STEAL new-thing). When steal is called on a thing, the thief will just take the thing if no one owns him. Otherwise, the thief will steal it and cause the original owner to have fits. (by calling the have-fit procedure defined at bottom) You should also check to make sure that the thing is in the current location of the thief and is indeed a thing. [Side Note: because a thief is inherited from person, the thief class cannot access instance variables in the person class just by its name. You must call (ask self 'place) or (usual 'place) for example to access the current place, you cannot just say place.] Part of the exercise is for you to determine exactly what it means to steal something. For instance, the THING class has a POSSESSOR instance variable that certainly needs to be updated. You will need to determine if there are other stored information that you should change. To get started, you should look at the TAKE method from the PERSON class. A4a. Stealing things cannot go unpunished in our world! adv.scm already created a place called jail that has no exits. Your job is to define a class called police. Police is a kind of PERSON who can arrest people. The police should have a method to indicate its type. The police should also have a method (ARREST new-thief). When the arrest method is called, the police should examine and remove everything in the thief's possession. If the thief has something that he stole from someone, the police should return it directly to its original owner (ask your partner about the TAKE-DIRECTLY method in the PERSON class). If the thief has something that he did not steal, the police takes that thing for himself. Afterwards, the police sends the thief directly to jail (ask your partner about the GO-DIRECTLY-TO method in the person class). To write arrest, you will likely need to modify your thief class to keep some extra information. You might find the idea of an association list helpful. An assoc list is a list of cons pairs. We say the car is the key and the cdr is the value. The primitive procedure (assoc key assoc-list) will take a key and an association list and return the value that correspond to the key if one exists and #f if it can't find the key. Be careful to make sure that the person you are arresting is in the same place as you and is a thief. A4b. Notice that PERSON has a talk and a set-talk! method. These are triggered by the NOTICE method. Your police class should have the phrase "Crime does not pay!" as the default-saying so that if I ask the police to talk, it will say "Crime does not pay!". In the police NOTICE method, if the police notices that the new person is a thief, the police should talk and then arrest the person directly. If the new person is not a thief, the police should be quiet and do nothing. --- End of Part I for Person A PART I, PERSON B: B3. Define a method (TAKE-DIRECTLY new-thing) for the PERSON class. The TAKE method for PERSON can only take something if it is in the same place as the person. TAKE-DIRECTLY is just like TAKE; it gives a thing to the person but unlike TAKE, the input-thing to TAKE-DIRECTLY does not belong in any place and does not belong to anyone (you should check for these conditions) [Note: you might need to modify the thing class to keep track of where the thing is at currently and APPEAR and GONE methods in PLACE class.] B4. Define a method (GO-DIRECTLY-TO new-place) for the PERSON class. Go-directly-to does not require that the new-place be adjacent to the current place. So by calling (ask SOMEONE 'go-directly-to SOMEWHERE) will send the person SOMEONE to SOMEWHERE immediately. B5. PERSON in our world should have attributes like STRENGTH or INTELLIGENCE. However, we aren't going to clutter up the person class by adding a local STRENGTH variable. That's because we can anticipate wanting to add lots more attributes as we develop the program further. People can have CHARISMA or WISDOM; things can be FOOD or not; places can be INDOORS or not. Therefore, you will create a class called BASIC-OBJECT that keeps a local variable called PROPERTIES containing an abstract data type called Table. A Table in this case is one-dimensional; you can lookup a key and find the corresponding value. constructor: (make-table) returns a new, empty table. mutator: (insert! key value table) adds a new key-value pair to a table. selector: (lookup key table) returns the corresponding value, or #F if the key is not in the table. You'll learn how tables are implemented in 3.3.3 (pp. 266-268). For now, just take them as primitive. You'll modify the person, place and thing classes so that they will inherit from basic-object. This object will accept a message PUT so that > (ask Brian 'put 'strength 100) does the right thing. Also, the basic-object should treat any message not otherwise recognized as a request for the attribute of that name, so > (ask Brian 'strength) 100 should work WITHOUT having to write an explicit STRENGTH method in the class definition. Don't forget that the property list mechanism in 3.3.3 returns #F if you ask for a property that isn't in the list. This means that > (ask Brian 'charisma) should never give an error message, even if we haven't PUT that property in that object. This is important for true-or-false properties, which will automatically be #F (but not an error) unless we explicitly PUT a #T value for them. Give people some reasonable (same for everyone) initial strength. B6. Create method (RANDOMLY-MOVE num) for PERSON class. Randomly-move will move a person to a random adjacent location and do this num number of times. A helper procedure (pick-randomly ls) has been defined for you that takes a list and returns a random element in the list. B7a. Define a FOOD class. A food is a type of thing that people can eat. It should have both NAME and CALORIES as instantiation variable, so a food item should be instantiated as (instantiate food NAME CALORIES) Also, calling (ask SOMEFOOD 'food?) should return #t; you should implement this through the attribute-table scheme you built in B5. The FOOD class does not need its own methods. B7b. Give PERSON an EAT method. The EAT method takes no arguments and when called, the person will consume all the food items in his possessions. Furthermore, for every food item that the person consumes, the person should increase his strength by the amount of calories a food item has. You should also display a message to indicate that the PERSON has eaten. --- END of Part I for Person B You should now combine your work with your partner. You can also now load story1.scm for the first chapter of the Marvelous Misadventure of Michael Matloob. Any similarity between the names used in the story and names of real world places/people is purely coincidental. You only need to turn in one code file, the modified adv.scm file. Name your file LOGIN1.LOGIN2.adv1.scm For example, if your login is cs61a-xy and your partner's is cs61a-uv, then you should turn in xy.uv.adv1.scm. The order of logins do not matter. IMPORTANT: you also need to turn in a transcript file, called LOGIN1.LOGIN2.transcript. You should show that you have adequately tested your project in the transcript file.