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

CS 61C, Summer 2010

Project 1 — World of 61C AdventureCraftHack

Due Saturday, July 3rd @ 11:59pm

Modified (in red) July 2nd @ 10:30pm

TA: Eric Chang, Alex Shyr

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

Project Overview
Getting Started
    File Summary
    Playing the Game
Assignment
    Part 1: Aliasing!
        Subpart 1: Modifying Aliases
        Subpart 2: Executing Aliases
        Subpart 3: Saving Aliases
    Part 2: Magic
Survival Tips
    Mini SVN Tutorial
    Pitfalls
Submission
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 your gaming experience to be smoother and more exciting. 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                game.h                  monsters.c              special_gamefiles/
alias.c                 globals.c               monsters.h              testworld.lvl
alias.h                 globals.h               obj/                    util.c
commands.c              level.c                 patch.c                 util.h
commands.h              level.h                 patch.h
common.h                level_table             puzzles.c
game.c                  main.c                  puzzles.h

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

To play the game, use the gnumake playthegame command on the Macs. You can always play any test levels or levels of your own creation as described in the section below.

Playing the Game

You can play the test world and try things out. An example transcript of interacting with the game is below:

~/adventure/trunk $ gnumake
[Stuffs prints out]
~/adventure/trunk $ ./game
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
Welcome to ./game, the CS 61C adventure extravaganza!
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
Could not open alias.save, starting with no aliases.
You wake up.
you are in room #0
There is an exit to the east.
> go east
you are in room #1
The door to the stairs is shut tight. You see a keypad with something scrawled to the side.
There are exits north and west.
> dawdle 15
You dawdle for 15 seconds...
> exit
goodbye!

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 gnumake playthegame on a Mac machine like bonza.cs. When you start the game, you should see the following lines:

~/adventure/trunk $ gnumake playthegame
[some commands running]
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
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):

Assignment

To make sure that everything's ready to go, make sure you can compile and run the game. Do this by running gnumake from within the proj1 directory. The project will compile and run, and you should be able to walk around and explore the world.

The autograder requires the function prototypes to stay the same; therefore, DO NOT change the function interfaces that we have predefined for you.

Part 1: Aliasing!

After you try out some game, you might get quickly annoyed at the fact that the commands are so long... I mean, who wants to type "attack goblin" like 10 times in a row?? And later when you implement magic, the mere idea of typing "cast fireball goblin" just gives me the creeps... You will add in the alias functionality so we can type less and enjoy the game more.

Subpart 1: Modifying aliases

First we would want to manage a list of aliases that you have. You will do so by defining the alias_t data type in alias.h and implementing functions in alias.c. Looking at alias.c, note that it has a global variable aliases that will be a variable-length data structure containing all the alias you defined. You will supply the appropriate struct definition for alias_t in alias.h, and then implement the functions in alias.c that manipulates this data structure.

When you finish this part, you should be able to use the alias and unalias commands inside the game and see the following:

> alias
There are 0 alias:
> alias a attack
> alias
There are 1 alias:
a = 'attack'
> alias l look
> alias
There are 2 alias:
a = 'attack'
l = 'look'
> alias c 'cast fireball goblin'
> alias
There are 3 alias:
a = 'attack'
c = 'cast fireball goblin'
l = 'look'
> unalias c
> alias
There are 2 alias:
a = 'attack'
l = 'look'
> unalias l
> alias
There are 1 alias:
a = 'attack'
> alias a 'attack goblin'
> alias
There are 1 alias:
a = 'attack goblin'
> unalias t
> alias
There are 1 alias:
a = 'attack goblin'
> exit
goodbye!

As can be seen from above, when alias is used with no arguments, it prints out a list of all the current aliases in alphabetical order. Otherwise, if it is called with two arguments (the first argument will be a single word if the second argument contains more than one word, it must be surrounded by single quotes), it adds an alias that sets the first argument equal to the second argument (replacing an old one if necessary). And unalias takes a single argument and removes it from the list of aliases if found, otherwise it does nothing.

The code for alias and unalias should be already supplied to you in commands.c (so you don't need to worry about the argument parsing), although it is helpful for you to take a look at them to get a feeling of how to implement functions in alias.c. You have a lot of freedom in this part; as long as you get the above output and all redundant memory are freed, you can implement the alias_t data type and the functions in any way you want. Feel free to define additional data structures and helper functions in alias.h and alias.c (but make sure gnumake still works).

Data Structures to Understand:

Functions to Understand:

Lines of code to add: about 70

Subpart 2: Executing Aliases

Once you get subpart 1 going you might be tempted to try and see if your aliases work, however, it probably doesn't work; because you haven't implemented the code that checks to see if you're typing an aliased command yet! Now you'll do so by modifying lookup_command in commands.c

First of all, if lookup_command works correctly you should be able to see the following output in your game:

There is 2 monster here:
        a mean spirited goblin
        a mean spirited goblin
> alias a 'attack goblin'
> alias
There are 1 alias:
a = 'attack goblin'
> a
You attack the goblin for 1 damage...
The goblin bites you for 1 points of damage
The goblin bites you for 1 points of damage
> alias a attack
> alias
There are 1 alias:
a = 'attack'
> a goblin
You attack the goblin for 1 damage...
you killed the goblin and gained 4 experience!
The goblin bites you for 1 points of damage
> alias c 'cast fireball'
> alias
There are 2 alias:
a = 'attack'
c = 'cast fireball'
> c goblin
A fireball forms at your fingertips. You hurl it across room at the goblin, doing 6 damage.
you killed the goblin and gained 4 experience!

Although you probably still can't cast fireballs at this point, notice that command arguments of the alias is appended at the end of the string that the alias stands for. For example, when c stands for 'cast fireball', typing c goblin is equivalent to calling the commands cast with the two arguments fireball and goblin. You will have to make sure that lookup_command implements this feature.

Here is a description of lookup_command:

lookup_command

Looks up the given command name in the available commands and returns a function pointer to be executed by the main program.

Parameters:

Return Value:

A function pointer that can be used to execute the given command, or NULL if no matching function is found.

Again, make sure you don't leave any unfreed memory with your implementation. Studying how the read-eval-print loop in main.c calls lookup_command might help. Also, the function tokenizer in util.c can be useful and save you a lot of time. If you really couldn't get the command line arguments working the correctly, at least make sure your lookup_command works with alias with no arguments so you can still get partial credit.

Data Structures to Understand:

Functions to Understand:

Lines of code to add: 15

Subpart 3 : Saving Aliases

Now your aliases should be up and running! But isn't it sad that everytime you restart the game you have to type them in all over again? If only we can save those aliases...

You'll implement this feature by filling in the save_alias() and load_alias() in level.c. With a correct implementation you should have the following output:

> alias a attack
> alias c 'cast fireball'
> alias l look
> alias
There are 3 alias:
a = 'attack'
c = 'cast fireball'
l = 'look'
> exit
goodbye!

~/adventure/trunk $ cat alias.save
a = 'attack'
c = 'cast fireball'
l = 'look'

~/adventure/trunk $ ./game
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
Welcome to ./game, the CS 61C adventure extravaganza!
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
You wake up.
you are in room #0
There is an exit to the east.
> alias
There are 3 alias:
a = 'attack'
c = 'cast fireball'
l = 'look'
>

Note that your aliases is saved to the file alias.save with the same format as the output of your print_alias() (I wonder why print_alias() takes a FILE pointer...). With this feature done, now you can kill all those monsters at BLAZING SPEED!!! Wahaha...

Data Structures to Understand:

Functions to Understand:

Lines of Code: 15

Part 2: 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 past 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. The cast() function should return 0 whenever there is an error (ie., when there is an incorrect number of arguments, when the spell does not exist, when the player's level is not high enough for the specified spell, and when the spell cast has no target).

We will add two spells to our player's arsenal: fireball and heal. The fireball spell should have a target (e.g., "cast fireball goblin"), inflicting upon the target in the room a damage that is twice the player's current level. The heal spell should be directed at the player himself ("cast heal self", "cast heal me", or simply, "cast heal"), giving the player a number of HP equivalent to the player's current level. Both spell functions fireball() and heal() should return 0 whenever there is an error.

Data Structures to Understand:

  • level_table[] and level_entry in game.c
  • Check out the function pointer typedef for spells in game.h

Functions to Understand:

  • get_spell_level() in game.c
  • get_spell() in game.c
  • cast() in commnd.c
  • fireball() in game.c
    • Usage: "cast fireball monster_name" or "cast fireball at monster_name"
    • Result: inflict damage of 2 times player's level on monster
  • heal() in game.c
    • Usage: "cast heal" or "cast heal me" or "cast heal self"
    • Result: gives the player a number of HP equivalent to the player's level, not exceeding the player's maximal HP
  • (optional) damage() in game.c

Lines of code to add: 70

 Here are two sample scenarios, showing the various inputs and corresponding output messages :			
 > status
 level: 2
 exp:   20
 hp:    20/20
 > look
 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.
 There are 2 monsters here:
        a mean spirited goblin
        a small brown donkey
 > cast
 Which spell would you like to cast?
 > cast iceball
 I don't think that iceball is a spell...
 > cast heal
 You must be at least level 4 to cast heal.
 > cast fireball
 Please specify a monster to torch.
 > cast fireball monster
 There's no monster here...
 > cast fireball donkey
 A fireball forms at your fingertips. You hurl it across room at the donkey, doing 4 damage.
 The donkey kicks you for 3 points of damage
 The goblin tries to bite you, but misses
 The donkey tries to kick you, but misses
 The goblin tries to bite you, but misses
 The goblin bites you for 1 points of damage
 The donkey kicks you for 3 points of damage
 
 > status
 level: 5
 exp:   70
 hp:    50/50
 > look
 A dim hallway stretches out before you. You see a doorway leading to stairs. 
 The door to the stairs is shut tight. You see a keypad with something scrawled to the side.
 There are exits north and up (locked).
 There are 2 monsters here:
        a small brown donkey
        a small brown donkey
 > cast heal donkey
 You want to heal anyone but yourself??
 > cast heal
 You are completely healthy! No need for healing.
 The donkey kicks you for 3 points of damage
 The donkey kicks you for 3 points of damage
 > cast heal me
 You suddenly feel refreshed, as your HP went up 5 points.
 The donkey kicks you for 3 points of damage
 The donkey tries to kick you, but misses
 > cast heal self
 You suddenly feel refreshed, as your HP went up 4 points.
 The donkey kicks you for 3 points of damage
 The donkey kicks you for 3 points of damage
 

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!

gdb (or your favorite debugging program) is your friend; tracking how variables change over time is especially useful since it can make you catch errors like using memory on a stack. When using gdb, remember to pass the game file special_gamefiles/cs61c-world.lvl as the argument after you start debugging.

Use comments!! Not only will it help you understand what you're doing the next day, but your friend/TA can understand your code and help you quicker. Also as a tip: NEVER delete your debugging code, just comment them out, so if the code breaks again you don't have to type all those printfs!! (although it'll be nice to delete them for your final submission

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
    (YOUR HOME DIR)
    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
    <snip>
    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.

Submission

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

When you're ready to submit, cd into your project directory, gnumake 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!