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

15. Light and dark

In many adventures, a lamp is a vital object. Without it, you cannot make it through the dark caverns that lie ahead.

The effect of being in the dark differs from game to game. Typically, it makes the command ‘look’ ineffective. In some games (e.g. Zork), darkness is deadly. In others, you can still make progress without a light source, provided you have drawn a detailed map of the dark area.

Our game will stay in between; being in the dark will not get you killed, but you will not be able to follow any passages either. An exception will be made for passages that lead back into the light; it seemed unfair to me to allow the player to run into a dark area, without an opportunity to get back to where he came from.

Alright; so first of all, in darkness you cannot see your surroundings.

location.c
  1. #include <stdbool.h>
  2. #include <stdio.h>
  3. #include "object.h"
  4. #include "misc.h"
  5. #include "match.h"
  6. #include "noun.h"
  7. bool executeLookAround(void)
  8. {
  9. if (isLit(player->location))
  10. {
  11. printf("You are in %s.\n", player->location->description);
  12. }
  13. else
  14. {
  15. printf("It is very dark in here.\n");
  16. }
  17. listObjectsAtLocation(player->location);
  18. return true;
  19. }
  20. bool executeLook(void)
  21. {
  22. OBJECT *obj = getVisible("what you want to look at", params[0]);
  23. switch (getDistance(player, obj))
  24. {
  25. case distHereContained:
  26. printf("Hard to see, try to get it first.\n");
  27. break;
  28. case distOverthere:
  29. printf("Too far away, move closer please.\n");
  30. break;
  31. case distNotHere:
  32. printf("You don't see any %s here.\n", params[0]);
  33. break;
  34. case distUnknownObject:
  35. // already handled by getVisible
  36. break;
  37. default:
  38. printf("%s\n", obj->details);
  39. listObjectsAtLocation(obj);
  40. }
  41. return true;
  42. }
  43. static void movePlayer(OBJECT *passage)
  44. {
  45. printf("%s\n", passage->textGo);
  46. if (passage->destination != NULL)
  47. {
  48. player->location = passage->destination;
  49. printf("\n");
  50. executeLookAround();
  51. }
  52. }
  53. bool executeGo(void)
  54. {
  55. OBJECT *obj = getVisible("where you want to go", params[0]);
  56. switch (getDistance(player, obj))
  57. {
  58. case distOverthere:
  59. movePlayer(getPassage(player->location, obj));
  60. break;
  61. case distNotHere:
  62. printf("You don't see any %s here.\n", params[0]);
  63. break;
  64. case distUnknownObject:
  65. // already handled by getVisible
  66. break;
  67. default:
  68. movePlayer(obj);
  69. }
  70. return true;
  71. }

Secondly, in darkness you cannot see or use nearby objects.

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. if (noun != NULL && *noun != '\0')
  9. {
  10. const char **tag;
  11. for (tag = obj->tags; *tag != NULL; tag++)
  12. {
  13. if (strcmp(*tag, noun) == 0) return true;
  14. }
  15. }
  16. return false;
  17. }
  18. static OBJECT ambiguousNoun;
  19. static OBJECT *getObject(const char *noun, OBJECT *from, DISTANCE maxDistance)
  20. {
  21. OBJECT *obj, *res = NULL;
  22. for (obj = objs; obj < endOfObjs; obj++)
  23. {
  24. if (objectHasTag(obj, noun) && getDistance(from, obj) <= maxDistance)
  25. {
  26. res = res == NULL ? obj : &ambiguousNoun;
  27. }
  28. }
  29. return res;
  30. }
  31. OBJECT *getVisible(const char *intention, const char *noun)
  32. {
  33. OBJECT *obj = getObject(noun, player, distOverthere);
  34. if (obj == NULL)
  35. {
  36. if (getObject(noun, player, distNotHere) == NULL)
  37. {
  38. printf("I don't understand %s.\n", intention);
  39. }
  40. else if (isLit(player->location))
  41. {
  42. printf("You don't see any %s here.\n", noun);
  43. }
  44. else
  45. {
  46. printf("It's too dark.\n");
  47. }
  48. }
  49. else if (obj == &ambiguousNoun)
  50. {
  51. printf("Please be specific about which %s you mean.\n", noun);
  52. obj = NULL;
  53. }
  54. return obj;
  55. }
  56. OBJECT *getPossession(OBJECT *from, const char *verb, const char *noun)
  57. {
  58. OBJECT *obj = NULL;
  59. if (from == NULL)
  60. {
  61. printf("I don't understand who you want to %s.\n", verb);
  62. }
  63. else if ((obj = getObject(noun, from, distHeldContained)) == NULL)
  64. {
  65. if (getObject(noun, player, distNotHere) == NULL)
  66. {
  67. printf("I don't understand what you want to %s.\n", verb);
  68. }
  69. else if (from == player)
  70. {
  71. printf("You are not holding any %s.\n", noun);
  72. }
  73. else
  74. {
  75. printf("There appears to be no %s you can get from %s.\n",
  76. noun, from->description);
  77. }
  78. }
  79. else if (obj == &ambiguousNoun)
  80. {
  81. printf("Please be specific about which %s you want to %s.\n",
  82. noun, verb);
  83. obj = NULL;
  84. }
  85. else if (obj == from)
  86. {
  87. printf("You should not be doing that to %s.\n", obj->description);
  88. obj = NULL;
  89. }
  90. return obj;
  91. }

In both cases, we used a function isLit. It is defined (and used some more) in misc.c.

misc.h
  1. typedef enum {
  2. distSelf,
  3. distHeld,
  4. distHeldContained,
  5. distLocation,
  6. distHere,
  7. distHereContained,
  8. distOverthere,
  9. distNotHere,
  10. distUnknownObject
  11. } DISTANCE;
  12. extern bool isHolding(OBJECT *container, OBJECT *obj);
  13. extern bool isLit(OBJECT *location);
  14. extern OBJECT *getPassage(OBJECT *from, OBJECT *to);
  15. extern DISTANCE getDistance(OBJECT *from, OBJECT *to);
  16. extern OBJECT *actorHere(void);
  17. extern int listObjectsAtLocation(OBJECT *location);
misc.c
  1. #include <stdbool.h>
  2. #include <stdio.h>
  3. #include "object.h"
  4. #include "misc.h"
  5. bool isHolding(OBJECT *container, OBJECT *obj)
  6. {
  7. return validObject(obj) && obj->location == container;
  8. }
  9. bool isLit(OBJECT *target)
  10. {
  11. OBJECT *obj;
  12. if (validObject(target))
  13. {
  14. if (target->light > 0)
  15. {
  16. return true;
  17. }
  18. for (obj = objs; obj < endOfObjs; obj++)
  19. {
  20. if (validObject(obj) && obj->light > 0 &&
  21. (isHolding(target, obj) || isHolding(target, obj->location)))
  22. {
  23. return true;
  24. }
  25. }
  26. }
  27. return false;
  28. }
  29. static bool isNoticeable(OBJECT *obj)
  30. {
  31. return obj->location == player ||
  32. isLit(obj) || isLit(obj->prospect) || isLit(player->location);
  33. }
  34. OBJECT *getPassage(OBJECT *from, OBJECT *to)
  35. {
  36. if (from != NULL && to != NULL)
  37. {
  38. OBJECT *obj;
  39. for (obj = objs; obj < endOfObjs; obj++)
  40. {
  41. if (isHolding(from, obj) && obj->prospect == to)
  42. {
  43. return obj;
  44. }
  45. }
  46. }
  47. return NULL;
  48. }
  49. DISTANCE getDistance(OBJECT *from, OBJECT *to)
  50. {
  51. return to == NULL ? distUnknownObject :
  52. !validObject(to) ? distNotHere :
  53. to == from ? distSelf :
  54. isHolding(from, to) ? distHeld :
  55. !isNoticeable(to) ? distNotHere :
  56. isHolding(to, from) ? distLocation :
  57. isHolding(from->location, to) ? distHere :
  58. isHolding(from, to->location) ? distHeldContained :
  59. isHolding(from->location, to->location) ? distHereContained :
  60. getPassage(from->location, to) != NULL ? distOverthere :
  61. distNotHere;
  62. }
  63. OBJECT *actorHere(void)
  64. {
  65. OBJECT *obj;
  66. for (obj = objs; obj < endOfObjs; obj++)
  67. {
  68. if (isHolding(player->location, obj) && obj != player &&
  69. isNoticeable(obj) && obj->health > 0)
  70. {
  71. return obj;
  72. }
  73. }
  74. return NULL;
  75. }
  76. int listObjectsAtLocation(OBJECT *location)
  77. {
  78. int count = 0;
  79. OBJECT *obj;
  80. for (obj = objs; obj < endOfObjs; obj++)
  81. {
  82. if (obj != player && isHolding(location, obj) && isNoticeable(obj))
  83. {
  84. if (count++ == 0)
  85. {
  86. printf("%s:\n", location->contents);
  87. }
  88. printf("%s\n", obj->description);
  89. }
  90. }
  91. return count;
  92. }

Explanation:

The implementation of isLit uses a new attribute light.

object.awk
  1. BEGIN {
  2. count = 0;
  3. obj = "";
  4. if (pass == "c2") {
  5. print "\nstatic bool alwaysTrue(void) { return true; }";
  6. print "\nOBJECT objs[] = {";
  7. }
  8. }
  9. /^- / {
  10. outputRecord(",");
  11. obj = $2;
  12. prop["condition"] = "alwaysTrue";
  13. prop["description"] = "NULL";
  14. prop["tags"] = "";
  15. prop["location"] = "NULL";
  16. prop["destination"] = "NULL";
  17. prop["prospect"] = "";
  18. prop["details"] = "\"You see nothing special.\"";
  19. prop["contents"] = "\"You see\"";
  20. prop["textGo"] = "\"You can't get much closer than this.\"";
  21. prop["weight"] = "99";
  22. prop["capacity"] = "0";
  23. prop["health"] = "0";
  24. prop["light"] = "0";
  25. prop["open"] = "cannotBeOpened";
  26. prop["close"] = "cannotBeClosed";
  27. prop["lock"] = "cannotBeLocked";
  28. prop["unlock"] = "cannotBeUnlocked";
  29. }
  30. obj && /^[ \t]+[a-z]/ {
  31. name = $1;
  32. $1 = "";
  33. if (name in prop) {
  34. prop[name] = $0;
  35. if (/^[ \t]*\{/) {
  36. prop[name] = name count;
  37. if (pass == "c1") print "static bool " prop[name] "(void) " $0;
  38. }
  39. }
  40. else if (pass == "c2") {
  41. print "#error \"" FILENAME " line " NR ": unknown attribute '" name "'\"";
  42. }
  43. }
  44. !obj && pass == (/^#include/ ? "c1" : "h") {
  45. print;
  46. }
  47. END {
  48. outputRecord("\n};");
  49. if (pass == "h") {
  50. print "\n#define endOfObjs\t(objs + " count ")";
  51. print "\n#define validObject(obj)\t" \
  52. "((obj) != NULL && (*(obj)->condition)())";
  53. }
  54. }
  55. function outputRecord(separator)
  56. {
  57. if (obj) {
  58. if (pass == "h") {
  59. print "#define " obj "\t(objs + " count ")";
  60. }
  61. else if (pass == "c1") {
  62. print "static const char *tags" count "[] = {" prop["tags"] ", NULL};";
  63. }
  64. else if (pass == "c2") {
  65. print "\t{\t/* " count " = " obj " */";
  66. print "\t\t" prop["condition"] ",";
  67. print "\t\t" prop["description"] ",";
  68. print "\t\ttags" count ",";
  69. print "\t\t" prop["location"] ",";
  70. print "\t\t" prop["destination"] ",";
  71. print "\t\t" prop[prop["prospect"] ? "prospect" : "destination"] ",";
  72. print "\t\t" prop["details"] ",";
  73. print "\t\t" prop["contents"] ",";
  74. print "\t\t" prop["textGo"] ",";
  75. print "\t\t" prop["weight"] ",";
  76. print "\t\t" prop["capacity"] ",";
  77. print "\t\t" prop["health"] ",";
  78. print "\t\t" prop["light"] ",";
  79. print "\t\t" prop["open"] ",";
  80. print "\t\t" prop["close"] ",";
  81. print "\t\t" prop["lock"] ",";
  82. print "\t\t" prop["unlock"];
  83. print "\t}" separator;
  84. delete prop;
  85. }
  86. count++;
  87. }
  88. }

By default, light is zero, meaning an object does not emit light. Every location in broad daylight (typically all but the underground ones) will be given a positive light value. It doesn’t really matter what value that is; as long as it’s not zero. We will also add a lamp the player can carry to traverse dark areas.

object.txt
  1. #include <stdbool.h>
  2. #include <stdio.h>
  3. #include "object.h"
  4. #include "toggle.h"
  5. typedef struct object {
  6. bool (*condition)(void);
  7. const char *description;
  8. const char **tags;
  9. struct object *location;
  10. struct object *destination;
  11. struct object *prospect;
  12. const char *details;
  13. const char *contents;
  14. const char *textGo;
  15. int weight;
  16. int capacity;
  17. int health;
  18. int light;
  19. void (*open)(void);
  20. void (*close)(void);
  21. void (*lock)(void);
  22. void (*unlock)(void);
  23. } OBJECT;
  24. extern OBJECT objs[];
  25. - field
  26. description "an open field"
  27. tags "field"
  28. details "The field is a nice and quiet place under a clear blue sky."
  29. capacity 9999
  30. light 100
  31. - cave
  32. description "a little cave"
  33. tags "cave"
  34. details "The cave is just a cold, damp, rocky chamber."
  35. capacity 9999
  36. - silver
  37. description "a silver coin"
  38. tags "silver", "coin", "silver coin"
  39. location field
  40. details "The coin has an eagle on the obverse."
  41. weight 1
  42. - gold
  43. description "a gold coin"
  44. tags "gold", "coin", "gold coin"
  45. location openBox
  46. details "The shiny coin seems to be a rare and priceless artefact."
  47. weight 1
  48. - guard
  49. description "a burly guard"
  50. tags "guard", "burly guard"
  51. location field
  52. details "The guard is a really big fellow."
  53. contents "He has"
  54. health 100
  55. capacity 20
  56. - player
  57. description "yourself"
  58. tags "yourself"
  59. location field
  60. details "You would need a mirror to look at yourself."
  61. contents "You have"
  62. health 100
  63. capacity 20
  64. - intoCave
  65. condition { return guard->health == 0 || silver->location == guard; }
  66. description "a cave entrance to the east"
  67. tags "east", "entrance"
  68. location field
  69. destination cave
  70. details "The entrance is just a narrow opening in a small outcrop."
  71. textGo "You walk into the cave."
  72. open isAlreadyOpen
  73. - intoCaveBlocked
  74. condition { return guard->health > 0 && silver->location != guard; }
  75. description "a cave entrance to the east"
  76. tags "east", "entrance"
  77. location field
  78. prospect cave
  79. details "The entrance is just a narrow opening in a small outcrop."
  80. textGo "The guard stops you from walking into the cave."
  81. open isAlreadyOpen
  82. - exitCave
  83. description "an exit to the west"
  84. tags "west", "exit"
  85. location cave
  86. destination field
  87. details "Sunlight pours in through an opening in the cave's wall."
  88. textGo "You walk out of the cave."
  89. open isAlreadyOpen
  90. - wallField
  91. description "dense forest all around"
  92. tags "west", "north", "south", "forest"
  93. location field
  94. details "The field is surrounded by trees and undergrowth."
  95. textGo "Dense forest is blocking the way."
  96. - wallCave
  97. description "solid rock all around"
  98. tags "east", "north", "rock"
  99. location cave
  100. details "Carved in stone is a secret password 'abccb'."
  101. textGo "Solid rock is blocking the way."
  102. - backroom
  103. description "a backroom"
  104. tags "backroom"
  105. details "The room is dusty and messy."
  106. capacity 9999
  107. - wallBackroom
  108. description "solid rock all around"
  109. tags "east", "west", "south", "rock"
  110. location backroom
  111. details "Trendy wallpaper covers the rock walls."
  112. textGo "Solid rock is blocking the way."
  113. - openDoorToBackroom
  114. description "an open door to the south"
  115. tags "south", "door", "doorway"
  116. destination backroom
  117. details "The door is open."
  118. textGo "You walk through the door into a backroom."
  119. open isAlreadyOpen
  120. close toggleDoorToBackroom
  121. - closedDoorToBackroom
  122. description "a closed door to the south"
  123. tags "south", "door", "doorway"
  124. location cave
  125. prospect backroom
  126. details "The door is closed."
  127. textGo "The door is closed."
  128. open toggleDoorToBackroom
  129. close isAlreadyClosed
  130. - openDoorToCave
  131. description "an open door to the north"
  132. tags "north", "door", "doorway"
  133. destination cave
  134. details "The door is open."
  135. textGo "You walk through the door into the cave."
  136. open isAlreadyOpen
  137. close toggleDoorToCave
  138. - closedDoorToCave
  139. description "a closed door to the north"
  140. tags "north", "door", "doorway"
  141. location backroom
  142. prospect cave
  143. details "The door is closed."
  144. textGo "The door is closed."
  145. open toggleDoorToCave
  146. close isAlreadyClosed
  147. - openBox
  148. description "a wooden box"
  149. tags "box", "wooden box"
  150. details "The box is open."
  151. weight 5
  152. capacity 10
  153. open isAlreadyOpen
  154. close toggleBox
  155. lock isStillOpen
  156. unlock isAlreadyOpen
  157. - closedBox
  158. description "a wooden box"
  159. tags "box", "wooden box"
  160. details "The box is closed."
  161. weight 5
  162. open toggleBox
  163. close isAlreadyClosed
  164. lock toggleBoxLock
  165. unlock isAlreadyUnlocked
  166. - lockedBox
  167. description "a wooden box"
  168. tags "box", "wooden box"
  169. location backroom
  170. details "The box is closed."
  171. weight 5
  172. open isStillLocked
  173. close isAlreadyClosed
  174. lock isAlreadyLocked
  175. unlock toggleBoxLock
  176. - keyForBox
  177. description "a tiny key"
  178. tags "key", "tiny key"
  179. location cave
  180. details "The key is really small and shiny."
  181. weight 1
  182. - lampOff
  183. description "a lamp"
  184. tags "lamp"
  185. location field
  186. details "The lamp is off."
  187. weight 5
  188. - lampOn
  189. description "a lamp"
  190. tags "lamp"
  191. details "The lamp is on."
  192. weight 5
  193. light 100

Explanation:

We will add some commands we can use to turn the lamp on and off.

parsexec.c
  1. #include <ctype.h>
  2. #include <stdbool.h>
  3. #include <stdio.h>
  4. #include "object.h"
  5. #include "misc.h"
  6. #include "match.h"
  7. #include "location.h"
  8. #include "inventory.h"
  9. #include "inventory2.h"
  10. #include "openclose.h"
  11. #include "onoff.h"
  12. typedef struct
  13. {
  14. const char *pattern;
  15. bool (*function)(void);
  16. } COMMAND;
  17. static bool executeQuit(void)
  18. {
  19. return false;
  20. }
  21. static bool executeNoMatch(void)
  22. {
  23. const char *src = *params;
  24. int len;
  25. for (len = 0; src[len] != '\0' && !isspace(src[len]); len++);
  26. if (len > 0) printf("I don't know how to '%.*s'.\n", len, src);
  27. return true;
  28. }
  29. bool parseAndExecute(const char *input)
  30. {
  31. static const COMMAND commands[] =
  32. {
  33. { "quit" , executeQuit },
  34. { "look" , executeLookAround },
  35. { "look around" , executeLookAround },
  36. { "look at A" , executeLook },
  37. { "look A" , executeLook },
  38. { "examine A" , executeLook },
  39. { "go to A" , executeGo },
  40. { "go A" , executeGo },
  41. { "get A from B" , executeGetFrom },
  42. { "get A" , executeGet },
  43. { "put A in B" , executePutIn },
  44. { "drop A in B" , executePutIn },
  45. { "drop A" , executeDrop },
  46. { "ask A from B" , executeAskFrom },
  47. { "ask A" , executeAsk },
  48. { "give A to B" , executeGiveTo },
  49. { "give A" , executeGive },
  50. { "inventory" , executeInventory },
  51. { "open A" , executeOpen },
  52. { "close A" , executeClose },
  53. { "lock A" , executeLock },
  54. { "unlock A" , executeUnlock },
  55. { "turn on A" , executeTurnOn },
  56. { "turn off A" , executeTurnOff },
  57. { "turn A on" , executeTurnOn },
  58. { "turn A off" , executeTurnOff },
  59. { "A" , executeNoMatch }
  60. };
  61. const COMMAND *cmd;
  62. for (cmd = commands; !matchCommand(input, cmd->pattern); cmd++);
  63. return (*cmd->function)();
  64. }

Below is the implementation of these commands.

Sample output
Welcome to Little Cave Adventure.
You are in an open field.
You see:
a silver coin
a burly guard
a cave entrance to the east
dense forest all around
a lamp

--> get lamp
You pick up a lamp.

--> get coin
You pick up a silver coin.

--> give coin
You give a silver coin to a burly guard.

--> go cave
You walk into the cave.

It is very dark in here.
You see:
an exit to the west

--> get key
It's too dark.

--> open door
It's too dark.

--> go south
It's too dark.

--> turn lamp on
You turn on a lamp.

You are in a little cave.
You see:
an exit to the west
solid rock all around
a closed door to the south
a tiny key

--> look around
You are in a little cave.
You see:
an exit to the west
solid rock all around
a closed door to the south
a tiny key

--> get key
You pick up a tiny key.

--> open door
You open a closed door to the south.

--> go south
You walk through the door into a backroom.

You are in a backroom.
You see:
solid rock all around
an open door to the north
a wooden box

--> quit

Bye!
onoff.h
  1. extern bool executeTurnOn(void);
  2. extern bool executeTurnOff(void);
onoff.c
  1. #include <stdbool.h>
  2. #include <stdio.h>
  3. #include "object.h"
  4. #include "match.h"
  5. #include "reach.h"
  6. #include "toggle.h"
  7. bool executeTurnOn(void)
  8. {
  9. OBJECT *obj = reachableObject("what you want to turn on", params[0]);
  10. if (obj != NULL)
  11. {
  12. if (obj == lampOff)
  13. {
  14. toggleLamp();
  15. }
  16. else
  17. {
  18. printf(obj == lampOn ? "The lamp is already on.\n"
  19. : "You cannot turn that on.\n");
  20. }
  21. }
  22. return true;
  23. }
  24. bool executeTurnOff(void)
  25. {
  26. OBJECT *obj = reachableObject("what you want to turn off", params[0]);
  27. if (obj != NULL)
  28. {
  29. if (obj == lampOn)
  30. {
  31. toggleLamp();
  32. }
  33. else
  34. {
  35. printf(obj == lampOff ? "The lamp is already off.\n"
  36. : "You cannot turn that off.\n");
  37. }
  38. }
  39. return true;
  40. }

To turn the lamp on and off, we will use the same trick we used to open and close the door and the box (see chapter 12).

toggle.h
  1. extern void cannotBeOpened(void);
  2. extern void cannotBeClosed(void);
  3. extern void cannotBeLocked(void);
  4. extern void cannotBeUnlocked(void);
  5. extern void isAlreadyOpen(void);
  6. extern void isAlreadyClosed(void);
  7. extern void isAlreadyLocked(void);
  8. extern void isAlreadyUnlocked(void);
  9. extern void isStillOpen(void);
  10. extern void isStillLocked(void);
  11. extern void toggleDoorToBackroom(void);
  12. extern void toggleDoorToCave(void);
  13. extern void toggleBox(void);
  14. extern void toggleBoxLock(void);
  15. extern void toggleLamp(void);
toggle.c
  1. #include <stdbool.h>
  2. #include <stdio.h>
  3. #include "object.h"
  4. #include "misc.h"
  5. #include "location.h"
  6. static void swapLocations(const char *verb1, OBJECT *obj1,
  7. const char *verb2, OBJECT *obj2)
  8. {
  9. OBJECT *tmp = obj1->location;
  10. OBJECT *obj = tmp != NULL ? obj1 : obj2;
  11. const char *verb = tmp != NULL ? verb1 : verb2;
  12. obj1->location = obj2->location;
  13. obj2->location = tmp;
  14. if (verb != NULL) printf("You %s %s.\n", verb, obj->description);
  15. }
  16. void cannotBeOpened(void) { printf("That cannot be opened.\n"); }
  17. void cannotBeClosed(void) { printf("That cannot be closed.\n"); }
  18. void cannotBeLocked(void) { printf("That cannot be locked.\n"); }
  19. void cannotBeUnlocked(void) { printf("That cannot be unlocked.\n"); }
  20. void isAlreadyOpen(void) { printf("That is already open.\n"); }
  21. void isAlreadyClosed(void) { printf("That is already closed.\n"); }
  22. void isAlreadyLocked(void) { printf("That is already locked.\n"); }
  23. void isAlreadyUnlocked(void) { printf("That is already unlocked.\n"); }
  24. void isStillOpen(void) { printf("That is still open.\n"); }
  25. void isStillLocked(void) { printf("That is locked.\n"); }
  26. void toggleDoorToBackroom(void)
  27. {
  28. swapLocations(NULL, openDoorToCave, NULL, closedDoorToCave);
  29. swapLocations("close", openDoorToBackroom, "open", closedDoorToBackroom);
  30. }
  31. void toggleDoorToCave(void)
  32. {
  33. swapLocations(NULL, openDoorToBackroom, NULL, closedDoorToBackroom);
  34. swapLocations("close", openDoorToCave, "open", closedDoorToCave);
  35. }
  36. void toggleBox(void)
  37. {
  38. swapLocations("close", openBox, "open", closedBox);
  39. }
  40. void toggleBoxLock(void)
  41. {
  42. if (keyForBox->location == player)
  43. {
  44. swapLocations("lock", closedBox, "unlock", lockedBox);
  45. }
  46. else
  47. {
  48. printf("You don't have a key.\n");
  49. }
  50. }
  51. void toggleLamp(void)
  52. {
  53. bool oldLit = isLit(player->location);
  54. swapLocations("turn off", lampOn, "turn on", lampOff);
  55. if (oldLit != isLit(player->location))
  56. {
  57. printf("\n");
  58. executeLookAround();
  59. }
  60. }

Explanation:

Finally, we will mark the dark locations in our generated map.

map.awk
  1. BEGIN { print "digraph map {\n\tnode [style=filled]"; }
  2. /^- / { outputEdges(); obj = $2; delete a; }
  3. /^[ \t]/ { a[$1] = $2; }
  4. END { outputEdges(); outputNodes(); print "}"; }
  5. function outputEdges()
  6. {
  7. color[obj] = a["light"] ? "white" : "grey";
  8. outputEdge(a["location"], a["destination"], "");
  9. outputEdge(a["location"], a["prospect"], " [style=dashed]");
  10. }
  11. function outputEdge(from, to, style)
  12. {
  13. if (to)
  14. {
  15. nodes[to] = 1;
  16. if (from)
  17. {
  18. nodes[from] = 1;
  19. print "\t" from " -> " to style;
  20. }
  21. }
  22. }
  23. function outputNodes()
  24. {
  25. for (n in nodes) print "\t" n " [fillcolor=" color[n] "]";
  26. }

Players, please be careful not to turn off the lamp and drop it. In the dark, you will never be able to find it back. You will be stuck! Fortunately, the next chapter will provide a way to undo clumsy actions.


⭳   Download source code 🌀   Run on Repl.it

Next chapter: 16. Savegame