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

How to program a text adventure in C

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

Next, we have to make some changes to function executeGo.

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

Explanation:

Function executeGo uses a separate function to check if two locations are connected by a passage. Because such a function will prove to be useful in the next chapter as well, I made it a general function getPassageTo as part of misc.c.

misc.h
1 2 3 4 extern OBJECT *getPassageTo(OBJECT *targetLocation); extern OBJECT *parseObject(const char *noun); extern OBJECT *personHere(void); extern int listObjectsAtLocation(OBJECT *location);
misc.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 #include <stdio.h> #include <string.h> #include "object.h" #include "misc.h" OBJECT *getPassageTo(OBJECT *targetLocation) { OBJECT *obj; for (obj = objs; obj < endOfObjs; obj++) { if (obj->location == player->location && obj->destination == targetLocation) { return obj; } } return NULL; } OBJECT *parseObject(const char *noun) { OBJECT *obj, *found = NULL; for (obj = objs; obj < endOfObjs; obj++) { if (noun != NULL && strcmp(noun, obj->tag) == 0) { found = obj; } } return found; } OBJECT *personHere(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:

The other modules (main.c and inventory.*) remain unchanged, you can see them in the previous chapter.

Obviously, the map in this sample is trivial: there are only two locations, and they are connected in both directions. I will leave it up to the reader to draw a more complicated map and turn it into a list of objects (locations and passages).

Many responses in the game are still kind of dull. For example, “go guard” returns “You can't go there.” Exactly the kind of output I criticized in the previous chapter! This is something we will be working on in the next chapter.


Next chapter: 7. Distance