How to program a text adventure in C
by Ruud Helderman
<r.helderman@hccnet.nl>
Licensed under
MIT License
5. Inventory
We will now make it possible for actors to ‘hold’ items.
In the previous chapter, we made an array to store all objects, including the player himself.
By considering the player as an object,
we make the player an integrated part of the game,
instead of a spectator watching the virtual world from a safe distance.
The advantage of this approach becomes most obvious in a game with multiple player characters
(either MUD-style
or a single-player game where you can switch between different characters),
but it is equally useful in a solo mission.
Player attributes no longer have to be stored in separate variables;
we can use the same data structure as used for any other object.
So the player, being an object:
- has a location (“where am I?”)
- is a location for any items the player may be holding.
This makes certain common actions very easy to implement:
Action |
Typical command |
Example |
player->location = cave; |
listObjectsAtLocation(cave); |
silver->location = player; |
silver->location = player->location; |
listObjectsAtLocation(player); |
silver->location = guard; |
silver->location = player; |
listObjectsAtLocation(guard); |
Sample output |
Welcome to Little Cave Adventure.
You are in an open field.
You see:
a silver coin
a burly guard
--> get silver
You pick up a silver coin.
--> inventory
You see:
a silver coin
--> look around
You are in an open field.
You see:
a burly guard
--> give silver
You give a silver coin to a burly guard.
--> inventory
You are empty-handed.
--> ask silver
You get a silver coin from a burly guard.
--> inventory
You see:
a silver coin
--> go cave
OK.
You are in a little cave.
You see:
a gold coin
--> give silver
There is nobody here to give that to.
--> drop silver
You drop a silver coin.
--> look around
You are in a little cave.
You see:
a silver coin
a gold coin
--> inventory
You are empty-handed.
--> quit
Bye!
|
Commands go and look (the first two examples above)
were already implemented in the previous chapter.
Now we will introduce some typical inventory actions
for both the player and the non-player characters (commands
get, drop, give, ask and inventory).
parsexec.c |
- #include <stdbool.h>
- #include <stdio.h>
- #include <string.h>
- #include "location.h"
- #include "inventory.h"
- bool parseAndExecute(char *input)
- {
- char *verb = strtok(input, " \n");
- char *noun = strtok(NULL, " \n");
- if (verb != NULL)
- {
- if (strcmp(verb, "quit") == 0)
- {
- return false;
- }
- else if (strcmp(verb, "look") == 0)
- {
- executeLook(noun);
- }
- else if (strcmp(verb, "go") == 0)
- {
- executeGo(noun);
- }
- else if (strcmp(verb, "get") == 0)
- {
- executeGet(noun);
- }
- else if (strcmp(verb, "drop") == 0)
- {
- executeDrop(noun);
- }
- else if (strcmp(verb, "give") == 0)
- {
- executeGive(noun);
- }
- else if (strcmp(verb, "ask") == 0)
- {
- executeAsk(noun);
- }
- else if (strcmp(verb, "inventory") == 0)
- {
- executeInventory();
- }
- else
- {
- printf("I don't know how to '%s'.\n", verb);
- }
- }
- return true;
- }
|
Explanation:
- Line 5:
another new module is included.
- Line 25-44:
adding these 20 lines makes the game recognize five more commands.
The new commands are implemented by the following module.
inventory.h |
- extern void executeGet(const char *noun);
- extern void executeDrop(const char *noun);
- extern void executeAsk(const char *noun);
- extern void executeGive(const char *noun);
- extern void executeInventory(void);
|
inventory.c |
- #include <stdio.h>
- #include "object.h"
- #include "misc.h"
- #include "noun.h"
- #include "move.h"
- void executeGet(const char *noun)
- {
- OBJECT *obj = getVisible("what you want to get", noun);
- if (obj == NULL)
- {
- // already handled by getVisible
- }
- else if (obj == player)
- {
- printf("You should not be doing that to yourself.\n");
- }
- else if (obj->location == player)
- {
- printf("You already have %s.\n", obj->description);
- }
- else if (obj->location == guard)
- {
- printf("You should ask %s nicely.\n", obj->location->description);
- }
- else
- {
- moveObject(obj, player);
- }
- }
- void executeDrop(const char *noun)
- {
- moveObject(getPossession(player, "drop", noun), player->location);
- }
- void executeAsk(const char *noun)
- {
- moveObject(getPossession(actorHere(), "ask", noun), player);
- }
- void executeGive(const char *noun)
- {
- moveObject(getPossession(player, "give", noun), actorHere());
- }
- void executeInventory(void)
- {
- if (listObjectsAtLocation(player) == 0)
- {
- printf("You are empty-handed.\n");
- }
- }
|
Explanation:
- Line 9, 34, 39, 44:
functions getVisible and getPossession
are used to determine which object belongs to a given noun;
see noun.c below.
- Line 28, 34, 39, 44:
function moveObject
is used to transport the object to its new location;
see move.c below.
- Line 39, 44:
due to the simplicity of our verb-noun parser,
commands ask and give have only one argument: the item.
The actor involved is implied by using function actorHere
(defined below).
- Line 49:
function listObjectsAtLocation
was already defined in the previous chapter (misc.c).
Its return value tells us how many objects were found.
- Line 51:
the user expects some response, even when the list of objects is empty.
Essentially, commands get, drop, give and ask
do little more than move an item from one place to another.
A single function moveObject
can perform that action for all four commands.
move.h |
- extern void moveObject(OBJECT *obj, OBJECT *to);
|
move.c |
- #include <stdio.h>
- #include "object.h"
- static void describeMove(OBJECT *obj, OBJECT *to)
- {
- if (to == player->location)
- {
- printf("You drop %s.\n", obj->description);
- }
- else if (to != player)
- {
- printf(to == guard ? "You give %s to %s.\n" : "You put %s in %s.\n",
- obj->description, to->description);
- }
- else if (obj->location == player->location)
- {
- printf("You pick up %s.\n", obj->description);
- }
- else
- {
- printf("You get %s from %s.\n",
- obj->description, obj->location->description);
- }
- }
- void moveObject(OBJECT *obj, OBJECT *to)
- {
- if (obj == NULL)
- {
- // already handled by getVisible or getPossession
- }
- else if (to == NULL)
- {
- printf("There is nobody here to give that to.\n");
- }
- else if (obj->location == NULL)
- {
- printf("That is way too heavy.\n");
- }
- else
- {
- describeMove(obj, to);
- obj->location = to;
- }
- }
|
Explanation:
- Line 6-23:
various messages to confirm that the move was successful.
Alternatively, we could have given a simple ‘OK’.
- Line 28-39:
various conditions under which the move will not take place.
- Line 32-35:
there is only one possibility for the item’s destination to be NULL:
when trying to ‘give’ something
at a location where the player is alone.
- Line 36-39:
some objects are not meant to be picked up;
for example field and cave.
The condition to recognize those objects, is still rather crude here.
For example, it is currently possible to pick up the guard.
This will be improved in chapter 10.
- Line 43:
the core of the function; here the object is actually moved.
Command ‘get’ uses function getVisible
to translate a noun to an object,
just like command ‘go’ does; see the previous chapter.
But for commands that operate on objects the player (or some other actor)
is already holding (drop, ask, give),
we need something slightly different.
We will add a function getPossession to noun.c.
noun.h |
- extern OBJECT *getVisible(const char *intention, const char *noun);
- extern OBJECT *getPossession(OBJECT *from, const char *verb, const char *noun);
|
noun.c |
- #include <stdbool.h>
- #include <stdio.h>
- #include <string.h>
- #include "object.h"
- static bool objectHasTag(OBJECT *obj, const char *noun)
- {
- return noun != NULL && *noun != '\0' && strcmp(noun, obj->tag) == 0;
- }
- static OBJECT *getObject(const char *noun)
- {
- OBJECT *obj, *res = NULL;
- for (obj = objs; obj < endOfObjs; obj++)
- {
- if (objectHasTag(obj, noun))
- {
- res = obj;
- }
- }
- return res;
- }
- OBJECT *getVisible(const char *intention, const char *noun)
- {
- OBJECT *obj = getObject(noun);
- if (obj == NULL)
- {
- printf("I don't understand %s.\n", intention);
- }
- else if (!(obj == player ||
- obj == player->location ||
- obj->location == player ||
- obj->location == player->location ||
- obj->location == NULL ||
- obj->location->location == player ||
- obj->location->location == player->location))
- {
- printf("You don't see any %s here.\n", noun);
- obj = NULL;
- }
- return obj;
- }
- OBJECT *getPossession(OBJECT *from, const char *verb, const char *noun)
- {
- OBJECT *obj = NULL;
- if (from == NULL)
- {
- printf("I don't understand who you want to %s.\n", verb);
- }
- else if ((obj = getObject(noun)) == NULL)
- {
- printf("I don't understand what you want to %s.\n", verb);
- }
- else if (obj == from)
- {
- printf("You should not be doing that to %s.\n", obj->description);
- obj = NULL;
- }
- else if (obj->location != from)
- {
- if (from == player)
- {
- printf("You are not holding any %s.\n", noun);
- }
- else
- {
- printf("There appears to be no %s you can get from %s.\n",
- noun, from->description);
- }
- obj = NULL;
- }
- return obj;
- }
|
Explanation:
- Line 45-75:
Just like function getVisible,
the new function getPossession is a wrapper around getObject
(see line 52)
that either returns a matching object,
or NULL if no appropriate object matches the noun.
Function actorHere is used in commands give and ask,
but it could be useful for other commands as well.
So we define it in misc.c.
misc.h |
- extern OBJECT *actorHere(void);
- extern int listObjectsAtLocation(OBJECT *location);
|
misc.c |
- #include <stdio.h>
- #include "object.h"
- OBJECT *actorHere(void)
- {
- OBJECT *obj;
- for (obj = objs; obj < endOfObjs; obj++)
- {
- if (obj->location == player->location && obj == guard)
- {
- return obj;
- }
- }
- return NULL;
- }
- int listObjectsAtLocation(OBJECT *location)
- {
- int count = 0;
- OBJECT *obj;
- for (obj = objs; obj < endOfObjs; obj++)
- {
- if (obj != player && obj->location == location)
- {
- if (count++ == 0)
- {
- printf("You see:\n");
- }
- printf("%s\n", obj->description);
- }
- }
- return count;
- }
|
Explanation:
- Line 4-15:
function actorHere returns a pointer to the actor
present at the same location as the player,
or NULL if the player is alone.
In line 9 there is an exhaustive, hard-coded list of non-player characters
(up until now, just one: the guard).
In chapter 10, we will start using an attribute as a more elegant way
to distinguish actors from items and other non-actor objects.
The other modules (main.c, location.c, object.c)
remain unchanged,
you can see them in the previous chapters.
In chapter 12, you will see us add more commands.
But first, we will be making some improvements to command go.
Next chapter: 6. Passages