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

6. Passages

It’s time to draw a map - and implement it!

The best tools for drawing a map will always be: a pencil and a piece of paper. A basic map consists of locations (the rectangles), connected by passages (the arrows). We already created locations in chapter 3, now we will start adding the passages.

Basic map with passages

In the virtual world, a ‘passage’ may be anything connecting two locations: a road, a door, a stretch of sand in a desert. Basically, a passage has the following properties:

Considering these properties, it may not come as a surprise that the struct object defined in chapter 4 is very suitable to store a passage. In fact, a passage is not that different from an item or actor; it is present at a certain location as a ‘visible exit’ (this location is the starting point). It just behaves differently to certain commands. In particular the command ‘go’: applied to a passage, go will change the player’s location. The target location (the ‘destination’) can be stored in the struct object as a new attribute.

struct object { const char *description; const char *tag; struct object *location; struct object *destination; };

Notes:

So we expand the array of objects:

Sample output
Welcome to Little Cave Adventure.
You are in an open field.
You see:
a silver coin
a burly guard
a cave entrance

--> go entrance
OK.
You are in a little cave.
You see:
a gold coin
an exit

--> go exit
OK.
You are in an open field.
You see:
a silver coin
a burly guard
a cave entrance

--> go cave
OK.
You are in a little cave.
You see:
a gold coin
an exit

--> quit

Bye!
object.h
  1. typedef struct object {
  2. const char *description;
  3. const char *tag;
  4. struct object *location;
  5. struct object *destination;
  6. } OBJECT;
  7. extern OBJECT objs[];
  8. #define field (objs + 0)
  9. #define cave (objs + 1)
  10. #define silver (objs + 2)
  11. #define gold (objs + 3)
  12. #define guard (objs + 4)
  13. #define player (objs + 5)
  14. #define intoCave (objs + 6)
  15. #define exitCave (objs + 7)
  16. #define endOfObjs (objs + 8)
object.c
  1. #include <stdio.h>
  2. #include "object.h"
  3. OBJECT objs[] = {
  4. {"an open field" , "field" , NULL , NULL },
  5. {"a little cave" , "cave" , NULL , NULL },
  6. {"a silver coin" , "silver" , field, NULL },
  7. {"a gold coin" , "gold" , cave , NULL },
  8. {"a burly guard" , "guard" , field, NULL },
  9. {"yourself" , "yourself", field, NULL },
  10. {"a cave entrance", "entrance", field, cave },
  11. {"an exit" , "exit" , cave , field }
  12. };

We will add a little helper function to misc.c to determine whether a passage exists between two given locations.

misc.h
  1. extern OBJECT *getPassage(OBJECT *from, OBJECT *to);
  2. extern OBJECT *actorHere(void);
  3. extern int listObjectsAtLocation(OBJECT *location);
misc.c
  1. #include <stdio.h>
  2. #include "object.h"
  3. OBJECT *getPassage(OBJECT *from, OBJECT *to)
  4. {
  5. if (from != NULL && to != NULL)
  6. {
  7. OBJECT *obj;
  8. for (obj = objs; obj < endOfObjs; obj++)
  9. {
  10. if (obj->location == from && obj->destination == to)
  11. {
  12. return obj;
  13. }
  14. }
  15. }
  16. return NULL;
  17. }
  18. OBJECT *actorHere(void)
  19. {
  20. OBJECT *obj;
  21. for (obj = objs; obj < endOfObjs; obj++)
  22. {
  23. if (obj->location == player->location && obj == guard)
  24. {
  25. return obj;
  26. }
  27. }
  28. return NULL;
  29. }
  30. int listObjectsAtLocation(OBJECT *location)
  31. {
  32. int count = 0;
  33. OBJECT *obj;
  34. for (obj = objs; obj < endOfObjs; obj++)
  35. {
  36. if (obj != player && obj->location == location)
  37. {
  38. if (count++ == 0)
  39. {
  40. printf("You see:\n");
  41. }
  42. printf("%s\n", obj->description);
  43. }
  44. }
  45. return count;
  46. }

Explanation:

We will use the new function getPassage in the implementation of command ‘go’ to determine whether or not a passage exists that can take the player to the desired location.

location.h
  1. extern void executeLook(const char *noun);
  2. extern void executeGo(const char *noun);
location.c
  1. #include <stdio.h>
  2. #include <string.h>
  3. #include "object.h"
  4. #include "misc.h"
  5. #include "noun.h"
  6. void executeLook(const char *noun)
  7. {
  8. if (noun != NULL && strcmp(noun, "around") == 0)
  9. {
  10. printf("You are in %s.\n", player->location->description);
  11. listObjectsAtLocation(player->location);
  12. }
  13. else
  14. {
  15. printf("I don't understand what you want to see.\n");
  16. }
  17. }
  18. void executeGo(const char *noun)
  19. {
  20. OBJECT *obj = getVisible("where you want to go", noun);
  21. if (obj == NULL)
  22. {
  23. // already handled by getVisible
  24. }
  25. else if (getPassage(player->location, obj) != NULL)
  26. {
  27. printf("OK.\n");
  28. player->location = obj;
  29. executeLook("around");
  30. }
  31. else if (obj->location != player->location)
  32. {
  33. printf("You don't see any %s here.\n", noun);
  34. }
  35. else if (obj->destination != NULL)
  36. {
  37. printf("OK.\n");
  38. player->location = obj->destination;
  39. executeLook("around");
  40. }
  41. else
  42. {
  43. printf("You can't get much closer than this.\n");
  44. }
  45. }

Explanation:

We will also use the new function getPassage to determine whether a certain location is visible from where the player is standing. Locations that are not connected to the current location by a passage, are not considered visible.

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

Explanation:

The other modules (main.c, parsexec.c, inventory.c, move.c) remain unchanged, you can see them in the previous chapters.

Obviously, the map in this sample is trivial: there are only two locations, and they are connected in both directions. A third location will be added in chapter 12. Homework assignment: draw a more elaborate map and turn it into a list of objects (locations and passages).


⭳   Download source code 🌀   Run on Repl.it

Next chapter: 7. Distance