CS 61C, Spring 2010
Due Saturday, February 13th @ 11:59pm
Last Updated 2/6
TA: Bing Xia
Original authors (i.e. these guys are awesome): Matt Johnson and Casey Rodarmor
Project Overview Getting Started File Summary Playing the Game Assignment Part 0: Remember that time... Save File Format Part 1: Items, items, items! Part 2: Where are my items? Part 3: Magic Survival Tips Mini SVN Tutorial Submission Extra for Experts
The usual story with video games is that there are programmers who make the game and gamers who play the game—the two parties and their actions are completely separate. But for this project game, programming the game is part of playing it! To progress through the game world you will need to implement game features, and every feature you complete will allow you to progress further. After you complete each part of the assignment, you should try playing the game to explore how far you can get.
We made this project because other 61C projects have been seriously lacking in FUN! And what's more fun than an adventure game?! We've done our best to make this framework simple and extensible, and we hope that you'll have fun creating new monsters, spells, levels, and items. You can even add new features!
Wow. I am super excited. Are you? Awesome. Let's move on and check out the framework that you'll build on.
The first step is to copy the game framework files into your home directory:
cs61c-tb@akane ~ > cp -r ~cs61c/proj/01/ ~/proj1 cs61c-tb@akane ~ > cd proj1 cs61c-tb@akane ~/proj1 > ls Bing.save game.c inventory.h main.c puzzles.h util.h Makefile game.h item_table monsters.c spec commands.c globals.c level.c monsters.h special_gamefiles commands.h globals.h level.h obj testworld.lvl common.h inventory.c level_table puzzles.c util.c
Here's a short description of each file, with the ones you'll need to edit in bold:
make
to build your project with the directives in Makefile. Make sure you take a look inside, we've given you a few useful shortcuts. For instance, try make debug
.
room_array
, num_rooms
, and the_player
. Declarations are in globals.h
make playthegame
it uses the puzzles.o file contained in this directory instead of the one generated from the provided puzzles.c. Note you can only run make playthegame
on one of the Macs in the lab, since puzzles.o is platform-dependent!
This is a very large codebase for a 61 series project! Make sure you check out the Survival Tips section for ideas about how to make it more manageable.
Before you program and after you finish each part of the project, *make sure* to test out your implementation in cs61c-world.lvl. It's where you will begin your zany adventure, use your newly implemented skills and learn what you will need in the tough hours to come! (You can also use testworld.lvl to quickly test out some of the simpler implementation details, but its not very exciting).
You can only play the cs61c-world.lvl level on lab machines! As an unfortunate consequence of only distributing the puzzles.o object file (which is only compiled for the Mac platform), you can only play the main game world of cs61c-world.lvl on lab machines (or by ssh
ing into lab machines; a list of the lab machines can be found here). To play the game, use the make playthegame
command. You can always play any test levels or levels of your own creation as described in the section below.
Once you implement some of the features, you will be able to play the test world. An example transcript of interacting with the game is below:
cs61c-tc@akane ~ > make Stuff prints out cs61c-tc@akane ~ > ./game -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- Welcome to game, the CS 61C adventure extravaganza! -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- You wake up. you are in room #0 There is an exit to the east > go east you are in room #1 There are exits to the north and west > dawdle 15 You dawdle for 15 seconds... > ^D cs61c-tc@pulsar ~ >
You start a new game with any level by calling ./game your-level-filename your-name
after running make
. The testworld.lvl file is the default when the game is called with no arguments, which will also start a new game for you with the name Malloc. You can load a saved game by typeing ./game your-name
.
However, the real fun is in special_gamefiles/cs61c-world.lvl, which will run automatically if you run make playthegame
on a lab machine like akane.cs
. When you start the game, you should see the following lines:
cs61c-tb@akane ~ > make playthegame -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- Welcome to ./game, the CS 61C adventure extravaganza! -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- You wake up. You are in a cold, concrete-floored room. There are no windows and almost no light. The only thing you can make out is the faint outline of a door to the south. There is an exit to the south. >
Here is a list of commands in the game (some of them not implemented for you):
look
— Look around the current room.
attack monster_name
— Attacks the first monster with monster_name. If multiple monsters with the same name are present, successive calls to attack will target the same monster.
dawdle num_seconds
or wait num_seconds
— Wait the given number of seconds of the game time. If it is called with no argument, it will wait for 15 seconds.
quit
or exit
— Ends the game.
status
— Prints out the player's status, including hit points and experience.
help
— Prints out all currently available command names.
cast spell target
— Cast the spell at the given target.
interact
— Interact with a room's puzzles or people.
go direction
— Move around the level. Valid directions are north
, south
, east
, west
, up
, and down
.
use item_name
— Use an item called item_name, assuming you have one.
drop [item_name]
— Drops items. If item_name is ommitted, will prompt the user if they want to drop each item.
To make sure that everything's ready to go, make sure you can compile and run the game. Do this by running make
from within the proj1
directory. The project will compile and run, and you can already play much of the game. Unfortunately, eventually you'll need to stop playing and get back to work; don't worry, the game will be saved when you exit. However, before you can resume your previous game you'll need to work on the project, since save file loading hasn't been implemented yet!
To load a game you've saved, you'll need to complete the function load()
, found in util.c. The function prototype from level.h is reproduced below:
void load(char *name);
The function should open the file named by name
, read in the level file you were playing in, use load_level()
to set up the rooms, read your hp and experience, load your items, and respawn monsters just where they were when you last quit. The function will allocate memory on the heap.
The load()
function itself is partially implemented in util.c. You will need to fill in the code to perform three tasks:
item_t
needs to be created for each item in this list and added to the players inventory.
A save file has the general format shown below, where lines starting with "***" are not literally in the file. An simple example can be found in Bing.save
.
LEVEL_FILE_NAME PLAYER_EXPERIENCE PLAYER_HP PLAYER_LOCATION **items** apple sandwich *** MORE LINES HERE, UP TO MAX_INVENTORY_SIZE *** **monsters** goblin 0 2 donkey 3 10 *** MORE LINES HERE ***
A save file must contain the first four lines that give the level file, experience, hp, and location. The level file name can be used directly by load_level()
to initialize the rooms. Location will always be a valid room id.
Next, there is whitespace (at least two newlines, as in the above example), followed by a line containing "**items**". In the next block, we are given the name of each item in the player's inventory. You are guarenteed that each item is a valid item (it can be found in __items
, which is defined in the file item_table. You are also guaranteed that there are at most MAX_INVENTORY_SIZE
items listed in this block.
The item list block is followed by more whitespace (at least two newlines, as above), followed by a line containing "**monsters**". The final block describes all the monsters that need to be loaded. Each entry is of the form name room_id hp
. There is no real limit on the number of monsters allowed, other than the limit imposed by the size of memory (we won't give you a file with enough monsters to cause malloc to fail). The monster block will be terminated with an two newlines.
We will not test your solution on malformed save files.
player_t
, room_t
, item_t
, and monster_t
, located in game.h, level.h, inventory.h, and monster.h respectively.
skip_characters()
in util.c
member()
in util.c
load()
in util.c.
At this point, you should be able load a saved game and continue your adventure. Try it out!
Eventually, you will want to use some items, so you'll need to implement some more code...
A data structure inventory_t
has already been defined for you. It contains an array of item_t
pointers of size MAX_INVENTORY_SIZE
. An inventory_t
is a data structure that supports the functions find_item
, add_item
, delete_item
, and num_items
. find_item
returns the first item in an inventory with the given name. add_item
adds a given item to the inventory, assuming there is room in the inventory; otherwise, it does nothing. delete_item
deletes an item from the inventory, assuming it exists; otherwise, it also does nothing. num_items
returns the current number of items in the inventory.
But wait, there's more. In order to provide the best data-structure interface ever, you'll also implement an inventory_iterator_t
, which will provide a convenient interface for outside code to access the items in an inventory_t
.
Check out this snippet, which removes all the items from an inventory:
item_t *item; // create a new inventory_iterator_t inventory_iterator_t *iter = make_inventory_iterator(the_player.inventory); // iterate over every item in the inventory_t while((item = next_item(iter))) { remove_last_item(iter); } // and finally, call delete_inventory_iterator() to delete any space // that make_inventory_iterator may have allocated delete_inventory_iterator(iter);
You are not responsible for what happens if delete_inventory_iterator()
is never called by outside code. Also, it is assumed that outside code will not add items from an inventory_t
between calls to make_inventory_iterator()
and delete_inventory_iterator()
. In addition, outside code will only remove items from the inventory_t
using remove_last_item()
This part of the project gives you a lot of freedom in design: as long as you support the interface defined in inventory.h, you can use any data structure you want! Be sure to read and think a bit before jumping into the code. Drawing a pictures always helps!
inventory_iterator_t
thoroughly. The fact that you must support it will influence your choice of data structure to use for inventory_iterator_t
.
Unfortunately, even after all the work you did for part 1, you still can't use items - because monsters can't drop any items yet! In part 2, you will write code to randomly generate items whenever a monster dies. Provided code will use the spawn_items()
function you will fill in to prompt the player for which items they want to pick up.
The possible items that can drop are defined in the file item_table. Each item_t
has a name and a function pointer which gets called whenever a player uses an item. These should be copied over from the data structure __items
defined in item_table into any new items that are spawned. Every time spawn_items is called, each item is spawned with probability 1/rarity (which means more than one item can be spawned). Any items that are spawned should be returned as a group inside an inventory_t
.
item_t
and item_entry
data structures in inventory.h and inventory.c
inventory_t
data structure in inventory.h
rand()
will return random numbers for you - for a more complete description of this function, this is a pretty good start.
Before you head on to the next implementation section, try playing the game! You should be able to do quite a bit in the game world before you need magic.
You'll come to a point where you need at least one magic spell, so this last implementation section is all about getting the cast
command to work to imbue the world with magic.
To get casting to work, you need to implement the get_spell()
function in game.c. After a player issues the cast
command, get_spell()
checks the level_table[]
for a spell entry whose name matches the provided string. For example, after receiving the in-game command "cast fireball" we want to check for a spell called "fireball" and, if we find it, return its corresponding function pointer. The level_table[]
is included via a macro in game.c, which essentially copies-and-pastes the exact text found in the level_table file. We keep that section in a separate file so that we can easily swap it out for another level table when testing your code.
You also need to implement the get_spell_level()
function in game.c, which takes the name of a spell being cast and returns the level required to use that spell according to the level table. Each element ("row") in the level table corresponds to a player level: the player's level is one more than the element's index, so the first element (at index 0) corresponds to player level 1, the second (at index 1) to player level 2, etc. If you don't understand the distinction between levels and experience, post to the newsgroup!
Finally, you must implement the cast()
function in commands.c. You will need to understand function pointer syntax, since you need to call the function returned by get_spell()
. Also, notice that the fireball()
spell we have implemented expects arguments like argc = 2
, argv = {"fireball", "goblin"}
. In other words, it doesn't expect to see "cast"
as the first entry in its argv
argument, so you shouldn't just pass the exact same argc
and argv
that cast()
is called with. Be sure to have checks in case the cast
command is issued with an unexpected number of arguments. In the case that cast is passed an incorrect number of arguments, it should just return 0.
level_table[]
and level_entry
in game.c
fireball()
in game.c
damage()
in game.c
Getting lost in a large codebase is easy, but there are a few things you can do about it.
grep
is a great tool for finding functions, types, and variables. Don't know where the_player
is declared? Just run grep the_player *.h from within the project directory. Nobody is blessed with the natural ability to navigate large projects, and it's one of the most useful skills you can learn.
Make it a habit to read the code which surrounds and makes use of your own. You can learn a lot from context, like how your function will be called, when it will be called, and what it's expected to do. Code reading is an oft overlooked skill, but it can save you a whole mess of time and effort.
Experiment! Don't be afraid to write some C to verify your model of how things work. Any time you're not sure about something, think of a way to verify it from within a little test program. Writing, compiling, and fixing something is the best way to learn. After all, the compiler is the final authority.
Also, if you like playing the game but are annoyed by the lack of a "save your game" command, OR if you want to try debugging some specific things in-game, add debug commands. When we were making the game world, we added commands to jump to any room index, add experience to the player (to level up), and to unlock any doors. Be sure you do not include these extra commands in your submission, since they will confuse the autograder!
Make your own tests! Don't just rely on our simple test files (like level files); make your own!
Version control is a wonderful tool for simplifying development. You can use it to track changes to your files, revert to previous reversions, and figure out exactly where you introduced a bug. For those of you who already know svn, or want to take the time to learn, here's a whirlwind tutorial on making your own repository on the inst machines:
First create a new repository named proj1_repository:
cs61c-tb@akane [~] svnadmin create proj1_repository
Get the full path of your homedir, and check out a working copy of proj1_repository. Make sure file: is followed by three forward slashes. Two because it's a URL, and one for the root directory:
cs61c-tb@akane [~] pwd (YOUR HOME DIR) cs61c-tb@akane [~] svn co file://(YOUR HOME DIR)/proj1_repository proj1_wc Checked out revision 0.
Copy all the framework files into your working copy, add them via svn, and commit them to the repository:
cs61c-tb@akane [~] cd proj1_wc cs61c-tb@akane [~/proj1_wc] cp -r ~cs61c/proj/01/* . cs61c-tb@akane [~/proj1_wc] svn add * A Makefile A commands.c <snip> A util.c A util.h cs61c-tb@akane [~/proj1_wc] svn commit -m "Initial check-in of proj1 framework" Adding Makefile Adding commands.cAdding util.c Adding util.h Transmitting file data ................... Committed revision 1.
Once you've got the repo working, you'll need to know some svn commands to interact with it. Here are some important ones: , , , and commit.
It might seem silly to keep a repository just for you, but once you experience the wonders of version control you'll never go back. It will improve your work flow, prevent file deletion disasters, and help you track your changes. It will also make experimentation and exploration easier, since you'll always be able to roll back your changes.
All your code should be contained in the provided files, and make
should build your program without any warnings.
When you're ready to submit, cd into your project directory, make clean
, and then submit proj1
.
Wow, you rock. You built a whole world from raw text files, populated it with dire monsters, and gave our protagonist some spells to fight them. You may have even plumbed the depths of cs61c-world.lvl, and discovered the dark secrets lurking within.
You might think that the fun is over—but the adventure doesn't have to end here!!! Proj1's unofficial extra for experts is to implement something cool. Seriously, anything. A new spell, a new game feature, or even your own world. Although you can't share code, feel free to post new puzzle.o/puzzle.h and level files to the newsgroup. Go nuts and have fun with it; game programming can be a lot of fun!
PS As much as we are dying to see all of your awesome new stuff, please keep the extra for experts separate from your to-be-graded submission. This project is going to be hard enough to auto-grade as it is!