Assignment #4

Intermediate C Programming

UW Experimental College


Assignment #4

Handouts:

Assignment #4
Assignment #3 Answers
Class Notes, Chapter 20

Exercises:

  1. This week we're going to add containers (so that objects can be put inside of other objects) and attributes, so that we can remember things about objects (such as whether they're containers, whether they're open, etc.).

    The code to implement these changes is scattered, and not all of it is in the distribution directories. You'll have to type some of this new code in yourself.

    Here is the modified object structure, along with a few definitions:
    struct object
    	{
    	char name[MAXNAME];
    	unsigned int attrs;
    	struct object *contents;	/* contents (if container) */
    	struct object *lnext;		/* next in list of contained objects */
    					/* (i.e. in this object's container) */
    	char *desc;			/* long description */
    	};
    
    #define CONTAINER	0x0001
    #define CLOSABLE	0x0002
    #define OPEN		0x0004
    #define HEAVY		0x0008
    #define BROKEN		0x0010
    #define TOOL		0x0020
    #define SOFT		0x0040
    #define SHARP		0x0080
    #define LOCK		0x0100
    #define KEY		0x0200
    #define LOCKED		0x0400
    #define TRANSPARENT	0x0800
    #define IMMOBILE	0x1000
    
    #define Iscontainer(o) ((o)->attrs & CONTAINER)
    #define Isopen(o) ((o)->attrs & OPEN)
    
    (This code is in the file game.xh in the week4 subdirectory, although the copy there is missing the new desc field for objects.)

    Here is new code, for commands.c, to implement new ``open,'' ``close,'' and ``put (in)'' commands:
    else if(strcmp(verb, "open") == 0)
    	{
    	if(objp == NULL)
    		{
    		printf("You must tell me what to open.\n");
    		return FALSE;
    		}
    	if(Isopen(objp))
    		{
    		printf("The %s is already open.\n", objp->name);
    		return FALSE;
    		}
    	if(!(objp->attrs & CLOSABLE))
    		{
    		printf("You can't open the %s.\n", objp->name);
    		return FALSE;
    		}
    	objp->attrs |= OPEN;
    	printf("The %s is now open.\n", objp->name);
    	}
    
    else if(strcmp(verb, "close") == 0)
    	{
    	if(objp == NULL)
    		{
    		printf("You must tell me what to close.\n");
    		return FALSE;
    		}
    	if(!(objp->attrs & CLOSABLE))
    		{
    		printf("You can't close the %s.\n", objp->name);
    		return FALSE;
    		}
    	if(!Isopen(objp))
    		{
    		printf("The %s is already closed.\n", objp->name);
    		return FALSE;
    		}
    	objp->attrs &= ~OPEN;
    	printf("The %s is now closed.\n", objp->name);
    	}
    
    else if(strcmp(verb, "put") == 0)
    	{
    	if(objp == NULL)
    		{
    		printf("You must tell me what to put.\n");
    		return FALSE;
    		}
    	if(!contains(player->contents, objp))
    		{
    		printf("You don't have the %s.\n", objp->name);
    		return FALSE;
    		}
    	if(cmd->preposition == NULL || strcmp(cmd->preposition, "in") != 0 ||
    			cmd->xobject == NULL)
    		{
    		printf("You must tell me where to put the %s.\n",
    							objp->name);
    		return FALSE;
    		}
    	if(!Iscontainer(cmd->xobject))
    		{
    		printf("You can't put things in the %s.\n",
    							cmd->xobject->name);
    		return FALSE;
    		}
    	if((cmd->xobject->attrs & CLOSABLE) && !Isopen(cmd->xobject))
    		{
    		printf("The %s is closed.\n", cmd->xobject->name);
    		return FALSE;
    		}
    	if(!putobject(player, objp, cmd->xobject))
    		{
    		/* shouldn't happen */
    		printf("You can't put the %s in the %s!\n",
    			objp->name, cmd->xobject->name);
    		return FALSE;
    		}
    	printf("Now the %s is in the %s.\n",
    			objp->name, cmd->xobject->name);
    	}
    
    (This code is in the file week4/commands.xc.)

    Here is a new version of newobject, for object.c, that initializes the new object attribute and contents fields. (It's also missing any required initialization of the new long description field; make sure you preserve yours.)
    struct object *
    newobject(char *name)
    {
    struct object *objp;
    
    if(nobjects >= MAXOBJECTS)
    	{
    	fprintf(stderr, "too many objects\n");
    	exit(1);
    	}
    
    objp = &objects[nobjects++];
    
    strcpy(objp->name, name);
    objp->lnext = NULL;
    objp->attrs = 0;
    objp->contents = NULL;
    
    return objp;
    }
    
    Here is the new putobject function, for object.c. (It's in week4/object.xc.)
    /* transfer object from actor to container */
    
    putobject(struct actor *actp, struct object *objp, struct object *container)
    {
    struct object *lp;
    struct object *prevlp = NULL;
    
    for(lp = actp->contents; lp != NULL; lp = lp->lnext)
    	{
    	if(lp == objp)				/* found it */
    		{
    		/* splice out of actor's list */
    
    		if(lp == actp->contents)	/* head of list */
    			actp->contents = lp->lnext;
    		else	prevlp->lnext = lp->lnext;
    
    		/* splice into container's list */
    
    		lp->lnext = container->contents;
    		container->contents = lp;
    
    		return TRUE;
    		}
    
    	prevlp = lp;
    	}
    
    /* didn't find it (error) */
    
    return FALSE;
    }
    
    Here is new code for the parsedatafile function in io.c, to read attributes for objects:
    	else if(strcmp(av[0], "attribute") == 0)
    		{
    		if(currentobject == NULL)
    			continue;
    		if(strcmp(av[1], "container") == 0)
    			currentobject->attrs |= CONTAINER;
    		else if(strcmp(av[1], "closable") == 0)
    			currentobject->attrs |= CLOSABLE;
    		else if(strcmp(av[1], "open") == 0)
    			currentobject->attrs |= OPEN;
    		else if(strcmp(av[1], "heavy") == 0)
    			currentobject->attrs |= HEAVY;
    		else if(strcmp(av[1], "soft") == 0)
    			currentobject->attrs |= SOFT;
    		else if(strcmp(av[1], "sharp") == 0)
    			currentobject->attrs |= SHARP;
    		}
    
    (This code is incomplete; at some point you'll have to add cases for the other attributes defined in game.h.)

    Here are the ``break'' and ``cut'' commands from commands.c, modified to make use of a few attributes:
    else if(strcmp(verb, "break") == 0)
    	{
    	if(objp == NULL)
    		{
    		printf("You must tell me what to break.\n");
    		return FALSE;
    		}
    	if(cmd->preposition == NULL || strcmp(cmd->preposition, "with") != 0 ||
    			cmd->xobject == NULL)
    		{
    		printf("You must tell me what to break with.\n");
    		return FALSE;
    		}
    	if(!contains(player->contents, cmd->xobject))
    		{
    		printf("You have no %s.\n", cmd->xobject->name);
    		return FALSE;
    		}
    	if(!(cmd->xobject->attrs & HEAVY))
    		{
    		printf("I don't think the %s is heavy enough to break things with.\n",
    							cmd->xobject->name);
    		return FALSE;
    		}
    	objp->attrs |= BROKEN;
    	printf("Oh, dear.  Now the %s is broken.\n", objp->name);
    	}
    
    else if(strcmp(verb, "cut") == 0)
    	{
    	if(objp == NULL)
    		{
    		printf("You must tell me what to cut.\n");
    		return FALSE;
    		}
    	if(cmd->preposition == NULL || strcmp(cmd->preposition, "with") != 0 ||
    			cmd->xobject == NULL)
    		{
    		printf("You must tell me what to cut with.\n");
    		return FALSE;
    		}
    	if(!contains(player->contents, cmd->xobject))
    		{
    		printf("You have no %s.\n", cmd->xobject->name);
    		return FALSE;
    		}
    	if(!(cmd->xobject->attrs & SHARP))
    		{
    		printf("I don't think the %s is sharp enough to cut things with.\n",
    							cmd->xobject->name);
    		return FALSE;
    		}
    	if(!(objp->attrs & SOFT))
    		{
    		printf("I don't think you can cut the %s with the %s.\n",
    					objp->name, cmd->xobject->name);
    		return FALSE;
    		}
    	printf("The %s is now cut in two.\n", objp->name);
    	}
    
    Try to get all of this code integrated, compiled, and working. You will probably have to add a few prototypes to game.h.

    Once it compiles, before you can play with the new features, you will have to make some changes to the data file, dungeon.dat, too. Make the hammer heavy by adding the line
    	attribute heavy
    
    just after the line
    	object hammer
    
    Make the kettle a container by adding the line
    	attribute container
    
    just after the line
    	object kettle
    
    Add a knife object by adding the lines
    	object knife
    	attribute sharp
    
    in one of the rooms. Add a box object by adding the lines
    	object box
    	attribute closable
    
    Now you should be able to break things with the hammer, and put things into the kettle or box (once you open the box). The knife won't cut things unless they're soft, so add an object with
    	attribute soft
    
    and try cutting it with the knife. Once you put things into containers, they'll seem to disappear; we'll fix that in the next two exercises.

    With these changes, the game suddenly explodes in potential complexity. There are lots and lots of features you'll be able to add, limited only by your own creativity and the amount of time you care to spend/waste. I've suggested several changes and improvements below, although you don't have to make all of them, and you're not limited to these suggestions, either. (You won't be able to add too many more attributes, though, without possibly running out of bits in an int. We'll start using a more open-ended scheme for implementing attributes next time. In the mean time, you could make the attrs field an unsigned long int if you were desperate to add more attributes.)
  2. Modify the findobject function in object.c so that it can find objects when they're inside containers (both in the actor's possession and sitting in the room). The original implementation of findobject has separate loops for searching the actor's list of objects and the room's list of objects. But now, when an object (in either list) is a container, we'll need to search through its list of (contained) objects, too. It will be easiest if you add a new function, findobj2, which searches for an object in any list of objects. The code in findobj2 will be just like the old code in findobject for searching the actor's and room's lists; in fact, once you write findobj2, you'll be able to replace the actor- and room-searching code with simple calls to
    	findobj2(actp->contents, name);
    
    and
    	findobj2(actp->location->contents, name);
    
    Ideally, your scheme should work even to find objects within objects within objects (or deeper); this will involve recursive calls to findobj2.
  3. Modify the takeobject function in object.c so that it can also take objects which are sitting in containers (both in the actor's possession and sitting in the room). If a container is CLOSABLE, make sure it's OPEN!
  4. Rewrite the ``examine'' command (in commands.c) to mention some of the relevant attributes (OPEN, BROKEN, etc.) of the object being examined. (Hint: this is a good opportunity to use the conditional or ?: operator.)
  5. Modify the listobjects function in object.c to list the contents (if any) of objects which are containers. Should it print the contents of closed containers? (In a more complicated game, we might worry about transparency...)
  6. Implement objects which can be locked (if they have the LOCK attribute). Add a ``lock'' command to lock them (i.e. to set the LOCKED attribute) and an ``unlock'' command to unlock them. Both ``lock'' and ``unlock'' should require that the user use a tool which has the KEY attribute. (In other words, ``lock strongbox with key'' should work, but ``lock strongbox with broom'' should not work. But don't worry about trying to make certain keys fit only certain locks.) Don't let objects be opened if they have the LOCK attribute and are locked. (You'll have to update the attribute-reading code in io.c to handle the LOCK, KEY, and LOCKED attributes.)
  7. Implement a ``fix'' command which will let the user fix broken objects, as long as the preposition ``with'' appears and the implement is an object with the TOOL attribute. (You'll have to update the attribute-reading code in io.c to handle the TOOL attribute.)
  8. Implement immobile objects that can't be picked up. (You'll have to update the attribute-reading code in io.c to handle the IMMOBILE attribute.)


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