University of California at Berkeley
College of Engineering
Department of Electrical Engineering and Computer Science

CS 61C, Summer 2008

Project 1 — World of 61C AdventureCraftHack

Extended to Sunday, July 13th @ 11:59pm

Due Friday, July 11th @ 11:59pm

Last Updated 7/10 @ 1:15pm

TA: Richard Guo

Original authors (i.e. these guys are awesome): Matt Johnson and Casey Rodarmor

Yet Another Note: For anyone who copied over files before 1:15 pm on 7/10, please replace your special_gamefiles/puzzles.o with the one located at
Also, copy over 2 new files, patch.c and patch.h, from ~cs61c/proj/01/patch.c and ~cs61c/proj/01/patch.h
playthegame is broken at the moment. We are working on a fix as fast as possible.

Another Note: For anyone who copied over files before 8:15 pm on 7/5, please replace your puzzles.c with the one located at ~cs61c/proj/01/puzzles.c

IMPORTANT NOTE: Some of the project files were updated on Saturday, 2008.07.05. If you copied the project files on or before that day, you must update your project files. However, none of them were files that you need to edit, and they will not affect any of the code you have written. To update your files, run the commands listed below from your project directory:

rm puzzles.o cs61c-world.lvl main.c Makefile
cp -r ~cs61c/proj/01/special_gamefiles ~cs61c/proj/01/main.c \
	~cs61c/proj/01/Makefile ~cs61c/proj/01/puzzles.c .

Note: There was a typo in the original monsters.h file comments, in which it asked you to implement some functions in commands.c instead of the correct location of monsters.c. The files are updated in ~cs61c/proj/01 now.

Project Overview
Getting Started
    File Summary
    Playing the Game
    Part 0: Let there be rooms!
        Level File Format
    Part 1: Get go()-ing!
    Part 2: Monster Mash_t
    Part 3: Magic
Survival Tips
    Mini SVN Tutorial
Extra for Experts

Project Overview

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 brand new 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, and levels. 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.

Getting Started

The first step is to copy the game framework files into your home directory:

cs61c-tm@quasar ~ > cp -r ~cs61c/proj/01/ ~/proj1
cs61c-tm@quasar ~ > cd proj1
cs61c-tm@quasar ~/proj1 > ls
Makefile            globals.c           monsters.c          special_gamefiles/
commands.c          globals.h           monsters.h          testworld.lvl
commands.h          level.c             obj/                util.c
common.h            level.h             puzzles.c           util.h
game.c              level_table         puzzles.h
game.h              main.c              spec/

File Summary

Here's a short description of each file, with the ones you'll need to edit in bold:

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 nova.cs platform), you can only play the main game world of cs61c-world.lvl on lab machines (or by sshing into lab machines). To play the game, use the gmake playthegame command. You can always play any test levels or levels of your own creation as described in the section below.

Playing the Game

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@pulsar ~ > gmake
	Stuff prints out
    cs61c-tc@pulsar ~ > ./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 can run the game with any level by calling ./game your-level-filename after running gmake. The testworld.lvl file is the default when the game is called with no arguments.

However, the real fun is in special_gamefiles/cs61c-world.lvl, which will run automatically if you run gmake playthegame on a lab machine like nova.cs. When you start the game, you should see the following lines:

	cs61c-tc@pulsar ~ > gmake 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):


To make sure that everything's ready to go, make sure you can compile and run the game. Do this by running gmake from within the proj1 directory. The project will compile and run, but will immediately crash. This is by design; it's trying to load the level file, but load_level() isn't doing a whole lot of anything at the moment. Let's fix that.

Part 0: Let there be rooms!

To initialize the game world from disk, you need to complete load_level(), found in level.c. The function prototype from level.h is reproduced below:

room_t *load_level(char *filename);

The function should open the file named by filename, set up room_array[] (a global array of room_t structs), and return a pointer to the starting room. The function will allocate memory on the heap, but because that memory will exist for the duration of the program, there is no need to free any of the memory allocated in load_level().

The load_level() function itself is partially implemented in level.c. You will need to fill in the code to perform three tasks:

Level File Format

A level file has the general format shown below, where lines starting with "***" are not literally in the file. An simple example can be found in the testworld.lvl file.


0 This is a room description, printed whenever a player enters the room.
1 This is another description.
2 And another!

can-go north 0 1       *** AN UNLOCKED EXIT ***
cant-go west 1 2       *** A LOCKED EXIT ***

A level file must start with an integer number that indicates the total number of rooms.

Next, there is whitespace (at least two newlines, as in the above example). In the next block each room is described: at the start of each line is a room number, then there is non-newline whitespace, and then the room description string. The description string is ended by a newline. The room description block will always have one line per room, and the room index at the start of each line is guaranteed to be valid (within bounds) but it may be multiple digits. Don't assume that the room numbers in the description block are in order, and don't assume that there is a description for each room (some numbers may be missing).

The room description block is followed by more whitespace (at least two newlines, as above). The final block is the connection block, which describes how rooms should be linked. Each line will start with either "can-go" or "cant-go," which declare an unlocked or locked exit between rooms (respectively). Locks are NOT bi-directional: a "cant-go north 0 1" line will set up a door that is locked from 0 going north, but it will also set up an exit in room 1 going south that is not locked.

After either "can-go" or "cant-go" there is non-newline whitespace, followed by a direction name, which is the direction of the exit from the FROM_ROOM to the TO_ROOM. The possible direction names can be found in direction_names[] in level.c. Finally, two numbers are given, separated by non-newline whitespace, which are the FROM_ROOM and TO_ROOM, in that order, which are the room indices to be connected. The indices will always be valid room indices. As an example, the line cant-go north 0 1 should connect the north exit of room 0 to room 1, the south exit of room 1 to room 0, and set the locked boolean of room 0's north exit to true. (while leaving the locked boolean of room 1's southern exit alone)

We will not test your solution on level files with repeated, poorly formed, or conflicting connecting statements, e.g. can-go north 0 1 followed by can-go north 0 2.

Data Structures to Understand:

Functions to Understand:

Lines of code to add: 25

Part 1: Get go()-ing!

Try loading the game and see what you can do! Not much, huh? Being able to move around is fun, so why don't you implement the go() function in commands.c. Go() moves the player from room to room by updating the current_room pointer in the_player, a global variable.

Since the go() command is issued in the game just like a command line in the shell, the arguments are passed exactly as in main(). For example, if you type the following when in game:

> go north

The arguments passed to go will be argc = 2, argv = {"go", "north"}.

Your go() function should check to see if the exit in the direction to move is locked, and only move the player if it is not. All commands return the amount of time that command took, and go() is no exception. It should return 0 if the player attempts to move in a direction where there is no exit, FAILED_MOVE_TIME if there is but it is locked, and MOVE_TIME if the move is successful.

Data Structures to Understand:

Functions to Understand:

Lines of code to add: 35

Part 2: Monster Mash_t

After implementing the go command, you should be able to explore quite a bit of the game world. Try it out! Remember, when you see a locked door, use the interact command to start solving the puzzle.

Eventually, you will come to an obstacle you cannot pass without being harder/better/faster/stronger. You must increase your level! But to do that, you're going to need to add some monsters to your world...

In order for every room in the game to hold a variable number of monsters, you will implement a variable length data structure called a mob_t. The code for this part is self contained within monsters.h and monsters.c. A mob_t is just a dynamic data structure of monster_ts. It supports a function called spawn_new_monster(mob_t **mob_handle) which adds a random monster_t to the mob_t, get the first monster of a particular type with find_monster(mob_t *mob, char *type). You'll also support
append_monster(mob_t **mob_handle, monster_t *monster) and
delete_monster(mob_t **mob_handle, monster_t *monster), which add and remove monsters from mobs.

Note that some functions take mob pointers (mob_t *) while others take
mob handles (mob_t **) as arguments. Make sure you understand the difference, and if you get stuck, draw a picture!

But wait, there's more. In order to provide the best data-structure interface ever, you'll also implement a mob_iterator_t, which will provide a convenient interface for outside code to access the monsters in a mob_t.

Check out this snippet, a loop which sets the hp of all monsters in a mob to 0:

    monster_t *monster;
    // create a new mob_iterator_t
    mob_iterator_t *iter = make_mob_iterator(&the_player.current_room->mob);
    // iterate over every monster in the mob_t
    while((monster = next_monster(iter))) {
        monster->hp = 0;
    // and finally, call delete_mob_iterator() to delete any space
    // that make_mob_iterator may have allocated

You are not responsible for what happens if delete_mob_iterator() is never called by outside code. Also, it is assumed that outside code will not add or remove monsters from a mob_t between calls to make_mob_iterator() and delete_mob_iterator().

This part of the project gives you a lot of freedom in design: as long as you support the interface defined in monsters.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!

Once you're done with with monsters, make sure you update the room initialization code you wrote in level.c. You'll have to do whatever initialization is needed to make sure that the mob_t* in each room starts off in an acceptable state.

Data Structures to Understand:

Functions to Understand:

Lines of Code: 125

Part 3: Magic

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 paste 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.

Data Structures to Understand:

Functions to Understand:

Lines of code to add: 20

Survival tips

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!

Mini SVN Tutorial

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-tc@nova [~] 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-tc@nova [~] pwd
    cs61c-tc@nova [~] 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-tc@nova [~] cd proj1_wc
    cs61c-tc@nova [~/proj1_wc] cp -r ~cs61c/proj/01/* .
    cs61c-tc@nova [~/proj1_wc] svn add *
    A         Makefile
    A         commands.c
    A         util.c
    A         util.h
    cs61c-tc@nova [~/proj1_wc] svn commit -m "Initial check-in of proj1 framework"
    Adding         Makefile
    Adding         commands.c
    Adding         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.

Common Pitfalls (a checklist)

Below is a list of common mistakes from past student solutions to this project. It would be a good idea to go down the list and double-check you don't have the same mistakes.


All your code should be contained in the provided files, and gmake should build your program without any warnings.

When you're ready to submit, cd into your project directory, gmake clean, and then submit proj1.

Extra for experts

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!