| Out: | Tuesday, May 10 | |
| Due: | Rough cut: | Friday, May 20, 11:59 pm |
| Rough cut crits: | Tuesday, May 24, 11:59 pm | |
| Final version: | Thursday, June 2, 11:59 pm | |
In this assignment, you will make a text and/or point-and-click adventure game using simple object-oriented simulation techniques in Meta.
This assignment requires a new version of Meta. So before starting the assignment, you should uninstall your current version and get the latest version from the web site. Next, you should download the code for the assignment and unzip the file into a directory in some convenient place.
Double-click the file Adventure.meta (or otherwise start Meta and open the file), and choose Execute All from the menu. Then open the file Sample game.meta, do Execute All, and run the procedure: [sample-game].
Here are the text commands it will recognize:
For commands that take a Thing as an argument, the thing must be in the room the player is presently in. The object can be specified by its name or by its type. That is, if there's a door called "symposium", you can say either "go to the door" or "go to symposium".
If you want to write a game where players can specify objects by their adjectives (as in "go to the big door" versus "go to the little door"), let me know and I'll hair up the parser and send you the revised version.
Try out the game, and then look through the code in Sample game.meta. Make sure you have a rough understanding of how it works.
Now make your own game. Start by making a new .meta file in the directory and adding its name to the variable source-files in the file Adventure.meta (place it at the end of the list of filenames so that it loads after the other files). Now make a procedure, run, as follows:
[define run
[→ place code here
[the-player ← [new Player "some name" some-place]]
other code here (optional)
[start-game "game name" width image-height text-pane-height input-height]]]
Fill in the italics as appropriate:
Your job is to build a game that you like. In order to prevent you from deciding that the game you like is outrageously simple, we are placing a few restrictions on your game:
If you add new fields or methods to existing classes, you can count those toward the total fields and methods. For example, some of you will undoubtedly want to add a combat system, in which case you need to add things like hit points and magic points to the basic Player class. Or you may want to add an inventory system (meaning give the player the ability to carry stuff) in which case you need to give the player pockets.
We've written up a FAQ for how to do simple things. Write to Ian with any questions you have for how to do fancier things (i.e. "how do I make places display different pictures at different times?") and I'll add them to the FAQ.
There are three due dates for this assignment. First, you will turn in a partial version of your game in a week and a half. You must have at least 1/4 of the total work done, or 2 places, 4 objects, 2 new classes, 1 new field, and 2 new methods, to get full credit for this part. However, there will be no extensions on this part because it will be critiqued. So if you don't have it done yet, upload whatever you can or you will miss the crit entirely and get a zero for it.
To turn in your assignment (both for part 1 and part 3), make a new zip file containing the complete Adventure directory (including your sounds and images) and upload it to the assignment server.
After everyone has uploaded their assignments, the web site software will anonymously assign everyone a set of games to critique. You will then download them, run them, and fill in a critique form on line. That will be the second part of the assignment. Your critiques will then be sent (again, anonymously) to the authors of their respective games, and you will see the critiques of your game. You are also free to continue working on your game during this period.
Finally, you will look at the feedback from other students, finish your game, and upload the completed version.
To make a new room, you just:
Start by adding the room to the game and specifying the image for the room. The run the game and go to the room.
That's all there is to it.
Notes:
To add a thing to your game, you would just choose a name for it (or use null, if you want the object unnamed), and a room to place it in. Then add the following line to the room's setup procedure:
[new Thing "name" place]
where name is the name of the object (or null) and place is the name of the variable in which you put the Place object for that room. You may want to specify the adjectives for the Thing, in which case, you'll need to put the thing in a variable, as in:
[with
mything = [new Thing "name" place]
[mything.adjectives ← [list "big" "green" "silly"]]]
At some point, you may find yourself needing to write code that manipulates this Thing from outside of the setup routine. Then you will need to use a global variable rather than a local variable like you would make using with. So then you need to add another define (outside of the setup routine) to make the global variable and then set it in the setup routine for its room. So instead of the command above, you'd have something like:
[mything ←
[new Thing "name" place]]
[mything.adjectives
← [list "big" "green" "silly"]]
with [define mything null] someplace outside of the setup procedure.
This is the same as adding new things, except you say:
[new Door "name" place destination]
That is, you change the name of the type and you add a second place, the destination.
Of course, there is the problem, as discussed in class, that doors are only one-way. So you probably want to make pairs of doors, in which case, you may want to steal the make-door-pair procedure from the sample game and use that.
If you make all your objects be of type Thing, then it will keep printing out text like "you see a thing name Sam". If you want it to say "you see a dog named Sam", then you need to create a new class of objects:
[define
Dog
[class [Dog name location]
Thing]]
and then change your code from (for example):
[new Thing "Sam" the-doghouse]
to:
[new Dog "Sam" the-doghouse]
Suppose you want to make a new kind of door that can be locked. Then you need to make a new class, say, LockableDoor, and add a new field to it called locked?
[define LockableDoor
[class [LockableDoor name location destionation locked?]
Door]]
Notice that this class's parent is Door, rather than Thing, so all the fields and methods for Doors (and Portals and Things) are now automatically used for lockable doors. However, LockableDoors have one extra field, the locked? field. This doesn't help us, though unless lockable doors only let you pass through when they're unlocked. How do we do that? Well, remember from the lecture that the way doors work is that you click on the door or type "go to door" and the system runs the generic procedure called click, and it moves the user through to the destination. We can change this by defining a new method for click that only runs when the argument is a LockableDoor:
[define-method [click [LockableDoor d]]
[if d.locked?
[print-line "Sorry, the door is locked"]
[call-next-method]]]
Notice that we use call-next-method here when the door is unlocked. This is because we want the door to do whatever it is that normal doors do. Rather than looking that up and retyping it, we can simply tell the system to continue on as if the door was a normal Door (or Portal) rather than a LockableDoor.
Of course, you would also need to provide the user with some way of unlocking the door. There are a lot of different ways of doing that, e.g. by providing the player with special keys, or code numbers, or other puzzles. One way or another, you need to give them some sort of command (probably using define-pattern) for unlocking the door.
Suppose you want to make a new kind of door that plays a sound when you go through it. This is even easier than the example above. We just make a new class, NoisyDoor, and define a new click method for it as above:
[define NoisyDoor
[class [NoisyDoor name location destination]
Door]][define-method [click [NoisyDoor d]]
[play-sound door-sound]
[call-next-method]]
This makes a new kind of door, NoisyDoor, and defines a new click method for it that starts by playing some sound and then continues on into the normal click method for doors.
Define-pattern just says what procedure to call when the user types a certain text pattern. So all you have to do is write the procedure to do different things in different places. Note, however, that the system doesn't have notions of north, south, east, and west, or of coordinates of places. So you'd have to modify the system to make it understand that.
There's a generic procedure called view-image, described in unit 6, that tells what picture to show for a given place. At the moment, it only has one method, which just returns the value of the "image" place object (i.e. p.image, if p is the place object). If you want multiple images, you can either change the image field, or you can make a new subclass of Place and write a new method for view-image for that new subclass. Then it can do whatever you want to decide what image to display. So in other words, you say something like:
[define MyPlaceClass
[class [MyPlaceClass fields ...]
Place]][define-method [view-image [MyPlaceClass p]]
... insert code to choose image here ...]
Note, however, that the way you tell the system to redraw the image is to the procedure that's handling user input (i.e. the procedure called by define-pattern or the procedure in the hotspot) return the value: true. If the handler returns true, the system redraws the display. If it returns false, it leaves the display alone.
search-for-object[define
To make the system search other places too, just add their contents to the list being searched. For example:
How do you add a new field to the player object? Read on.
Just open the Player.meta file and add it to the class definition. Then save the file and do [reload]. For example, if you want to add a pocket to the player object, just change the definition of the Player class from:
[define Player
[class [Player name location]
Actor
previous-location]]
to:
[define Player
[class [Player name location]
Actor
previous-location pocket]]
Since, in this case, I added the pockets field to last line, which are fields that don't take their values from the arguments in the new call, we probably want to add an initialize method for Player to make a Place object for the pockets field:
[define-method [initialize [Player p]]
[p.pocket ← [new Place "Pocket"]]]
This says that whenever some new Player object is created, set its pocket field to be a new Place object name "Pocket".