Assignment #4 Answers

Intermediate C Programming

UW Experimental College


Assignment #4 ANSWERS

Exercise 2. Modify the findobject function in object.c so that it can find objects when they're inside containers.

As suggested in the assignment, we'll need to break the old findobject function up into two functions: findobject and findobj2. findobject accepts an actor and the name of an object to find (as before); findobj2 accepts a pointer to a list of objects, and the name of an object to find.

static struct object *findobj2(struct object *, char *);

struct object *
findobject(struct actor *actp, char *name)
{
struct object *lp;

/* first look in actor's possessions: */

lp = findobj2(actp->contents, name);
if(lp != NULL)
	return lp;

/* now look in surrounding room: */

if(actp->location != NULL)
	{
	lp = findobj2(actp->location->contents, name);
	if(lp != NULL)
		return lp;
	}

/* didn't find it */

return NULL;
}

/* find a named object in an object list */
/* (return NULL if not found) */

static struct object *
findobj2(struct object *list, char *name)
{
struct object *lp;

for(lp = list; lp != NULL; lp = lp->lnext)
	{
	if(strcmp(lp->name, name) == 0)
		return lp;
	if(lp->contents != NULL)
		{
		struct object *lp2 = findobj2(lp->contents, name);
		if(lp2 != NULL)
			return lp2;
		}
	}

/* didn't find it */

return NULL;
}
The body of findobj2 is the loop that findobject used to use when it searched the player's possessions and the room's contents; findobject now simply calls findobj2 at those two spots. findobj2 also calls itself, recursively, when one of the objects it's inspecting (from either list, or any list) is a container object with its own contents.

I've declared findobj2 as static, which means that it can only be called from within object.c. It's an auxiliary function, which only findobject calls, so no one outside needs to (or should) be able to call it. The function prototype for findobj2 includes the keyword static, too.

Exercise 3. Modify the takeobject function in object.c so that it can also take objects which are sitting in containers.

This is harder than it ought to be, because we have to remove the object from the container's list; however, it's not trivial to find the object which contains another object. Ideally, all objects would also have pointers back to their containers (just as actors already have pointers back to their rooms). For now, we'll write a findcontainer function which is kind of like findobject except that it returns the object's container. As for findobject, we'll also need a recursive findcont2 function.

I added this code at the end of takeobject (just before the ``didn't find it'' comment and the last return FALSE; line):

/* perhaps it's in a container */

containerp = findcontainer(objp, actp, roomp);

if(containerp != NULL)
	{
	/* this test should probably be up in commands.c) */

	if((containerp->attrs & CLOSABLE) && !Isopen(containerp))
		{
		printf("The %s is closed.\n", containerp->name);
		return FALSE;
		}

	/* re-find in container's list, for splicing */

	prevlp = NULL;

	for(lp = containerp->contents; lp != NULL; lp = lp->lnext)
		{
		if(lp == objp)				/* found it */
			{
			/* splice out of room's list */

			if(lp == containerp->contents)	/* head of list */
				containerp->contents = lp->lnext;
			else	prevlp->lnext = lp->lnext;

			/* splice into actor's list */

			lp->lnext = actp->contents;
			actp->contents = lp;

			return TRUE;
			}

		prevlp = lp;
		}
	}
As the comment indicates, the test for a closed container probably belongs up in commands.c. (That's where the other similar tests are; the functions in object.c are supposed to be low-level, and just manipulate data structures.) But, because of the clumsy way we're currently handling actors, rooms, objects, and containers, it's hard to do it right. (You may not yet have realized the clumsiness of the current scheme. The problem is that actors are really objects and rooms are really containers, and since containers are objects, rooms are objects, too. We'll have more to say about this issue later.)

Here are findcontainer and findcont2:

/* find an object's container (in actor's possession, or room) */
/* (return NULL if not found) */

static struct object *
findcontainer(struct object *objp, struct actor *actp, struct room *roomp)
{
struct object *lp, *lp2;

/* first look in possessions: */

for(lp = actp->contents; lp != NULL; lp = lp->lnext)
	{
	if(lp->contents != NULL)
		{
		lp2 = findcont2(objp, lp);
		if(lp2 != NULL)
			return lp2;
		}
	}

/* now look in room: */

for(lp = roomp->contents; lp != NULL; lp = lp->lnext)
	{
	if(lp->contents != NULL)
		{
		lp2 = findcont2(objp, lp);
		if(lp2 != NULL)
			return lp2;
		}
	}

return NULL;
}

static struct object *
findcont2(struct object *objp, struct object *container)
{
struct object *lp;

for(lp = container->contents; lp != NULL; lp = lp->lnext)
	{
	if(lp == objp)
		return container;
	if(lp->contents != NULL)
		{
		struct object *lp2 = findcont2(objp, lp);
		if(lp2 != NULL)
			return lp2;
		}
	}

return NULL;
}

Exercise 4. Rewrite the ``examine'' command to mention some of the relevant attributes of the object being examined.

Here is the modified code (from docommand in commands.c):

else if(strcmp(verb, "examine") == 0)
	{
	int printedsomething;

	if(objp == NULL)
		{
		printf("You must tell me what to examine.\n");
		return FALSE;
		}

	printedsomething = FALSE;

	if(objp->desc != NULL && *objp->desc != '\0')
		{
		printf("%s\n", objp->desc);
		printedsomething = TRUE;
		}

	if(Iscontainer(objp))
		{
		if(objp->attrs & CLOSABLE)
			{
			printf("The %s is %s.\n", objp->name,
				Isopen(objp) ? "open" : "closed");
			printedsomething = TRUE;
			}

		if((!(objp->attrs & CLOSABLE) || Isopen(objp)) &&
				objp->contents != NULL)
			{
			printf("The %s contains:\n", objp->name);
			listobjects(objp->contents);
			printedsomething = TRUE;
			}
		}

	if(objp->attrs & BROKEN)
		{
		printf("The %s is broken.\n", objp->name);
		printedsomething = TRUE;
		}

	if(objp->attrs & SHARP)
		{
		printf("The %s is quite sharp.\n", objp->name);
		printedsomething = TRUE;
		}

	if(!printedsomething)
		printf("You see nothing special about the %s.\n", objp->name);
	}
There are several conditions under which this code prints something ``special'' about the object being examined: if the object has a long description, if the object is a container, if the object has certain attributes, etc. Whenever the code prints one of these messages (i.e. under any circumstances) it sets the Boolean variable printedsomething to TRUE. At the very end, if we haven't found anything interesting to print (i.e. if printedsomething is still FALSE), we fall back on the generic ``You see nothing special'' message.

In simpler cases, it's nice to arrange conditionals so that you don't need extra Boolean variables. Here, however, the condition under which we print ``You see nothing special'' would be so complicated if we tried to express it directly that it's much easier to just use the little printedsomething variable. (Among other things, using printedsomething means that it will be easier to add more attribute printouts later, as long as they all set printedsomething, too.)

Exercise 5. Modify the listobjects function in object.c to list the contents of objects which are containers.

Here is the simple, straightforward implementation:

void
listobjects(struct object *list)
{
struct object *lp;

for(lp = list; lp != NULL; lp = lp->lnext)
	{
	printf("%s\n", lp->name);
	if(lp->contents != NULL)
		{
		printf("The %s contains:\n", lp->name);
		listobjects(lp->contents);
		}
	}
}

However, this won't look too good, because when we list, say, the contents of a room, and the room contains a container and some other objects, the list of objects in the container won't be demarcated from the other objects in the room. So, a fancier solution would be to indent each container's list relative to the surrounding list. To do this, we add a ``depth'' parameter which keeps track (with each recursive call) of how deeply the objects and containers we're listing are. The depth obviously starts out as 0, and to keep all the old callers of listobjects from having to add this new argument, we rename listobjects as listobjs2, and then have a new, stub version of listobjects (which everyone else continues to call) which simply calls listobjs2, starting off the recursive chain at a depth of 0. (Since listobjs2 is only called by listobjects, it's declared static.)

static void listobjs2(struct object *, int);

void
listobjects(struct object *list)
{
listobjs2(list, 0);
}

static void
listobjs2(struct object *list, int depth)
{
struct object *lp;

for(lp = list; lp != NULL; lp = lp->lnext)
	{
	printf("%*s%s\n", depth, "", lp->name);
	if(lp->contents != NULL)
		{
		printf("%*sThe %s contains:\n", depth, "", lp->name);
		listobjs2(lp->contents, depth + 1);
		}
	}
}
The indentation is done using a trick: the "%*s" format means to print a string in a field of a given width, where the width is taken from printf's argument list. The string we ask to be printed is the empty string, because all we want is a certain number of spaces (that is, the spaces which printf will add to pad our string out to the requested width). But (once we know the trick, anyway) this is considerably easier than having to write little loops which print a certain number of spaces.

Exercise 6. Implement objects which can be locked.

Here are the new ``lock'' and ``unlock'' commands:

else if(strcmp(verb, "lock") == 0)
	{
	if(objp == NULL)
		{
		printf("You must tell me what to lock.\n");
		return FALSE;
		}
	if(!(objp->attrs & LOCK))
		{
		printf("You can't lock the %s.\n", objp->name);
		return FALSE;
		}
	if(Isopen(objp))
		{
		printf("The %s is open.\n", objp->name);
		return FALSE;
		}
	if(cmd->preposition == NULL || strcmp(cmd->preposition, "with") != 0 ||
			cmd->xobject == NULL)
		{
		printf("You must tell me what to lock 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 & KEY))
		{
		printf("The %s won't lock the %s.\n",
					cmd->xobject->name, objp->name);
		return FALSE;
		}
	if(objp->attrs & LOCKED)
		{
		printf("The %s is already locked.\n", objp->name);
		return FALSE;
		}

	objp->attrs |= LOCKED;
	printf("The %s is now locked.\n", objp->name);
	}

else if(strcmp(verb, "unlock") == 0)
	{
	if(objp == NULL)
		{
		printf("You must tell me what to unlock.\n");
		return FALSE;
		}
	if(!(objp->attrs & LOCK))
		{
		printf("You can't unlock the %s.\n", objp->name);
		return FALSE;
		}
	if(cmd->preposition == NULL || strcmp(cmd->preposition, "with") != 0 ||
			cmd->xobject == NULL)
		{
		printf("You must tell me what to unlock 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 & KEY))
		{
		printf("The %s won't unlock the %s.\n",
					cmd->xobject->name, objp->name);
		return FALSE;
		}
	if(!(objp->attrs & LOCKED))
		{
		printf("The %s is already unlocked.\n", objp->name);
		return FALSE;
		}

	objp->attrs &= ~LOCKED;
	printf("The %s is now unlocked.\n", objp->name);
	}

Here is the modified ``open'' command:

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;
		}
	if((objp->attrs & LOCK) && (objp->attrs & LOCKED))
		{
		printf("The %s is locked.\n", objp->name);
		return FALSE;
		}
	objp->attrs |= OPEN;
	printf("The %s is now open.\n", objp->name);
	}
(The ``close'' command doesn't need modifying; we'll let open containers and doors swing and latch closed even if they were already locked.)

Finally, here are a few more lines for io.c, to read the new attributes needed by this and the following two exercises:

		else if(strcmp(av[1], "lock") == 0)
			currentobject->attrs |= LOCK;
		else if(strcmp(av[1], "locked") == 0)
			currentobject->attrs |= LOCKED;
		else if(strcmp(av[1], "key") == 0)
			currentobject->attrs |= KEY;
		else if(strcmp(av[1], "tool") == 0)
			currentobject->attrs |= TOOL;
		else if(strcmp(av[1], "immobile") == 0)
			currentobject->attrs |= IMMOBILE;

Exercise 7. Implement a ``fix'' command which will let the user fix broken objects.

Here is the code (for docommand in commands.c, of course):

else if(strcmp(verb, "fix") == 0)
	{
	if(objp == NULL)
		{
		printf("You must tell me what to fix.\n");
		return FALSE;
		}
	if(!(objp->attrs & BROKEN))
		{
		printf("The %s is not broken.\n", objp->name);
		return FALSE;
		}
	if(cmd->preposition == NULL || strcmp(cmd->preposition, "with") != 0 ||
			cmd->xobject == NULL)
		{
		printf("You must tell me what to fix 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 & TOOL))
		{
		printf("I don't see how you can fix things with a %s.\n",
							cmd->xobject->name);
		return FALSE;
		}

	objp->attrs &= ~BROKEN;
	printf("Somehow you manage to fix the %s with the %s.\n",
				objp->name, cmd->xobject->name);
	}

Exercise 8. Implement immobile objects that can't be picked up.

Here is the modified ``take'' command:

else if(strcmp(verb, "take") == 0)
	{
	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(objp->attrs & 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");


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