Assignment #6

Intermediate C Programming

UW Experimental College


Assignment #6

Handouts:

Assignment #6
Assignment #5 Answers
Class Notes, Chapter 23
Class Notes, Chapter 24

Exercises:

  1. In this exercise, we'll almost completely rewrite commands.c so that instead of having a giant if/else chain, with many calls to strcmp and the code for all the actions interspersed, there will be one function per command, a table relating command verbs to the corresponding functions, and some much simpler code which will look up a command verb in the table and decide which function to call. The functions in the table will, of course, be referred to by function pointers.

    In the week6 subdirectory of the distribution materials are two new files, cmdtab.c and cmdtab.h, and a modified (though incomplete) copy of commands.c. cmdtab.h defines the command table structure, struct cmdtab:
    struct cmdtab
    	{
    	char *name;
    	int (*func)(struct actor *, struct object *, struct sentence *);
    	};
    
    extern struct cmdtab *findcmd(char *, struct cmdtab [], int);
    


    With this structure in hand, the first thing to do is to declare a bunch of functions, one for each command, and then build the ``command table,'' which is an array of struct cmdtab. All of the command functions are going to take as arguments the actor (struct actor *), the object being acted upon (struct object *, if any), and the complete sentence structure (struct sentence *) describing the command we're trying to interpret/implement. (The object being acted upon is the direct object of the sentence and could also be pulled out of the sentence structure; we're passing it as a separate argument because that might make things more convenient for the command functions themselves.) Here, then, is the command table, incorporating all the commands we've ever used (your copy of the game might not include all of the commands referenced here):
    static int dircmd(struct actor *, struct object *, struct sentence *);
    static int takecmd(struct actor *, struct object *, struct sentence *);
    static int dropcmd(struct actor *, struct object *, struct sentence *);
    static int lookcmd(struct actor *, struct object *, struct sentence *);
    static int invcmd(struct actor *, struct object *, struct sentence *);
    static int examcmd(struct actor *, struct object *, struct sentence *);
    static int hitcmd(struct actor *, struct object *, struct sentence *);
    static int breakcmd(struct actor *, struct object *, struct sentence *);
    static int fixcmd(struct actor *, struct object *, struct sentence *);
    static int cutcmd(struct actor *, struct object *, struct sentence *);
    static int readcmd(struct actor *, struct object *, struct sentence *);
    static int opencmd(struct actor *, struct object *, struct sentence *);
    static int closecmd(struct actor *, struct object *, struct sentence *);
    static int lockcmd(struct actor *, struct object *, struct sentence *);
    static int unlockcmd(struct actor *, struct object *, struct sentence *);
    static int putcmd(struct actor *, struct object *, struct sentence *);
    static int helpcmd(struct actor *, struct object *, struct sentence *);
    static int quitcmd(struct actor *, struct object *, struct sentence *);
    
    static struct cmdtab commands[] =
    	{
    	"n",		dircmd,
    	"north",	dircmd,
    	"s",		dircmd,
    	"south",	dircmd,
    	"e",		dircmd,
    	"east",		dircmd,
    	"w",		dircmd,
    	"west",		dircmd,
    	"ne",		dircmd,
    	"northeast",	dircmd,
    	"se",		dircmd,
    	"southeast",	dircmd,
    	"nw",		dircmd,
    	"northwest",	dircmd,
    	"sw",		dircmd,
    	"southwest",	dircmd,
    	"up",		dircmd,
    	"down",		dircmd,
    	"take",		takecmd,
    	"drop",		dropcmd,
    	"look",		lookcmd,
    	"i",		invcmd,
    	"inventory",	invcmd,
    	"examine",	examcmd,
    	"hit",		hitcmd,
    	"break",	breakcmd,
    	"fix",		fixcmd,
    	"cut",		cutcmd,
    	"read",		readcmd,
    	"open",		opencmd,
    	"close",	closecmd,
    	"lock",		lockcmd,
    	"unlock",	unlockcmd,
    	"put",		putcmd,
    	"?",		helpcmd,
    	"help",		helpcmd,
    	"quit",		quitcmd,
    	};
    


    With this structure defined, the body of docommand is drastically simplified. In fact, since we'll break the searching of the command table out to a separate function, all the new docommand has to do is call that function, then (if the command is found) call the command function associated with the command:
    docommand(struct actor *player, struct sentence *cmd)
    {
    struct cmdtab *cmdtp;
    
    cmdtp = findcmd(cmd->verb, commands, Sizeofarray(commands));
    
    if(cmdtp != NULL)
    	{
    	(*cmdtp->func)(player, cmd->object, cmd);
    	return TRUE;
    	}
    else	{
    	printf("I don't know the word \"%s\".\n", cmd->verb);
    	return FALSE;
    	}
    }
    
    The new findcmd function wants to know (as its third argument) the number of entries in the command table. It would be a nuisance if we had to count them, and we'd have to keep updating our count each time we added an entry to the table. The built-in sizeof operator can tell us the size of the array, but it tells us the size in bytes, while we want to know the number of entries. But the number of entries is the size of the array in bytes divided by the size of one entry in bytes, or
    	sizeof(commands) / sizeof(commands[0])
    
    (here we get a handle on ``the size of one entry'' by arbitrarily taking sizeof(commands[0])). Since this is a common operation, I like to define a function-like macro to encapsulate it:
    	#define Sizeofarray(a) (sizeof(a) / sizeof(a[0]))
    
    With that definition in place, the number of entries in the commands array is simply Sizeofarray(commands).

    Here is the findcmd function, from cmdtab.c:
    #include <string.h>
    #include "cmdtab.h"
    
    struct cmdtab *
    findcmd(char *cmd, struct cmdtab cmdtab[], int ncmds)
    {
    int i;
    
    for(i = 0; i < ncmds; i++)
    	{
    	if(strcmp(cmdtab[i].name, cmd) == 0)
    		return &cmdtab[i];
    	}
    
    return NULL;
    }
    


    The ``only'' thing left to do is to write all of the individual command functions. The code for all of them will come from the corresponding if clause from the old, bulky version of docommand. For example, here is takecmd:
    
    static int
    takecmd(struct actor *player, struct object *objp,
    					struct sentence *cmd)
    {
    	if(objp == NULL)
    		{
    		printf("You must tell me what to take.\n");
    		return FALSE;
    		}
    	if(contains(player->contents, objp))
    		{
    		printf("You already have the %s.\n", objp->name);
    		return FALSE;
    		}
    	if(hasattr(objp, "immobile"))
    		{
    		printf("The %s cannot be picked up.\n", objp->name);
    		return FALSE;
    		}
    	if(!takeobject(player, objp))
    		{
    		/* shouldn't happen */
    		printf("You can't pick up the %s.\n", objp->name);
    		return FALSE;
    		}
    	printf("Taken.\n");
    
    return SUCCESS;
    }
    
    With one exception, all of the rest of the command functions are similar; they just have
    	static int
    	xxxcmd(struct actor *actor, struct object *objp,
    					struct sentence *cmd)
    	{
    	...
    	return SUCCESS;
    	}
    
    wrapped around the code that used to be in one of the
    	else if(strcmp(verb, "xxx") == 0)
    		{
    		...
    		}
    
    blocks.

    (Although the copy of commands.c in the week6 subdirectory reflects this structure, you probably won't be able to use that file directly, because it does not reflect all of the many changes and additions we've been making to commands.c over the past several weeks.)

    The integer code SUCCESS that each of these functions is returning isn't really used yet, so you don't have to worry about what it's for. (Right now, docommand is ignoring the return value of the called command function.)

    The one command function that doesn't quite fit the pattern is dircmd, because it's called for any of the direction commands, and has to take another look at the command verb to see what direction it represents:
    static int
    dircmd(struct actor *player, struct object *objp,
    					struct sentence *cmd)
    {
    struct room *roomp = player->location;
    char *verb = cmd->verb;
    int dir;
    
    if(strcmp(verb, "n") == 0 || strcmp(verb, "north") == 0)
    	dir = NORTH;
    else if(strcmp(verb, "s") == 0 || strcmp(verb, "south") == 0)
    	dir = SOUTH;
    else if(strcmp(verb, "e") == 0 || strcmp(verb, "east") == 0)
    	dir = EAST;
    else if(strcmp(verb, "w") == 0 || strcmp(verb, "west") == 0)
    	dir = WEST;
    else if(strcmp(verb, "ne") == 0 || strcmp(verb, "northeast") == 0)
    	dir = NORTHEAST;
    else if(strcmp(verb, "se") == 0 || strcmp(verb, "southeast") == 0)
    	dir = SOUTHEAST;
    else if(strcmp(verb, "nw") == 0 || strcmp(verb, "northwest") == 0)
    	dir = NORTHWEST;
    else if(strcmp(verb, "sw") == 0 || strcmp(verb, "southwest") == 0)
    	dir = SOUTHWEST;
    else if(strcmp(verb, "up") == 0)
    	dir = UP;
    else if(strcmp(verb, "down") == 0)
    	dir = DOWN;
    else	return ERROR;
    
    if(roomp == NULL)
    	{
    	printf("Where are you?\n");
    	return ERROR;
    	}
    
    /* If the exit does not exist, or if gotoroom() fails,	*/
    /* the player can't go that way.			*/
    
    if(roomp->exits[dir] == NULL || !gotoroom(player, roomp->exits[dir]))
    	{
    	printf("You can't go that way.\n");
    	return ERROR;
    	}
    
    /* player now in new room */
    
    listroom(player);
    
    return SUCCESS;
    }
    
    This function also returns another status code, ERROR (which is also ignored, for now, by docommand). If your copy of game.h doesn't include them, you can put the lines
    	#define ERROR	0
    	#define SUCCESS	1
    
    in it to #define these values.
  2. Modify io.c to recognize a new entrypoint line in the data file, which will list the name of the room which the actor should be placed in to start. What if the entrypoint line precedes the description of the room it names? How should the code in io.c record the entry-point room so that getentryroom in rooms.c can use it? (There are many answers to these questions; pick one, and implement it.)
  3. Another part of the game that has the same sort of large, if/strcmp/else structure as did docommand in commands.c is parsedatafile in io.c. Think about what it would take to use the cmdtab structure, and the findcmd function, to streamline the code that processes lines in the data file.

    (If you try it, you'll probably run into some difficulties which will make it rather hard to finish, so save a copy of io.c before you start! In the answers handed out next week, I'll describe the difficulties, and outline how they might be circumvented.)


This page by Steve Summit // Copyright 1995-9 // mail feedback