CSC230 Homework 5
This homework is to be done individually.
You may use any functions in the standard library on this assignment.
- Writeup change:
- Added missing description of the prompt, and how inputs are made lower case before processing.
Note that tests failing due to case change for the "
CRAZY GARBAGE
" tests will not count against you, but we'd prefer if you match our lower-casing habit to make grading easier. - This and any furter additions will be noted with a dashed grey rectangle and a timestamp.
- Added missing description of the prompt, and how inputs are made lower case before processing.
Note that tests failing due to case change for the "
- Writeup change:
- Extra credit added. Write-up is now fully complete.
- Jenkins is now LIVE!
- Writeup change:
- Fixed small mistake on the
action_get_weapon
flowchart -- not-found message changed to "There's nothing like that here!"
- Fixed small mistake on the
- Starter kit changes:
VERSION
timestamp: Tue Jul 7 22:40:13 EDT 2015test.sh
: the script was not configured to userelaxed_diff
. Also, added a timeout of three seconds to each test case in case of infinite loop (this is mainly to help Jenkins).quester-reference
: Fixed a bug in the reference implementation's handling of invalid files.- No other files have been modified in the kit since the 2:04pm update
- Writeup status:
- Added the testing section and the grading rubric.
- Minor corrections and clarifications.
- Starter kit changes:
VERSION
timestamp: Tue Jul 7 16:36:02 EDT 2015- The starter kit now includes testing materials: a
test.sh
script, inputs, and expected outputs. - Added more test worlds.
- Modified the package/Makefile so that test worlds are included in the package rather than generated by an included program.
- You may consider the starter kit for this assignment 'live'; no further changes will be made, except to correct any mistakes that are found.
- Writeup changes:
- None.
Note: this is the first semester this particular assignment is being used. Please report any unclear explanations, mistakes, or issues to the instructors.
Learning Outcomes
- Write small to medium C programs having several separately-compiled modules.
- Explain what happens to a program during preprocessing, lexical analysis, parsing, code generation, code optimization, linking, and execution, and identify errors that occur during each phase. In particular, they will be able to describe the differences in this process between C and Java.
- Correctly identify error messages and warnings from the preprocessor, compiler, and linker, and avoid them.
- Find and eliminate runtime errors using a combination of logic, language understanding, trace printout, and gdb or a similar command-line debugger.
- Interpret and explain data types, conversions between data types, and the possibility of overflow and underflow.
- Explain, inspect, and implement programs using structures such as enumerated types, unions, and constants and arithmetic, logical, relational, assignment, and bitwise operators.
- Trace and reason about variables and their scope in a single function, across multiple functions, and across multiple modules.
- Allocate and deallocate memory in C programs while avoiding memory leaks and dangling pointers. In particular, they will be able to implement dynamic arrays and singly-linked lists using allocated memory.
- Use the C preprocessor to control tracing of programs, compilation for different systems, and write simple macros.
- Write, debug, and modify programs using library utilities, including, but not limited to assert, the math library, the string library, random number generation, variable number of parameters, standard I/O, and file I/O.
- Use simple command-line tools to design, document, debug, and maintain their programs.
- Use an automatic packaging tool, such as make or ant, to distribute and maintain software that has multiple compilation units.
- Use a version control tools, such as subversion (svn) or git, to track changes and do parallel development of software.
- Distinguish key elements of the syntax (what’s legal), semantics (what does it do), and pragmatics (how is it used) of a programming language.
Quester: A Text Adventure in C
Computers (and before that, vaguely computer-like electronic things) have invariably been put to the task of gaming, no matter their limitations. When a computer interface was nothing more than a grid of a few lightbulbs, it was made to play Nim. When a computer interface was nothing more than an oscilloscope, it was made to play tennis (see right). And when a computer interface was just a text terminal like the kind we're emulating to this day, the first text adventures were born. The very first was Adventure, and probably the most well known was Zork.
In addition to being an important historical landmark on the way to today's pinnacle of gaming, text adventures can also feature a ton of fairly rigorous programming concepts, including dynamic data structures, packed binary file storage, length-prefixed strings and structures, and function pointer lookup tables.
In this assignment, you're going to write a simple text adventure engine capable of loading an adventure world from file and interactively allowing the player to explore. The game is called Quester, and the object is to find the key and unlock the chest full of treasure without being killed by the hideous Javgoblin.
Program Requirements
The game will accept a world file as its only argument; if run without argument, it displays the following usage message:
"\n"
"Syntax:\n"
" quester <world-file>\n"
"\n"
In each room, there can be any combination of the following four "objects":
- A key (used to open the chest)
- A chest (unlocking this with the key is how you win)
- A weapon (you need this if you want to defeat an enemy)
- An enemy (will kill you after your next command unless you kill it first, and you need to weapon to do that)
Upon starting, the game prints a description of the starting room, and then a prompt. The user can input one of a limited number of commands, such as move in a direction ("east", "north", etc.), pick up an item ("get key", "get weapon"), attack an enemy ("attack"), or open the chest ("open chest"). There are a few meta-commands as well, such as "inventory" and "quit".
"> "
.
(Note: this means that automated test output will appear to have a prompt followed immediately by a command's response (e.g. "> You travel east."
), since inputs are not echoed to the log during automated testing.)
In order to make the interface case insensitive, commands shall be made lower case before being interpreted.
The player must locate the key, then the chest, and open the chest to obtain the treasure. However, the world may include one or more enemies; to dispatch these enemies, the player will need to find a weapon. It may not be clear where the enemy is, as all the player can tell is if an enemy is in a given direction, not how far.
Play continues until (a) the user unlocks the chest with the key and wins, (b) the user is killed by an enemy, or (c) the user quits with the "quit" command. After any of these cases, the program exits, and the exit status code indicates how the game ended.
Below is an example playthrough using the minimal testing world file test_world_1.dat
in which the player wins:
For full marks to be awarded, your implementation must meet the following requirements:
- Faithful, stable implementation of the game
- No alteration/deletion of anything in the provided header (.h) files; modest additions may be made if needed
- All structs must be dynamically allocated.
- All structs must be freed when no longer needed (no memory leaks)
- No buffer overflows OR security vulnerabilities
Design
The world
The design of the program shall make heavy use of structures, pointers, and dynamic memory allocation. Each room will be stored in memory as a heap-allocated structure, with pointer links between rooms representing exits. A master table of rooms will be kept in a heap-allocated array called the world.The world will be read from a binary file. The detailed structure is given later in this document, but at a high level, the file will make use of length-prefixed records. Typically, we've been using delimited records, meaning that a record continues until a special character or string is encountered. For example, our strings tend to be null-terminated, and data we've read from the console or ASCII files has been delimited by spaces or newlines. Delimited records like this have an advantage in that they can be of any size with very little overhead; the downside is that mistakes involving the delimeter can lead to malfunctions or security vulnerabilities.
Contrast this with length-prefixed records, in which the first few bytes stored are the length of the structure that follows.
This means that no terminator or delimeter is needed, so there's nothing at the end to overwrite and cause problems.
Length-prefixed records are often easier to read in binary files, because after you get the length, you know exactly how much further to read, and often can even do it with a single fread
call.
On the downside, you have to agree ahead of time what the maximum length of your record is going to be, and make your length prefix big enough to store that number.
This can cause growing pains when software ends up needing even longer records down the road.
(For a detailed treatment of the topic, see this excellent article by Joel Spolsky.)
The detailed design of the world file is given in the Implementation section of this document.
The interface
Commands will be read from standard input, and will be delimited by newlines. Unlike most text adventures, we will not be implementing a parser (i.e. our program will not understand "get" as a distinct verb; instead, it will just know "get key" as a single discrete command). In order to make our interface easily extensible, we will create a command table which maps commands to functions reference via function pointers. This use of a function lookup table is very common and very powerful, allowing commands to be mapped to a wide variety of operations without the need for a single giant sequential if statement.You will be given freedom in your design to add additional commands (though these will not be tested). Also, we will relax the diff check when it comes to spacing and optional text colorization.
While navigating, the interface will print a description of the room, which will include its name, description, and objects that may be present (including enemies), and a list of exits from the room (north, east, south, or west).
Further, the interface will warn the player if an enemy is present in any of the available directions, but this information will not indicate how far the enemy may be. For example, consider the following map:
[here]
, then an enemy will be reported east, south, and west, since there's an enemy directly in "line of sight" in these directions. However, no enemy will be reported in the north. To discover that enemy, the player will have to go north twice, then the enemy will be reported to the east.
In other words, enemies are never reported "around corners".
NOTE: There is a potential issue with this design. It is possible for rooms in a given direction to form a loop, making the traversal for the enemy check into an infinite loop. The proper solution would be some form of loop detection, usually by marking the rooms traversed in some way. That is NOT a requirement of this assignment, and worlds with loops in a single direction will not be tested.
The specifics of the interface will be discussed in the Implementation section of this document.
Code structure
Because of the size of the assignment, the program will be broken down into separate modules: player.[ch]
1, world.[ch]
1, and quester.c
.
Further, a skeleton of the assignment is provided in the starter kit.
The main
function is to be contained in quester.c
, which should be fairly lightweight, likely just handling command line argument parsing and the game logic loop.
Everything having to do with loading the world from file and navigating it is handled by the world.[ch]
module.
The player's location, inventory, and actions are handled in player.[ch]
.
1 The [ch]
is regular expression notation for a 'character class', meaning 'either c or h', and is often used by C programmers to refer to code and header files simultaneously.
Extra credit
This assignment includes several opportunities for extra credit. As these are not core requirements, less detail and direction are given, and students are invited to research, explore, and experiment toward a solution.Do not let pursuit of extra credit endanger the core requirements! Extra credit will only be considered when the majority of core requirements have been met.
You may pursue ONE (1) extra credit opportunity at most. If you pursue more than one, indicate which you'd like graded in a readme.txt
file, otherwise we will grade a random one.
Cheating solver
Possible points: +15Develop an additional program called cheat_quester
which takes a world file as an input parameter and outputs on stdout a sequence of commands that will result in winning if winning is possible for a given world.
For worlds where winning is impossible (e.g. there's no chest anywhere, or the only key and weapon are behind an enemy you can't get around, etc.), simply emit the "quit" command.
In order words, develop a program that when run as follows will cause quester to exit with status 0 (win) in any possible case, and status 2 (quit) otherwise:
Note: Traversing a graph such as the world structure is usually covered in CSC316. You'll want to look into graph traversal algorithms. Be careful with worlds that have loops, though...
To apply for this extra credit, include a readme.txt
file that indicates that you've written chest_quester
.
Additionally, include any extra documentation that may be needed to test it, and note any known problems it may have.
Fair solver
Possible points: +30Develop an additional program called ai_quester
which runs quester
as a subprocess, redirecting its stdout and stdin to allow bi-directional programmatic control.
Without peeking at the world file, ai_quester
should use the output of quester
to play as best it can.
Our game provides incomplete information about the game world, so it will not be possible to win in all possible cases as with the cheating solver.
Full credit will only be given for a play algorithm that meaningfully uses quester
output to guide its choices.
The syntax for the program will be:
Note: Launching a sub-process and taking bi-directional control of its stdin and stdout is an advanced technique usually introduced in CSC246 or later, so you'll have to do some digging to learn more, and there are pitfalls to watch out for.
To apply for this extra credit, include a readme.txt
file that indicates that you've written ai_quester
.
Additionally, include any extra documentation that may be needed to test it, and note any known problems it may have.
World editor
Possible points: +15
Develop an additional program called world_maker
which, by some means of your own design, allows users to create world files.
The editor must be able to represent everything that can be in a world file (rooms, text fields, exits, and objects).
Solutions may implement some form of command line interface or take as input the product of existing software.
For credit to be awarded, the editor must automatically determine the number of rooms,
the length prefixes of room names and descriptions,
the indexes of the rooms in the exit linkages,
and how exits and object IDs correlate to numeric values.
In other words, the included make_test_world.c
program is woefully inadequate to be considered,
since it generates worlds solely from inline code (it doesn't take input) and it requires the user (who's actually the programmer) to determine things like exit indices by hand.
To apply for this extra credit, include a readme.txt
file that indicates that you've written world_maker
.
You must include complete documentation on how to use it -- documentation will be considered incomplete if we cannot learn how to make any possible world using your editor.
Additionally, include any extra information that may be needed to test it, and note any known problems it may have.
Additional commands and/or objects
Possible points: +10Without modifying the existing ones, add additional commands or objects to the game. Any changes you make must NOT affect the output of the test cases which use the standard worlds and commands.
Commands: These must be non-trivial -- adding a "wave
" command that just prints "You wave hello.\n"
will not suffice.
The determination of non-triviality will be at the discretion of the instructors.
Objects: These must affect gameplay in some way -- adding a cool hat that does nothing will not suffice.
Further, if you decide to add additional objects, you will have to create new world files to test these objects.
Lastly, you can only use the 4 unused bits of the objects
field, because you may NOT alter the world file format to allow for more.
If you are unsure if a change you'd like to make meets the requirements above, ask the instructor.
To apply for this extra credit, include a readme.txt
file that indicates that you've added additional functionality.
Describe what the functionality is, how to use it, how it affects gameplay, and how the changes are reflected in world files.
If the changes you make require differing world files to see, include at least two world files of your own creation for testing, and note these in the readme file.
Finally, include any extra documentation that may be needed to test it, and note any known problems it may have.
Something else?
If you have a different idea you'd like to propose, ask the instructor in person.
Implementation
The world representation: world.[ch]
The world
module (world.c
and world.h
) will be responsible for loading the world and managing the in-memory data structure that represents it.
This will make heavy use of structures, pointers, and dynamic memory allocation.
First, the world will be referenced via a struct world
accessed via struct world*
pointer.
The structure is defined as:
rooms
field shall be a dynamically allocated array of struct room*
pointers, each pointing to a separate dynamically allocated struct room
. See figure:

In turn, struct room
is defined as follows:
Here, name
is a character array inside the struct, whereas desc
is a pointer to a separate heap-allocated string of arbitrary size.
The objects
is a bit field indicating the presence of any of the game objects, and exits
is a set of 4 pointers for each of the cardinal directions -- these point to other rooms, or NULL
if there's no exit in that direction.
See figure:

As an example, consider the following potential world:

This world would be represented by the following set of structures:

Everything having to do with loading the world is to be implemented in world.[ch]
. The only "public" functions are those listed in world.h
:
void print_room(struct room* rm)
: Print the room in the following format:= <Room name> =
<Room description which may be multiple lines and
go on for quite some time.>
A bloodthirsty Javgoblin snarls at you, ready to strike!2
You spot a sharp, crescent-shaped blade resting at your feet.3
You spot a key glimmering on the ground.4
A large oak chest sits here.5
Exits:
north6 (but you see a shadow moving in the distance)7
east6 (but you see a shadow moving in the distance)7
south6 (but you see a shadow moving in the distance)7
west6 (but you see a shadow moving in the distance)7
2 This line only listed if an enemy is present.
3 This line only listed if there's a weapon here.
4 This line only listed if there's a key here.
5 This line only listed if there's a treasure chest here.
6 The direction is listed only if it is a valid exit from this room.
7 This parenthatical only shown if if an enemy lies in this direction; not just in the next room, but at any distance in this direction. See the discussion of enemy detection in section 3.2.
struct world* load_world(char* filename)
: Load the world from the given file and return it as a pointer to astruct world
. If an error occurs, the function returnsNULL
, and a globalchar*
namedload_world_error_msg
is set to a string describing the problem. See the following section for details on the world file format.
void destroy_world(struct world* world)
: Free all memory associated with the world, including all room description strings, room structures, and the world structure itself.
The world file format
The world file consists of:
N : The number of rooms as a 32-bit integer in little-endian order- A series of
N room records - A series of zero or more exit linkages
Each room record consists of:
- The room name as a length-prefixed string, with a maximum length of 63 bytes
- The room description as a length-prefixed string, with no maximum length
- The objects as a single byte; indicates which objects are present by means of a bit field:
OBJ_KEY (0x01)
: This bit is set if the room contains a key.OBJ_CHEST (0x02)
: This bit is set if the room contains a chest.OBJ_WEAPON (0x04)
: This bit is set if the room contains a weapon.OBJ_ENEMY (0x08)
: This bit is set if the room contains an enemy.
A length-prefixed string is defined as:
L : The following string's length as a 32-bit integer in little-endian order- A series of
L bytes representing the string. The string is NOT null-terminated!
Lastly, each exit linkage consists of:
- The origin room (where the exit is connected from), expressed as a 16-bit integer in little endian order. The number refers to the order in which it appears in the room records section of the file, so the first room is referenced as 0, the second as 1, etc.
- The destination room (where the exit is connecting to), expressed in the same 16-bit format.
- The direction, a single byte indicating which direction the exit is heading from the origin room (0=NORTH, 1=EAST, 2=SOUTH, 3=WEST).
See the following diagram (not to scale):

Note that file format means you cannot simply fread
whole rooms directly into a struct room
.
Rather, you'll have to read in each field separately, making necessary conversions as you go.
There are three key differences between the on-disk and in-memory representations of the world:
- The on-disk strings are length-prefixed and do not have null terminators, whereas the in-memory strings must have null terminators and not have a length prefix.
- The exits are stored in a separate region on-disk, whereas they're stored in the
struct room
in memory. - Exit linkages on-disk are stored as integer "room indexes", i.e. references to the order in which the rooms appear in the file. Exit linkages in-memory must be pointers directly to the destination room's
struct room
.
Also, note that exits are one-way by default. To create an exit that the user can traverse both directions requires two linkages running opposite directions. In other words, if you want a two-way exit between A and B, you need both A⇒B and A⇐B.
As an example, consider the following hex dump:
Deconstructing it:
- Offset 0x00: Number of rooms,
2
- Offset 0x04: Length of first room name,
8 - Offset 0x08: Name of first room, "
Room one " - Offset 0x10: Length of first room description,
24 - Offset 0x14: Description of first room, "
This is room number one. " - Offset 0x2c: Object flags of the room,
00000111 in binary (key+chest+weapon)
- Offset 0x2d: Length of the second room name,
8 - Offset 0x31: Name of the second room, "
Room two " - Offset 0x39: Length of the second room description,
27 - Offset 0x3d: Description of the second room, "
Welcome to the second room. " - Offset 0x58: Object flags of the room,
00001000 in binary (enemy)
- Offset 0x59: Origin point of the first exit, room id
0 (room one, the first room) - Offset 0x5b: Destination point of the first exit, room id
1 (room two, the second room) - Offset 0x5d: Direction of this exit,
1 (east)
- Offset 0x5e: Origin point of the second exit, room id
1 (room two, the second room) - Offset 0x60: Destination point of the second exit, room id
0 (room one, the first room) - Offset 0x62: Direction of this exit,
3 (west)
load_world()
, if an error occurs, the function returns NULL
,
and a global char*
named load_world_error_msg
is set to a string describing the problem.
Possible error messages include:
- A standard UNIX I/O message if opening the file fails, commonly given by
strerror(errno)
Note: you will NOT be usingperror()
from withinload_world()
, because that function shall not print anything. "Unable to read number of rooms"
"Unable to read room name length"
"Room name length exceeds maximum"
"Unable to read room name"
"Unable to read room description length"
"Unable to read room description"
"Unable to read object bitfield"
"Unable to read exit linkage"
(probably hit only if there's an incomplete linkage record at the end of the file)
Command parsing and actions: player.[ch]
The player
module (player.c
and player.h
) will keep track of the player's current location (a struct room*
pointer) and inventory and
handle execution of commands (though not the actual task of reading the command from the console).
This is achieved through three "public" functions:
void init_player(struct room* starting_room)
: set the player's location and clear their inventory.result_t do_command(char* cmd)
: attempt to execute the given command, returning one of the following values, defined in the enumresult_t
:OK
- command successful (or at least didn't kill them), keep playingWIN
- player wins! game over.LOSE
- player loses! game over.QUIT
- player quits! game over.INVALID
- no such command
void print_current_room()
: print the current room description (almost certainly using theworld
module'sprint_room()
function)
The implementation of do_command()
shall internally make use of a command lookup table, which correlates commands to a function pointer.
This table is defined as follows:
result_t action_whatever()
.
This means that do_command()
simply needs to compare the provided command to those in the table, and call the corresponding function when it finds a match (returning INVALID if no match could be found, i.e. if the user typed an invalid command).
Note that multiple commands can map to the same action; this allows you to specify synonyms for commands, such as both "n" and "north" meaning to go north.
Game logic
NOTE: To allow for easy copy/pasting, these flowcharts are also available as a PDF.
To allow you to get a feel for the behavior of the game, we've included a reference implementation; this is a compiled copy of the instructor solution. Note that this document remains the authoritative source of program requirements -- consider the reference implementation to be a secondary resource.
Below is a flowchart illustrating the behavior of the front-end code, quester.c
.
Note how it relies on the do_command function for the actual processing.

Below are flowcharts for the actual action functions in the player
module.






Magic Numbers
All "magic numbers" that you use, (for example the default values for the probabilities and exit codes), must be defined as constants. For example:Get your environment set up
Note: If you successfully cloned your GitHub repo for a previous homework, you can skip to the section "Get the starter files".
Make sure that you're developing in the 5_homework
directory!
Start by cloning the provided CSC230 GitHub repo in your local AFS (or development space) using the following commands:
$
This will create a directory with your repo's name.
If you cd
into the directory, you should see directories for each of the homeworks for the class.
You'll want to do all of your development in 5_homework
.
If you do NOT see the 5_homework
directory, enter the following command to make the directory:
Get the starter files
Next, you will need to copy the hw5_starter.tgz
from the course locker into your 5_homework
directory. Use the following command:
Untar using the command:
About the starter kit
The starter kit includes the following:Makefile
: As this is a fairly complex assignment with multiple modules, a Makefile has been provided for you. No changes are needed unless you're doing something fancy. Note that this Makefile compiles to intermediate object (.o) files, and will build your project in an efficient, work-conserving fashion.player.c
: Code for yourplayer
module goes here --note the TODO comments .player.h
: Header file for theplayer
module -- no alterations/deletions are permitted, but you may add modestly to this file if you wish.quester.c
: Front-end code -- note the TODO comments. Contains themain
for the project.quester-reference
: Due to the complexity of this new assignment, we're including a reference implementation; this is a compiled copy of the instructor solution. Note that this document remains the authoritative source of program requirements -- consider the reference implementation to be a secondary resource.VERSION
: This file simply contains a timestamp of when the starter kit was built in case we have to update it; no change needed.world.c
: Code for yourworld
module goes here --note the TODO comments .world.h
: Header file for theworld
module -- no alterations/deletions are permitted, but you may add modestly to this file if you wish.- Other files: Test materials and utilities, see the "Testing materials provided" section below.
You must use the appropriate file names to allow for automated grading.
Reminder: A header file is a place to store information that is shared between different C modules. In this case, this includes function prototypes, certain type definitions, and any cross-module global variables. Do not modify the provided functions and struct names in the header file! You may add members to existing structs, as long as (a) you don't break compatibility with the posted world file format and (b) you keep the same pointer-based in-memory layout.
NOTE: The provided files are NOT fully commented and may not conform to all style rules for the course.
You must update the files to meet the style guidelines!
(Exception: you can ignore make_test_world.c
, which is provided for your convenience.)
You MAY use magic numbers in your test program.
Implementation Success Strategies
The teaching staff have the following recommendations for this assignment:
- Work on development of the program in small chunks and commit a working chunk to the GitHub repository as you finish it.
- The recommended order for development is
world
, thenplayer
, thenquester.c
. Note that you can include a testmain
in theworld
orplayer
modules to facilitate testing; just comment it out before you move onto the next module. - Test each function as you work on it. Even better, do test driven development. Watch this video for an example of test driven development in Java. The ideas are similar to what you would do in C.
- You should be checking for memory leaks and buffer overflows throughout development by using Valgrind (more below).
Pushing changes
When you are ready to commit to your local repository, execute:
$
When you are ready to submit to the remote repository for automated grading or teaching staff feedback, execute:
NOTE: It is considered good practice in industry to commit
locally very frequently, and to push
whenever you have a functioning unit of work.
This is doubly true when you have a continuous integration system like Jenkins to constantly provide feedback on compilation and test completion!
Tools
Valgrind
Valgrind is a tool that can be used to find memory leaks and heap memory problems. Use the following command for Valgrind after building the executable:
Do not attempt to run valgrind on any of the make commands above. Instead of running valgrind on your program, you will be running valgrind on make (which amusingly enough has memory leaks). You must verify that your unit testing of your list library has no memory leaks.
Valgrind output looks like the following:
You want to see 0 errors from 0 contexts. If there is memory in the "definitely lost", "indirectly lost", and "possibly lost" categories (not shown above - the categories are only shown when there are possible memory leaks), you will have an error. The "still reachable" category means that the memory wasn't freed before the program stopped execution. Since that memory is freed when program execution stops, we will not deduct for memory in that category (and Valgrind doesn't report an error), but we encourage you to free all memory before stopping program execution.
A limitation of Valgrind is that it can only detect memory leaks on actual executions. Having no memory leaks reported for the provided tests and your tests does not mean that there is not a memory leak in your program. The teaching staff will also inspect your code for potential memory leaks. The best way to avoid memory leaks is to free all dynamically allocated memory on all paths in the program (e.g., don't forget to free memory on error paths or to free memory in the test code!).
make_test_world
A simple utility to create world files has been included, make_test_world.c
.
There's no need to document or modify this code unless you wish to.
Note that the utility is "dumb", i.e. it you have to figure out the room count and room id's yourself for it to produce correct output.
The kit comes configured to build this tool when you type make
; the Makefile
is also configured to run the tool to produce test files:
test_world_1.dat
: A very minimalist world with just three rooms. The first has the weapon, the second has the enemy and key, and the third has the chest.test_world_2.dat
: A world designed to test your handling of objects, this file has rooms with all 16 combinations of objects possible.test_world_3.dat
: A world designed to test your long-range enemy detector code, this file has an enemy in line-of-sight along three directions, with around around a corner in the fourth direction; you should only detect an enemy from the starting room in the first three.
strip_escape_codes.pl
and relaxed_diff
strip_escape_codes.pl
is a simple utility written in Perl to remove ANSI color escape codes from any input it gets, either stdin or filename arguments.
You may find it useful if you decide to use color, but still want to be able to diff against known outputs. Example:

relaxed_diff
is a front-end to diff that will ignore both whitespace differences and ANSI color escape sequences. Options are the same as diff, except the order in which they're specified is rigid:
test.sh
use relaxed_diff
, as do the instructor tests. This means that you can change the spacing or coloration of your output without failing tests.
Testing
Testing materials provided
The following materials are included in the starter kit:- Testing worlds:
test_world_1.dat
: A fairly small 3-room world that represents the minimum adventure: weapon, key+enemy, chest.test_world_2.dat
: A world with 16 test rooms, each with a different combination of objects (24=16). See starting room for a map.test_world_3.dat
: A world with an enemy straight in every direction except north. The distances differ to help you test your long-range enemy detector. To the north, there's an enemy around a corner, which you should NOT be able to see from here. See starting room for a map.test_world_4.dat
: A conversion of the entire ROM 2.4b6 MUD world to the format used by this project. For a map of the starting area, see this sweet ASCII art. You can find a weapon at the armoury (south/west/south from Temple Square, the starting point), a key in the forest (south, then several rooms west of the Temple Square), and a chest in the inn (east/east of Temple Square). NOTE: there are areas of this world that loop back in on themselves, meaning they can lock up even a correctly implemented enemy detector. You have to go pretty far to find them, and no existing test case hits one.bad_world_1.dat
: A world file with an invalid name length (exceeds maximum).bad_world_2.dat
: A world file with an invalid exit linkage (origin room out of range).bad_world_3.dat
: A world file with an invalid exit linkage (direction not in the range [0,3]).tiny_world.dat
: A healthy version of the bad worlds, only two rooms.
- System tests:
tests/
: A collection of over 200 system test inputs and expected outputs.test.sh
: A script to conduct these system tests.
- Unit test framework:
quester_test.c
: An alternate front-end for theworld
/player
modules for use in developing unit tests. Sample interactions with these modules are provided, but no checks as the results have been implemented.
- Utilities:
make_test_world.c
: The code that generated the first three test worlds, available for you to modify if you wish.quester-reference
: A pre-compiled reference implementation of the instructor's solution.relaxed_diff
: A front-end for the "diff" utility that will ignore differences in whitespace and ANSI color codes. The tests intest.sh
use this tool, as do the instructor tests.strip_escape_codes.pl
: A utility to remove ANSI color codes from any input files given, printing the result to stdout.
- Test cases 0..233: A sequence of increasingly successful attempts at test world 1, each followed by every possible command as a separate test case.
- Test case 234: A complete walk of test world 2, killing monsters as needed, to verify that object handling is correct.
- Test cases 235..248: Various walks of test world 3, some hitting into walls or dying from the enemy, in order to test enemy handling and remote detection.
- Test cases 249..253: Various adventures in the very large world 4.
- Test cases 254..256: Attempts to open the bad test worlds. We are NOT grading on exact error output, all you need to do is exit gracefully and not segfault.
Additional testing requirements
At this point, you should have a decent understanding of good testing practices. As such, the testing requirement will be stated simply:- You must develop a new legal world file.
- You must develop at least two (2) system test cases against this world file, each terminating in a different exit status. Add them to
test.sh
. - You must develop at least three (3) unit tests in
quester_test.c
. The code already present calls a fewplayer
functions, but does not check the results in any way; you'll fix that. The exact nature of your unit tests and the manner in which they report to the console is left to you.
Checking Jenkins Feedback
We have created a Jenkins build job for you. Jenkins is a continuous integration server that is used in industry to compile, build, and test applications under development. We will be using Jenkins to compile, build, and test your homework submissions to GitHub, which will provide early feedback on the completeness (does your implementation meet the requirements) and quality (does your implementation correctly implement the requirements).
Your Jenkins job is associated with your GitHub repository and will poll or query GitHub every two minutes for changes. After you have pushed code to GitHub, Jenkins will notice the change and automatically start a build process on your code. The following actions will occur:
- Code will be pulled from your GitHub repository
- A check for carriage return/line feed pairs will run. If a message is printed, make sure that your editor is set to not include carriage returns.
- The style checker vera++ will run on your code and report notifications that you should fix.
- A magic number checker will run on your code and report notifications that you should fix. We will only run the magic number checker on
player.[ch]
,world.[ch]
, andquester.c
. - We'll make your unit tests and execute them. A non-zero exit code will stop the build process.
- We'll run a series of system tests against your game.
- We'll run a subset of the system tests through valgrind to identify any memory leaks.
- A failing build means that one or more of the unit tests (yours or ours) didn't pass. Use the messages for feedback or ask for clarification from the teaching staff.
Jenkins will record the results of each execution. To obtain your Jenkins feedback, do the following tasks:
- Go to Jenkins for CSC230
- Click the project named HW5-<unityid>
- There will be a table called Build History in the lower left, click the link for the latest build
- Click the Console Output link in the left menu (4th item)
- The console output provides the feedback from static analysis (style and magic number checks), compiling your program, and executing the test cases
Instructions for Submission
The following files MUST be pushed to your assigned CSC230 GitHub repository:
Makefile
player.c
player.h
quester.c
world.c
world.h
test.sh
-- including your additional system test casestests/*
-- including your additional system test cases and all test resultsquester_test.c
-- your unit test cases
By submitting the actual results from the tests, you will prove that you have tested your program with the minimum set of the provided acceptance tests. Automated grading will not run on your program without at least one generated actual result file (it doesn't mean your tests have to pass, just that you've attempted the tests locally before pushing to your repo).
Additional Considerations
Follow the CSC 230 Style Guidelines. Make sure your program compiles on the common platform and Jenkins cleanly (no errors or warnings), with the required compile options.
There are certain learning outcomes and basic software engineering skills that this assignment is assessing. See the rubric below for deductions may be applied to your submission as enforcement of good software engineering practices and assignment intentions.
Make sure that you push your code to the GitHub repository provided for you! Pushing your code to GitHub is your submission!
There is a 24 hour window for late submissions. After the main deadline, continue to submit to GitHub. We will use the last commit to GitHub before the late deadline for grading and the timestamp of that commit will determine a deduction, if any.
Rubric
Quester
+5 for compiling on the common platform with gcc –Wall –std=c99 options, with no warnings
+50 for passing teaching staff unit tests. The precise weighting of the test cases is at the discretion of the instructor (for example, over 200 test cases act on a test world 1; this will be balanced against the one test case that runs against test world 2).
+5 for developing a new legal test world
+10 for at least two (2) high quality system test cases using the new world; tests are to be added to test.sh
+10 for at least three (3) high quality unit tests in quester_test.c
.
+10 for passing the student written tests listed above.
+20 for comments and style
-5 for meaningless or missing comments
-5 for inconsistent formatting
-5 for magic numbers (other than 0 as a loop bound, +1, -1, and minor increments/decrements - if the value has meaning (say as a state or a resolution) it should be given a name (e.g., a macro, an enum, etc.). Magic numbers as expected test output are allowed in list_test.c! Good tests MUST have concrete expected values!
-5 for correct header information
Extra credit
+15 extra credit for the cheating solver cheat_quester
.
OR
+30 extra credit for the fair solver ai_quester
.
OR
+15 extra credit for the world editor world_maker
.
OR
+10 extra credit for additional commands/objects.
Total Points Possible: 110 (without extra credit)
Deductions (we will not go negative with your score)
-20 for late submission
-20 points: a memory leak is reported via valgrind tool (see tools section) OR as observed via a test or inspection by teaching staff
-20 points: for any observed buffer overflow OR security vulnerability
-60 points: not implementing the dynamically allocated structures described
-10 for a submission where a file is misnamed