This handout presents
improvements to the game,
in terms of an assignment,
but more as food for thought.
- Last week we added a func field to struct object,
so that an object could contain a pointer to a function
implementing actions specific to that object.
gives us is to write new, object-specific code
which is intended to be fired up not directly,
in response to a user's typed command,
but rather, indirectly,
at other spots in the game where we're working with objects.
We'll do this by letting objects optionally define special ``verbs''
(with names beginning with periods, by convention)
which we'll ``call'' when we need to.
Suppose we wanted the name or description of an object
(printed in the listing of a room's contents or the actor's possessions,
or in response to the ``examine'' command)
to vary, depending on the state of the object.
(We've already done that, in a crude way,
by having the ``examine'' command
look at a few of the object attributes we've defined.)
we might want the player to be able to find an object
which is described only as
a wad of rubberized fabric
until the player also finds the ``air pump'' object,
and thinks to type
inflate wad with pump
at which point the object will be described as
an inflatable boat
To do this,
we'll let an object define a pseudoverb ``.list''.
Then, any time we would have
printed the object's name field in a list of objects,
we'll let it print its own name,
if it wants to.
the hypothetical rubber boat
might have object-specific code like this:
int boatfunc(struct actor *actp, struct object *objp,
struct sentence *cmd)
if(strcmp(cmd->verb, ".list") == 0)
printf("an inflatable boat");
else printf("a wad of rubberized fabric");
When we're printing a list of objects,
we'll have to ``call'' the ``.list'' command
call the object's func,
if it has one,
passing a command verb of ``.list'').
Setting up one of these special-purpose commands
as if it were a ``sentence'' the user typed
will be a tiny bit of a nuisance,
so we'll write an intermediate function to do it:
int objcall(struct actor *actp, struct object *objp, char *command)
struct sentence cmd;
if(objp->func == NULL)
cmd.verb = command;
cmd.object = objp;
cmd.preposition = NULL;
cmd.xobject = NULL;
return (*objp->func)(actp, objp, &cmd);
Now we can rewrite the object-listing code
Where it used to say
it can now say
/* if obj has a function, try letting it list itself */
if(lp->func != NULL && objcall(actp, lp, ".list") == SUCCESS)
else printf("%s\n", lp->name);
If objcall returns SUCCESS,
the object has printed itself,
and all we have to do is append the trailing newline.
Otherwise, we print the object's name, as before.
(This modification to the object-listing code has one problem.
If the listobjects function in object.c
is going to call objcall in this way,
it needs a pointer to the actor,
but it doesn't have it.
So we're going to have to rewrite the listobjects function,
and each of the places it's called,
to pass the actor pointer as an additional argument.
As it happens,
the other improvements suggested in this assignment
end up requiring exactly the same change.
It turns out that listobjects
probably should have been accepting the actor pointer
as an extra argument all along,
if there are so many good reasons why it ends up needing it.)
There are several other situations where we can use
custom, ``internal'' functions like these.
We can arrange that an object also be able to print
its long description,
by interpreting a ``.describe'' verb.
(We'd rewrite the ``examine'' command
to print an object's desc field
only if the object didn't succeed
at performing the ``.describe'' verb.)
We could also implement customized ``hooks''
into the action of picking up an object.
In last week's assignment
we implemented an object with a custom ``take'' command
which printed some text
but then returned CONTINUE,
so that the rest of the default ``take'' machinery
would handle the actual picking up of the object.
But we might want the custom ``take'' action
(and any messages it prints)
to happen after the default machinery
has performed most of the picking up of the object.
To do this, we could define a new special verb ``.take2'',
and rewrite the ``take'' code in commands.c
to call .take2 at the very end,
after the call to takeobject.
- When we started writing object-specific functions last week,
we placed them in a file called objfuncs.c.
(as we've begun to see)
it can be a fantastically powerful ability
to allow objects to ``point at''
arbitrarily-complicated functions written just for them,
writing these functions will still be a nuisance.
we've just been playing with the data file,
and we've come up with a fun new object to put in the dungeon for
the player to play with,
and the object needs some new, special-purpose code to make it do its stuff.
We'd have to write that code
(in C, of course),
before we could
hook the new function
up to the new object.
The whole point of the data file is that the information about
objects (and the rest of the dungeon)
can be read out of it;
we stopped editing the source files to change the dungeon
way back during the first week,
when we introduced the data file in the first place.
So, another major leap will be
to put actual code
(not just a pointer to a function)
This code will, however, not be written in C,
for a variety of reasons.
(A sufficient reason is
that there's no easy way to get the C
compiler to compile scraps of source code we're reading from a data file
and to attach the resultant object code to some data structures
in the already-running program.)
So, we'll take the (seemingly big, but it's not that bad) step
of defining our own little miniature language for describing
what objects can do,
writing an interpreter (not a compiler)
which reads and implements this language,
and then writing the code scraps for objects in the little language.
Therefore, we'll add
to struct object,
right next to the func field we added last week:
For objects which contain these new, ``scripted'' functions,
func will be a pointer to
the script-interpreting function
(one function, the same function, for all objects which are scripted),
the script field
the text of the script to be
First, a description of the little language.
It is optimized for the kinds of things that we want our object
(at least the simple ones) to be able to do:
check attributes of the tool or direct object,
set attributes of the tool or direct object,
and print messages.
Here is a sample script:
message "It is broken."
This is some code which might be suitable for the hammer object.
The entry line says that this is code for the verb ``break''.
(There can potentially be many entry lines,
if a tool can be used in multiple ways.)
The chktoolqual line makes sure that the tool has
the given attribute,
and prints an appropriate message (and fails) if it does not.
The csetobjstate conditionally sets the state of
the direct object,
unless the object already has that state,
in which case it prints a message like ``The x is
already broken'', and fails.
Finally, the message line prints a message,
and the return line returns.
The code for the interpreter which will execute these little
scripts is too large to print out,
but it's in the week8 subdirectory of the distributed
It interfaces with the rest of the game in two places.
We must arrange for the interpreter to be called at the right time(s),
but that's done already:
if an object's func pointer points to the new interpreter function,
the code we added to docommand last week
will call it.
But we must also
be able to read in a script
(from the data file)
along with the rest
of a description of an object.
Here is a new case for parsedatafile:
else if(strcmp(av, "script") == 0)
/* XXX cumbersome un-getwords required */
int i, l = 0;
if(currentobject == NULL)
for(i = 1; i < ac; i++)
l += strlen(av[i]) + 1;
currentobject->script = chkmalloc(l);
*currentobject->script = '\0';
for(i = 1; i < ac; i++)
if(i > 1)
strcat(currentobject->script, " ");
currentobject->func = interp;
The script is read all from one line
(which is a nuisance, but this is a preliminary implementation).
The long description reading code faced the same problem;
this code uses a different solution,
by un-doing the action of getwords and rebuilding a
(in malloc'ed memory)
which the script field can point to.
Integrating the interpreter code requires paying attention to
a few other details.
to add the prototype
extern int interp(struct actor *, struct object *, struct sentence *);
to game.h if it's not there already,
and the line
objp->script = NULL;
to the newobject function in object.c.
for the moment,
we'll have to pay attention to the numeric values
of the status codes
(SUCCESS, FAILURE, CONTINUE, ERROR)
we defined last week,
because the simple interpreter doesn't know how to handle
the symbolic names.
have to say return 1 for SUCCESS, etc.
- This game
is a prime candidate for moving from C to C++.
Actors and rooms are really special cases of objects:
Rooms contain objects,
just like actors and container objects do,
and rooms also contain actors.
Rather than having separate structures for rooms, actors, and objects,
we'd like to say things like ``a room is just like an object,
but it also has exits.''
If we rigged it up right
(and cleaned up a number of messes which we've perpetrated
along the way because of the fact that we hadn't been using this
we could have a single piece of code
which would transfer objects between rooms and actors (``take''),
between actors and rooms (``drop''),
between actors and (container) objects (``put in'')
and which would even transfer actors between rooms
(when the actor moved from one room to another).
This function (and much of the rest of the game)
could operate on generic ``objects,''
without worrying about whether they were simple objects, actors,
Only those portions of the game specific to operating on actors
or rooms would look at the additional fields differentiating an
actor or room from an object.
The notion that some data structures are extensions of others,
that an ``x'' is just like a ``y'' except for
some extra stuff,
is the another cornerstone (perhaps the cornerstone)
of Object-Oriented Programming.
The formal term for this idea is inheritance,
and the data structures are usually spoken of as being
objects, which is precisely what the word
``object'' is doing in ``Object-Oriented
(It's again more than an coincidence,
but rather an indication that our game is a perfect application of C++ or
another object-oriented language,
that we were already calling our fundamental data structures
The changes to the game to let it use C++ are extensive,
and I'm not going to try to present them all here.
(I've completed many of the changes, however,
and you can find them in
subdirectories of the
the on-line web pages
for this class.)
The basic idea is that our old struct object
is no longer just a structure;
it is a class:
object(const char *);
struct list *attrs;
object *lnext; /* next in list of contained objects */
/* (i.e. in this object's container) */
char *desc; /* long description */
(Among other things,
all objects now contain pointers back to their containers,
analogous to the way struct actor used to contain
a pointer back to its location.)
We write a constructor for new instances of this class,
replacing our old newobject function in object.c:
object::object(const char *newname)
lnext = NULL;
attrs = NULL;
contents = NULL;
container = NULL;
desc = NULL;
Wherever we used to write
objp = newobject(name);
we instead use the C++ new operator:
objp = new object(name);
(Actually, the only place we called newobject
was in readdatafile in io.c,
when setting currentobject.
It is entirely a coincidence,
though not a surprising one,
that the use of the C++ new operator here
looks so eerily similar to the old newobject call.)
Going back to game.h,
we define the actor and room classes
as being derived from class object:
class actor : public object
class room : public object
room(const char *);
struct room *next; /* list of all rooms */
These say that an actor is just like an object
(we don't actually have any actor-specific information at the moment),
and that a room is just like an object
except that it has an exits array
and an extra next pointer so that we can construct a list of all rooms.
Here is the new,
general-purpose object-transferring function, for object.c:
/* transfer object from one general container to another */
transferobject(object *objp, object *newcontainer)
object *prevlp = NULL;
if(objp->container != NULL)
object *oldc = objp->container;
for(lp = oldc->contents; lp != NULL; lp = lp->lnext)
if(lp == objp) /* found it */
/* splice out of old container's list */
if(lp == oldc->contents) /* head of list */
oldc->contents = lp->lnext;
else prevlp->lnext = lp->lnext;
prevlp = lp;
/* splice into new container's list */
if(newcontainer != NULL)
objp->lnext = newcontainer->contents;
newcontainer->contents = objp;
objp->container = newcontainer;
Notice that the parameters are declared
as being of type ``object *''.
every structure and class you declare
has its tag name implicitly defined as a typedef,
so that the keyword struct or class
is no longer needed in later declarations.
(Theoretically, we should seek out every instance
of struct object in the game
them with object or perhaps class object,
and similarly for every struct actor and struct room.
The compiler I'm using,
the C++ version of the GNU C Compiler,
doesn't seem to be complaining about stray struct keywords
referring to what I've actually redefined as classes,
but I'm not sure what the formal rules of C++ say.)
In any case,
even though transferobject looks like it's defined
only for use on objects,
since actors and rooms are now also objects,
we can also use transferobject
to move objects to and from the actor,
and even to move the actor between rooms.
we can rewrite the other transfer functions from
object.c and rooms.c
takeobject(actor *actp, object *objp)
return transferobject(objp, actp);
dropobject(actor *actp, object *objp)
return transferobject(objp, actp->container);
putobject(actor *actp, object *objp, object *container)
return transferobject(objp, container);
gotoroom(actor *actor, room *room)
return transferobject(actor, room);
- Another improvement which you might be interested in
(and another sweeping one)
would be to rewrite the game so that several people could play it at once,
over the network.
Instead of one instance of struct actor,
representing one player sitting at one keyboard
and viewing output on one screen,
we could have arbitrarily many actor structures
(just as we now have arbitrarily many objects and rooms),
with each actor representing a player
sitting somewhere on the network,
typing input and receiving output over a network connection.
The changes to make a multiplayer version of the game
are actually rather straightforward and self-contained,
with the glaring exception of the fact that
call printf to print some text to the user's screen,
we must instead call some special output function of our own
which knows how to send the text
to the network connection of the appropriate player.
To represent the network connection,
we'll add a file descriptor to the actor structure.
If we're using Unix-style networking,
the file descriptor will just be an integer:
If we've gone over to the C++ style of doing things,
this means that class actor now does have something
to distinguish it from a plain object:
class actor : public object
struct actor *next; /* list of all actors */
(It turns out we're also going to need a list of all players,
just like we need a list of all rooms.)
Then, using what we learned about variable-length argument lists
chapter 25 of the class notes,
we can write an output function
which is like printf
except that it writes the message
to a particular player's network connection:
void output(struct actor *actp, char *msg, ...)
char tmpbuf; /* XXX */
vsprintf(tmpbuf, msg, argp);
write(actp->remotefd, tmpbuf, strlen(tmpbuf));
We call vsprintf to ``print''
the printf-style message
to a temporary string buffer,
then use the low-level Unix write function
to write the string to the network connection.
(This function has a significant limitation as written:
if a single message is ever more than 200 characters long,
the tmpbuf array will overflow,
with results which might range from annoying to catastrophic.
It's unfortunately tricky to this sort of thing right;
the comment /* XXX */ is a reminder
that the tmpbuf array with its fixed size of 200
is a weakness in the program.)
The rest of the code for the multiplayer version of the game
is too bulky to include here,
but I've placed it in
subdirectories of the
file there for more information.