Contents

1. Introduction
2. The main loop
3. Locations
4. Objects
5. Inventory
6. Passages
7. Distance
8. North, east, south, west
9. Code generation
10. More attributes
11. Conditions
12. Open and close
13. The parser
14. Multiple nouns
15. Light and dark
16. Savegame
17. Test automation
18. Abbreviations
19. Conversations
20. Combat
21. Multi-player
22. Client-server
23. Database
24. Speech
25. JavaScript

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:

This makes certain common actions very easy to implement:

Action Typical
command
Example
Player moves from location to location go player->location = cave;
List items and actors present at a location look listObjectsAtLocation(cave);
Player gets an item get silver->location = player;
Player drops an item drop silver->location = player->location;
List the player’s inventory inventory listObjectsAtLocation(player);
Player gives an item to an actor give silver->location = guard;
Player receives an item from an actor ask silver->location = player;
List another actor’s inventory examine 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
  1. #include <stdbool.h>
  2. #include <stdio.h>
  3. #include <string.h>
  4. #include "location.h"
  5. #include "inventory.h"
  6. bool parseAndExecute(char *input)
  7. {
  8. char *verb = strtok(input, " \n");
  9. char *noun = strtok(NULL, " \n");
  10. if (verb != NULL)
  11. {
  12. if (strcmp(verb, "quit") == 0)
  13. {
  14. return false;
  15. }
  16. else if (strcmp(verb, "look") == 0)
  17. {
  18. executeLook(noun);
  19. }
  20. else if (strcmp(verb, "go") == 0)
  21. {
  22. executeGo(noun);
  23. }
  24. else if (strcmp(verb, "get") == 0)
  25. {
  26. executeGet(noun);
  27. }
  28. else if (strcmp(verb, "drop") == 0)
  29. {
  30. executeDrop(noun);
  31. }
  32. else if (strcmp(verb, "give") == 0)
  33. {
  34. executeGive(noun);
  35. }
  36. else if (strcmp(verb, "ask") == 0)
  37. {
  38. executeAsk(noun);
  39. }
  40. else if (strcmp(verb, "inventory") == 0)
  41. {
  42. executeInventory();
  43. }
  44. else
  45. {
  46. printf("I don't know how to '%s'.\n", verb);
  47. }
  48. }
  49. return true;
  50. }

Explanation:

The new commands are implemented by the following module.

inventory.h
  1. extern void executeGet(const char *noun);
  2. extern void executeDrop(const char *noun);
  3. extern void executeAsk(const char *noun);
  4. extern void executeGive(const char *noun);
  5. extern void executeInventory(void);
inventory.c
  1. #include <stdio.h>
  2. #include "object.h"
  3. #include "misc.h"
  4. #include "noun.h"
  5. #include "move.h"
  6. void executeGet(const char *noun)
  7. {
  8. OBJECT *obj = getVisible("what you want to get", noun);
  9. if (obj == NULL)
  10. {
  11. // already handled by getVisible
  12. }
  13. else if (obj == player)
  14. {
  15. printf("You should not be doing that to yourself.\n");
  16. }
  17. else if (obj->location == player)
  18. {
  19. printf("You already have %s.\n", obj->description);
  20. }
  21. else if (obj->location == guard)
  22. {
  23. printf("You should ask %s nicely.\n", obj->location->description);
  24. }
  25. else
  26. {
  27. moveObject(obj, player);
  28. }
  29. }
  30. void executeDrop(const char *noun)
  31. {
  32. moveObject(getPossession(player, "drop", noun), player->location);
  33. }
  34. void executeAsk(const char *noun)
  35. {
  36. moveObject(getPossession(actorHere(), "ask", noun), player);
  37. }
  38. void executeGive(const char *noun)
  39. {
  40. moveObject(getPossession(player, "give", noun), actorHere());
  41. }
  42. void executeInventory(void)
  43. {
  44. if (listObjectsAtLocation(player) == 0)
  45. {
  46. printf("You are empty-handed.\n");
  47. }
  48. }

Explanation:

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
  1. extern void moveObject(OBJECT *obj, OBJECT *to);
move.c
  1. #include <stdio.h>
  2. #include "object.h"
  3. static void describeMove(OBJECT *obj, OBJECT *to)
  4. {
  5. if (to == player->location)
  6. {
  7. printf("You drop %s.\n", obj->description);
  8. }
  9. else if (to != player)
  10. {
  11. printf(to == guard ? "You give %s to %s.\n" : "You put %s in %s.\n",
  12. obj->description, to->description);
  13. }
  14. else if (obj->location == player->location)
  15. {
  16. printf("You pick up %s.\n", obj->description);
  17. }
  18. else
  19. {
  20. printf("You get %s from %s.\n",
  21. obj->description, obj->location->description);
  22. }
  23. }
  24. void moveObject(OBJECT *obj, OBJECT *to)
  25. {
  26. if (obj == NULL)
  27. {
  28. // already handled by getVisible or getPossession
  29. }
  30. else if (to == NULL)
  31. {
  32. printf("There is nobody here to give that to.\n");
  33. }
  34. else if (obj->location == NULL)
  35. {
  36. printf("That is way too heavy.\n");
  37. }
  38. else
  39. {
  40. describeMove(obj, to);
  41. obj->location = to;
  42. }
  43. }

Explanation:

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
  1. extern OBJECT *getVisible(const char *intention, const char *noun);
  2. extern OBJECT *getPossession(OBJECT *from, const char *verb, const char *noun);
noun.c
  1. #include <stdbool.h>
  2. #include <stdio.h>
  3. #include <string.h>
  4. #include "object.h"
  5. static bool objectHasTag(OBJECT *obj, const char *noun)
  6. {
  7. return noun != NULL && *noun != '\0' && strcmp(noun, obj->tag) == 0;
  8. }
  9. static OBJECT *getObject(const char *noun)
  10. {
  11. OBJECT *obj, *res = NULL;
  12. for (obj = objs; obj < endOfObjs; obj++)
  13. {
  14. if (objectHasTag(obj, noun))
  15. {
  16. res = obj;
  17. }
  18. }
  19. return res;
  20. }
  21. OBJECT *getVisible(const char *intention, const char *noun)
  22. {
  23. OBJECT *obj = getObject(noun);
  24. if (obj == NULL)
  25. {
  26. printf("I don't understand %s.\n", intention);
  27. }
  28. else if (!(obj == player ||
  29. obj == player->location ||
  30. obj->location == player ||
  31. obj->location == player->location ||
  32. obj->location == NULL ||
  33. obj->location->location == player ||
  34. obj->location->location == player->location))
  35. {
  36. printf("You don't see any %s here.\n", noun);
  37. obj = NULL;
  38. }
  39. return obj;
  40. }
  41. OBJECT *getPossession(OBJECT *from, const char *verb, const char *noun)
  42. {
  43. OBJECT *obj = NULL;
  44. if (from == NULL)
  45. {
  46. printf("I don't understand who you want to %s.\n", verb);
  47. }
  48. else if ((obj = getObject(noun)) == NULL)
  49. {
  50. printf("I don't understand what you want to %s.\n", verb);
  51. }
  52. else if (obj == from)
  53. {
  54. printf("You should not be doing that to %s.\n", obj->description);
  55. obj = NULL;
  56. }
  57. else if (obj->location != from)
  58. {
  59. if (from == player)
  60. {
  61. printf("You are not holding any %s.\n", noun);
  62. }
  63. else
  64. {
  65. printf("There appears to be no %s you can get from %s.\n",
  66. noun, from->description);
  67. }
  68. obj = NULL;
  69. }
  70. return obj;
  71. }

Explanation:

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
  1. extern OBJECT *actorHere(void);
  2. extern int listObjectsAtLocation(OBJECT *location);
misc.c
  1. #include <stdio.h>
  2. #include "object.h"
  3. OBJECT *actorHere(void)
  4. {
  5. OBJECT *obj;
  6. for (obj = objs; obj < endOfObjs; obj++)
  7. {
  8. if (obj->location == player->location && obj == guard)
  9. {
  10. return obj;
  11. }
  12. }
  13. return NULL;
  14. }
  15. int listObjectsAtLocation(OBJECT *location)
  16. {
  17. int count = 0;
  18. OBJECT *obj;
  19. for (obj = objs; obj < endOfObjs; obj++)
  20. {
  21. if (obj != player && obj->location == location)
  22. {
  23. if (count++ == 0)
  24. {
  25. printf("You see:\n");
  26. }
  27. printf("%s\n", obj->description);
  28. }
  29. }
  30. return count;
  31. }

Explanation:

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.


⭳   Download source code 🌀   Run on Repl.it

Next chapter: 6. Passages