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

20. Combat

Since the early classics were influenced by fantasy, it is not surprising that many text-based games adopted elements from RPG games like Dungeons & Dragons.

Multi-player games thrive on role play and combat; you see that in every MUD. Single-player games on the other hand, typically focus on puzzle solving. The boundaries are not always that sharp. In the early days, before the rise of the internet, there were plenty of single-player games that featured combat, though often in a simplified form.

It is easy enough to implement a ‘kill’ command that will dispose of any actors standing in your way.

guard->health--; /* do this often enough and he's dead */

But of course there is a lot more to it. Some weapons may be more effective than others. Some opponents may be tougher than others. Both the player and non-player characters stand a better chance in combat when being healthy, skilled and well-equiped. A lot can be learned from tabletop role-playing games, where statistics determine the chances of survival. Each stat can be implemented as an attribute in our ‘object’ structure; this was discussed in chapter 10. A combat-related command should use these stats in a ‘damage formula’ that determines the impact of the command on the actors involved.

guard->health -= calculate_damage(player, guard);

Random

Traditionally, some uncertainty is built into a damage formula. Tabletop games have dice, computer games have a random number generator. Please note that ‘randomness’ may cause trouble with the ‘roll-forward’ savegame technique proposed in chapter 16. Roll-forward demands repeatability. When saving a game, you expect the exact same game state when restoring. But different random numbers might completely change the outcome of battles that took place prior to saving.

Fortunately, computers typically use pseudorandom number generators. Reproducibility is one of their big advantages. Just make sure to seed the PRNG with the same fixed number at the start of every game. If you prefer to have a uniquely random game experience for every new game session (though I doubt that will be relevant for a text adventure, where a player is typically dedicated to a single ongoing session), then at least make sure the seed is stored as part of the savegame.

This applies not only to combat, but to everything that is random, for example objects positioned randomly on the map, or NPCs moving randomly between locations.

Real-time vs turn based

When implementing combat in a text adventure, you will have to choose between real-time and turn-based. In multi-player games, turn-based only makes sense when playing offline (as in play-by-mail). Online players do not want to be kept waiting for other players to respond, which is why a MUD is typically played in real time. This brings a few coding challenges; in particular synchronization. It’s also important to implement some kind of cooldown, to prevent things like combat to turn into a speed typing contest.

Single-player games are typically turn-based. Computers respond faster than humans, so a human player will not be frustrated by the time it takes for a non-player character to respond to the player’s actions. Turn-based has the advantage of not rushing players, giving them all the time in the world to think about their next move, while at the same time offering instant feedback at every attempt from the player to progress through the game.

Turn-based does not mean there is no notion of time; it’s just not real time. Every command from the user is a ‘turn’ in the game. During that turn, some ‘game time’ passes, which gives other (non-player) characters an equal opportunity to take action. For example, after the player has taken a swing at an orc, it’s the orc’s turn to retaliate.

In its simplest form, every command represents an equal time slice. But it is also possible to make certain commands ‘consume’ more time than others, giving the rest of the world more time to do their ‘thing’ in response. For example, the player may be given the opportunity to travel from one town to another with a single ‘go’ command, but that may cost the player so much game time, that night will fall halfway through, bringing out nasty night crawlers. ‘Long’ commands may also consume more resources, like food, water, physical strength, and fuel for a lamp.

At the very least, I would recommend to let ‘supportive’ commands, for example ‘help’, ‘save’, ‘load’, consume zero game time. These commands are not part of the storyline; it would be unfair to penalize the player for using them. The same thing goes for typos and other input that is rejected by the parser; this is supposed to be an adventure, not a spelling bee.

The orc looks very angry. --> hit orc You hit the orc on the head. The orc is dazed, and for a moment, is defenseless. --> stabbb orc I don't know how to 'stabbb'. The orc recovers and strikes back. You are dead. --> *** rage quit ***

Please note that in battle, a return action is not necessarily implied by the player’s last command. Once enraged, the orc is likely to keep responding with violence, regardless of what the player’s subsequent commands will be.

--> hit orc You hit the orc; orc loses 10 HP. Orc strikes back; you lose 10 HP. --> give flowers to orc Orc accepts your gift and makes a flower basket out of it. --> mood swings???

Therefore, it is best to implement all actions from ‘the rest of the world’ not as part of any user command, but as a separate step in the main loop. Remember the main loop from chapter 2? We will just make an additional function call there.

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
a wooden club
a dagger

--> get club
You pick up a wooden club.

--> get dagger
You pick up a dagger.

--> get lamp
You pick up a lamp.

--> attack
You hit a burly guard with a dagger.
You are hit by a burly guard with bare hands.

--> attack with club
You hit a burly guard with a wooden club.

--> drop dagger
You drop a dagger.
You see a burly guard pick up a dagger.

--> attack guard
You hit a burly guard with a wooden club.
You are hit by a burly guard with a dagger.

--> attack with dagger
You are not holding any dagger.

--> attack with lamp
You try to hit a burly guard with a lamp, but you miss.

--> wait
Some time passes...
You are hit by a burly guard with a dagger.

--> quit

Bye!
main.c
  1. #include <stdbool.h>
  2. #include <stdio.h>
  3. #include <string.h>
  4. #include "expand.h"
  5. #include "parsexec.h"
  6. #include "turn.h"
  7. static char input[100] = "look around";
  8. static bool getFromFP(FILE *fp)
  9. {
  10. bool ok = fgets(input, sizeof input, fp) != NULL;
  11. if (ok) input[strcspn(input, "\n")] = '\0';
  12. return ok;
  13. }
  14. static bool getInput(const char *filename)
  15. {
  16. static FILE *fp = NULL;
  17. bool ok;
  18. if (fp == NULL)
  19. {
  20. if (filename != NULL) fp = fopen(filename, "rt");
  21. if (fp == NULL) fp = stdin;
  22. }
  23. else if (fp == stdin && filename != NULL)
  24. {
  25. FILE *out = fopen(filename, "at");
  26. if (out != NULL)
  27. {
  28. fprintf(out, "%s\n", input);
  29. fclose(out);
  30. }
  31. }
  32. printf("\n--> ");
  33. ok = getFromFP(fp);
  34. if (fp != stdin)
  35. {
  36. if (ok)
  37. {
  38. printf("%s\n", input);
  39. }
  40. else
  41. {
  42. fclose(fp);
  43. ok = getFromFP(fp = stdin);
  44. }
  45. }
  46. return ok;
  47. }
  48. static bool processInput(char *ptr, int size)
  49. {
  50. return turn(parseAndExecute(expand(ptr, size)));
  51. }
  52. int main(int argc, char *argv[])
  53. {
  54. (void)argc;
  55. printf("Welcome to Little Cave Adventure.\n");
  56. while (processInput(input, sizeof input) && getInput(argv[1]));
  57. printf("\nBye!\n");
  58. return 0;
  59. }

Explanation:

Function ‘turn’ implements everything that is going on around the player. In a multi-player game, this would include activity of other human players. A single-player game only has non-player characters, but of course there could be many other processes. A typical example is a lamp that is slowly dying. Every turn with the lamp being on, it will consume oil or battery power. Once everything is consumed, the light will dim. Other examples are forces of nature: a storm, a flood, a volcanic eruption.

turn.h
  1. extern bool turn(int time);
turn.c
  1. #include <stdbool.h>
  2. #include <stdio.h>
  3. #include "object.h"
  4. #include "misc.h"
  5. #include "location.h"
  6. #include "damage.h"
  7. static OBJECT *findBestWeaponAround(OBJECT *actor, OBJECT *weapon)
  8. {
  9. OBJECT *obj;
  10. for (obj = objs; obj < endOfObjs; obj++)
  11. {
  12. if (isHolding(actor->location, obj) && obj->impact < weapon->impact)
  13. {
  14. weapon = obj;
  15. }
  16. }
  17. return weapon;
  18. }
  19. static void actorTakingTurn(OBJECT *actor)
  20. {
  21. if (isHolding(player->location, actor) && actor->trust < 0)
  22. {
  23. OBJECT *weapon = getOptimalWeapon(actor);
  24. OBJECT *best = findBestWeaponAround(actor, weapon);
  25. if (weapon == best)
  26. {
  27. dealDamage(actor, weapon, player);
  28. }
  29. else
  30. {
  31. best->location = actor;
  32. printf("You see %s pick up %s.\n",
  33. actor->description, best->description);
  34. }
  35. }
  36. }
  37. static void depleteLight(OBJECT *obj, int time)
  38. {
  39. if ((obj->light -= time) <= 0 &&
  40. (isHolding(player, obj) || isHolding(player->location, obj)))
  41. {
  42. printf("You see %s go out.\n", obj->description);
  43. }
  44. }
  45. bool turn(int time)
  46. {
  47. if (time > 0)
  48. {
  49. bool originallyLit = isLit(player->location);
  50. OBJECT *obj, *originalLocation = player->location;
  51. for (obj = objs; obj < endOfObjs; obj++)
  52. {
  53. if (validObject(obj) && obj->location != NULL)
  54. {
  55. if (obj->health > 0) actorTakingTurn(obj);
  56. if (obj->light > 0) depleteLight(obj, time);
  57. }
  58. }
  59. if (player->health <= 0)
  60. {
  61. printf("You rise up to a more peaceful place...\n");
  62. player->location = heaven;
  63. player->health = 100;
  64. for (obj = objs; obj < endOfObjs; obj++)
  65. {
  66. if (obj->location == player) obj->location = field;
  67. }
  68. }
  69. if (originallyLit != isLit(player->location) ||
  70. originalLocation != player->location)
  71. {
  72. executeLookAround();
  73. }
  74. }
  75. return time >= 0;
  76. }

Explanation:

Functions dealDamage and getOptimalWeapon are implemented in the following module. Separating them from turn.c makes sense, because we will need the same functionality once again as we implement an ‘attack’ command by the end of this chapter.

damage.h
  1. extern void dealDamage(OBJECT *attacker, OBJECT *weapon, OBJECT *victim);
  2. extern OBJECT *getOptimalWeapon(OBJECT *attacker);
damage.c
  1. #include <stdbool.h>
  2. #include <stdlib.h>
  3. #include <stdio.h>
  4. #include "object.h"
  5. #include "misc.h"
  6. static void describeAttack(OBJECT *attacker, OBJECT *victim, OBJECT *weapon)
  7. {
  8. const char *weaponDescription = weapon == attacker ? "bare hands"
  9. : weapon->description;
  10. if (attacker == player)
  11. {
  12. printf("You hit %s with %s.\n", victim->description, weaponDescription);
  13. }
  14. else if (victim == player)
  15. {
  16. printf("You are hit by %s with %s.\n",
  17. attacker->description, weaponDescription);
  18. }
  19. else
  20. {
  21. printf("You see %s hit %s with %s.\n",
  22. attacker->description, victim->description, weaponDescription);
  23. }
  24. }
  25. static void describeDeath(OBJECT *victim)
  26. {
  27. if (victim == player)
  28. {
  29. printf("You die.\n");
  30. }
  31. else
  32. {
  33. printf("You see %s die.\n", victim->description);
  34. }
  35. }
  36. void dealDamage(OBJECT *attacker, OBJECT *weapon, OBJECT *victim)
  37. {
  38. int damage = (rand() % 6) * weapon->impact * attacker->health / 100;
  39. if (damage < 0)
  40. {
  41. if (victim->health > 0)
  42. {
  43. describeAttack(attacker, victim, weapon);
  44. victim->health += damage;
  45. if (victim->health <= 0)
  46. {
  47. victim->health = 0;
  48. describeDeath(victim);
  49. }
  50. if (attacker == player)
  51. {
  52. victim->trust--;
  53. }
  54. }
  55. else if (attacker == player)
  56. {
  57. printf("That will have little effect; %s is already dead.\n",
  58. victim->description);
  59. }
  60. }
  61. else if (attacker == player)
  62. {
  63. printf("You try to hit %s with %s, but you miss.\n",
  64. victim->description,
  65. weapon == attacker ? "bare hands" : weapon->description);
  66. }
  67. }
  68. OBJECT *getOptimalWeapon(OBJECT *attacker)
  69. {
  70. OBJECT *obj, *weapon = attacker;
  71. for (obj = objs; obj < endOfObjs; obj++)
  72. {
  73. if (isHolding(attacker, obj) && obj->impact < weapon->impact)
  74. {
  75. weapon = obj;
  76. }
  77. }
  78. return weapon;
  79. }

Explanation:

Now that we have given other characters the means to attack the player, it’s time to let the player return the favor!

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. #include "talk.h"
  13. #include "attack.h"
  14. typedef struct
  15. {
  16. const char *pattern;
  17. int (*function)(void);
  18. } COMMAND;
  19. static int executeQuit(void)
  20. {
  21. return -1;
  22. }
  23. static int executeNoMatch(void)
  24. {
  25. const char *src = *params;
  26. int len;
  27. for (len = 0; src[len] != '\0' && !isspace(src[len]); len++);
  28. if (len > 0) printf("I don't know how to '%.*s'.\n", len, src);
  29. return 0;
  30. }
  31. static int executeWait(void)
  32. {
  33. printf("Some time passes...\n");
  34. return 1;
  35. }
  36. int parseAndExecute(const char *input)
  37. {
  38. static const COMMAND commands[] =
  39. {
  40. { "quit" , executeQuit },
  41. { "look" , executeLookAround },
  42. { "look around" , executeLookAround },
  43. { "look at A" , executeLook },
  44. { "look A" , executeLook },
  45. { "examine A" , executeLook },
  46. { "go to A" , executeGo },
  47. { "go A" , executeGo },
  48. { "get A from B" , executeGetFrom },
  49. { "get A" , executeGet },
  50. { "put A in B" , executePutIn },
  51. { "drop A in B" , executePutIn },
  52. { "drop A" , executeDrop },
  53. { "ask A from B" , executeAskFrom },
  54. { "ask A" , executeAsk },
  55. { "give A to B" , executeGiveTo },
  56. { "give A" , executeGive },
  57. { "inventory" , executeInventory },
  58. { "open A" , executeOpen },
  59. { "close A" , executeClose },
  60. { "lock A" , executeLock },
  61. { "unlock A" , executeUnlock },
  62. { "turn on A" , executeTurnOn },
  63. { "turn off A" , executeTurnOff },
  64. { "turn A on" , executeTurnOn },
  65. { "turn A off" , executeTurnOff },
  66. { "talk with B about A" , executeTalkTo },
  67. { "talk about A with B" , executeTalkTo },
  68. { "talk about A" , executeTalk },
  69. { "talk A" , executeTalk },
  70. { "attack with B" , executeAttack },
  71. { "attack A with B" , executeAttack },
  72. { "attack A" , executeAttack },
  73. { "wait" , executeWait },
  74. { "A" , executeNoMatch }
  75. };
  76. const COMMAND *cmd;
  77. for (cmd = commands; !matchCommand(input, cmd->pattern); cmd++);
  78. return (*cmd->function)();
  79. }

Explanation:

It is time to implement the command ‘attack’.

attack.h
  1. extern int executeAttack(void);
attack.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. #include "reach.h"
  8. #include "damage.h"
  9. static OBJECT *victimHere()
  10. {
  11. OBJECT *victim = actorHere();
  12. if (victim == NULL)
  13. {
  14. printf("There is nobody here to attack.\n");
  15. }
  16. return victim;
  17. }
  18. int executeAttack(void)
  19. {
  20. OBJECT *victim =
  21. *params[0] == '\0' ? victimHere()
  22. : reachableObject("who to attack", params[0]);
  23. if (victim != NULL)
  24. {
  25. OBJECT *weapon =
  26. *params[1] == '\0' ? getOptimalWeapon(player)
  27. : getPossession(player, "wield", params[1]);
  28. if (weapon != NULL)
  29. {
  30. dealDamage(player, weapon, victim);
  31. return 1;
  32. }
  33. }
  34. return 0;
  35. }

Explanation:

Finally, here are the new attributes, the weapons and the respawn location.

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. const char *gossip;
  16. int weight;
  17. int capacity;
  18. int health;
  19. int light;
  20. int impact;
  21. int trust;
  22. void (*open)(void);
  23. void (*close)(void);
  24. void (*lock)(void);
  25. void (*unlock)(void);
  26. } OBJECT;
  27. extern OBJECT objs[];
  28. - heaven
  29. description "little heaven"
  30. tags "heaven", "little heaven"
  31. details "Everything looks so peaceful here."
  32. gossip "It's where all the good adventurers go."
  33. capacity 9999
  34. light 100
  35. - respawn
  36. description "a respawn portal"
  37. tags "portal", "respawn portal"
  38. location heaven
  39. destination field
  40. details "Looks like a gateway into the unknown."
  41. textGo "A bright flash of light, and you are back in the field."
  42. open isAlreadyOpen
  43. - heavenEWNS
  44. description "nothing but peace and quiet"
  45. tags "east", "west", "north", "south"
  46. location heaven
  47. textGo "In this dimension, there are no directions."
  48. gossip "It's just a compass direction."
  49. - field
  50. description "an open field"
  51. tags "field"
  52. details "The field is a nice and quiet place under a clear blue sky."
  53. gossip "A lot of tourists go there."
  54. capacity 9999
  55. light 100
  56. - cave
  57. description "a little cave"
  58. tags "cave"
  59. details "The cave is just a cold, damp, rocky chamber."
  60. gossip "It's dark in there; bring a lamp!"
  61. capacity 9999
  62. - silver
  63. description "a silver coin"
  64. tags "silver", "coin", "silver coin"
  65. location field
  66. details "The coin has an eagle on the obverse."
  67. gossip "Money makes the world go round..."
  68. weight 1
  69. - gold
  70. description "a gold coin"
  71. tags "gold", "coin", "gold coin"
  72. location openBox
  73. details "The shiny coin seems to be a rare and priceless artefact."
  74. gossip "Money makes the world go round..."
  75. weight 1
  76. - guard
  77. description "a burly guard"
  78. tags "guard", "burly guard"
  79. location field
  80. details "The guard is a really big fellow."
  81. gossip "Easy to bribe..."
  82. contents "He has"
  83. health 100
  84. impact -1
  85. capacity 20
  86. - player
  87. description "yourself"
  88. tags "yourself"
  89. location field
  90. details "You would need a mirror to look at yourself."
  91. gossip "You're not from around here, are you?"
  92. contents "You have"
  93. health 100
  94. impact -1
  95. capacity 20
  96. - intoCave
  97. condition { return guard->health == 0 || silver->location == guard; }
  98. description "a cave entrance to the east"
  99. tags "east", "entrance"
  100. location field
  101. destination cave
  102. details "The entrance is just a narrow opening in a small outcrop."
  103. textGo "You walk into the cave."
  104. open isAlreadyOpen
  105. - intoCaveBlocked
  106. condition { return guard->health > 0 && silver->location != guard; }
  107. description "a cave entrance to the east"
  108. tags "east", "entrance"
  109. location field
  110. prospect cave
  111. details "The entrance is just a narrow opening in a small outcrop."
  112. textGo "The guard stops you from walking into the cave."
  113. open isAlreadyOpen
  114. - exitCave
  115. description "an exit to the west"
  116. tags "west", "exit"
  117. location cave
  118. destination field
  119. details "Sunlight pours in through an opening in the cave's wall."
  120. textGo "You walk out of the cave."
  121. open isAlreadyOpen
  122. - wallField
  123. description "dense forest all around"
  124. tags "west", "north", "south", "forest"
  125. location field
  126. details "The field is surrounded by trees and undergrowth."
  127. textGo "Dense forest is blocking the way."
  128. gossip "You cannot go there, it is impenetrable."
  129. - wallCave
  130. description "solid rock all around"
  131. tags "east", "north", "rock"
  132. location cave
  133. details "Carved in stone is a secret password 'abccb'."
  134. textGo "Solid rock is blocking the way."
  135. - backroom
  136. description "a backroom"
  137. tags "backroom"
  138. details "The room is dusty and messy."
  139. gossip "There is something of value to be found there."
  140. capacity 9999
  141. - wallBackroom
  142. description "solid rock all around"
  143. tags "east", "west", "south", "rock"
  144. location backroom
  145. details "Trendy wallpaper covers the rock walls."
  146. textGo "Solid rock is blocking the way."
  147. - openDoorToBackroom
  148. description "an open door to the south"
  149. tags "south", "door", "doorway"
  150. destination backroom
  151. details "The door is open."
  152. textGo "You walk through the door into a backroom."
  153. open isAlreadyOpen
  154. close toggleDoorToBackroom
  155. - closedDoorToBackroom
  156. description "a closed door to the south"
  157. tags "south", "door", "doorway"
  158. location cave
  159. prospect backroom
  160. details "The door is closed."
  161. textGo "The door is closed."
  162. open toggleDoorToBackroom
  163. close isAlreadyClosed
  164. - openDoorToCave
  165. description "an open door to the north"
  166. tags "north", "door", "doorway"
  167. destination cave
  168. details "The door is open."
  169. textGo "You walk through the door into the cave."
  170. open isAlreadyOpen
  171. close toggleDoorToCave
  172. - closedDoorToCave
  173. description "a closed door to the north"
  174. tags "north", "door", "doorway"
  175. location backroom
  176. prospect cave
  177. details "The door is closed."
  178. textGo "The door is closed."
  179. open toggleDoorToCave
  180. close isAlreadyClosed
  181. - openBox
  182. description "a wooden box"
  183. tags "box", "wooden box"
  184. details "The box is open."
  185. gossip "You need a key to open it."
  186. weight 5
  187. capacity 10
  188. open isAlreadyOpen
  189. close toggleBox
  190. lock isStillOpen
  191. unlock isAlreadyOpen
  192. - closedBox
  193. description "a wooden box"
  194. tags "box", "wooden box"
  195. details "The box is closed."
  196. weight 5
  197. open toggleBox
  198. close isAlreadyClosed
  199. lock toggleBoxLock
  200. unlock isAlreadyUnlocked
  201. - lockedBox
  202. description "a wooden box"
  203. tags "box", "wooden box"
  204. location backroom
  205. details "The box is closed."
  206. weight 5
  207. open isStillLocked
  208. close isAlreadyClosed
  209. lock isAlreadyLocked
  210. unlock toggleBoxLock
  211. - keyForBox
  212. description "a tiny key"
  213. tags "key", "tiny key"
  214. location cave
  215. details "The key is really small and shiny."
  216. gossip "A small key opens a small lock."
  217. weight 1
  218. - lampOff
  219. description "a lamp"
  220. tags "lamp"
  221. location field
  222. details "The lamp is off."
  223. gossip "Essential in dark areas."
  224. weight 5
  225. - lampOn
  226. description "a lamp"
  227. tags "lamp"
  228. details "The lamp is on."
  229. weight 5
  230. light 100
  231. - club
  232. description "a wooden club"
  233. tags "club", "wooden club"
  234. location field
  235. details "Two feet of solid wood."
  236. weight 5
  237. impact -2
  238. - dagger
  239. description "a dagger"
  240. tags "dagger"
  241. location field
  242. details "The dagger is very sharp."
  243. weight 7
  244. impact -5

Explanation:

The new attributes demand a change in object.awk as well.

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["gossip"] = "\"I know nothing about that.\"";
  22. prop["weight"] = "99";
  23. prop["capacity"] = "0";
  24. prop["health"] = "0";
  25. prop["light"] = "0";
  26. prop["impact"] = "0";
  27. prop["trust"] = "0";
  28. prop["open"] = "cannotBeOpened";
  29. prop["close"] = "cannotBeClosed";
  30. prop["lock"] = "cannotBeLocked";
  31. prop["unlock"] = "cannotBeUnlocked";
  32. }
  33. obj && /^[ \t]+[a-z]/ {
  34. name = $1;
  35. $1 = "";
  36. if (name in prop) {
  37. prop[name] = $0;
  38. if (/^[ \t]*\{/) {
  39. prop[name] = name count;
  40. if (pass == "c1") print "static bool " prop[name] "(void) " $0;
  41. }
  42. }
  43. else if (pass == "c2") {
  44. print "#error \"" FILENAME " line " NR ": unknown attribute '" name "'\"";
  45. }
  46. }
  47. !obj && pass == (/^#include/ ? "c1" : "h") {
  48. print;
  49. }
  50. END {
  51. outputRecord("\n};");
  52. if (pass == "h") {
  53. print "\n#define endOfObjs\t(objs + " count ")";
  54. print "\n#define validObject(obj)\t" \
  55. "((obj) != NULL && (*(obj)->condition)())";
  56. }
  57. }
  58. function outputRecord(separator)
  59. {
  60. if (obj) {
  61. if (pass == "h") {
  62. print "#define " obj "\t(objs + " count ")";
  63. }
  64. else if (pass == "c1") {
  65. print "static const char *tags" count "[] = {" prop["tags"] ", NULL};";
  66. }
  67. else if (pass == "c2") {
  68. print "\t{\t/* " count " = " obj " */";
  69. print "\t\t" prop["condition"] ",";
  70. print "\t\t" prop["description"] ",";
  71. print "\t\ttags" count ",";
  72. print "\t\t" prop["location"] ",";
  73. print "\t\t" prop["destination"] ",";
  74. print "\t\t" prop[prop["prospect"] ? "prospect" : "destination"] ",";
  75. print "\t\t" prop["details"] ",";
  76. print "\t\t" prop["contents"] ",";
  77. print "\t\t" prop["textGo"] ",";
  78. print "\t\t" prop["gossip"] ",";
  79. print "\t\t" prop["weight"] ",";
  80. print "\t\t" prop["capacity"] ",";
  81. print "\t\t" prop["health"] ",";
  82. print "\t\t" prop["light"] ",";
  83. print "\t\t" prop["impact"] ",";
  84. print "\t\t" prop["trust"] ",";
  85. print "\t\t" prop["open"] ",";
  86. print "\t\t" prop["close"] ",";
  87. print "\t\t" prop["lock"] ",";
  88. print "\t\t" prop["unlock"];
  89. print "\t}" separator;
  90. delete prop;
  91. }
  92. count++;
  93. }
  94. }

Explanation:

Trust could also affect commands that are unrelated to combat. Most notably ask.

move.c
  1. #include <stdbool.h>
  2. #include <stdio.h>
  3. #include "object.h"
  4. #include "misc.h"
  5. static int weightOfContents(OBJECT *container)
  6. {
  7. int sum = 0;
  8. OBJECT *obj;
  9. for (obj = objs; obj < endOfObjs; obj++)
  10. {
  11. if (isHolding(container, obj)) sum += obj->weight;
  12. }
  13. return sum;
  14. }
  15. static void describeMove(OBJECT *obj, OBJECT *to)
  16. {
  17. if (to == player->location)
  18. {
  19. printf("You drop %s.\n", obj->description);
  20. }
  21. else if (to != player)
  22. {
  23. printf(to->health > 0 ? "You give %s to %s.\n" : "You put %s in %s.\n",
  24. obj->description, to->description);
  25. }
  26. else if (obj->location == player->location)
  27. {
  28. printf("You pick up %s.\n", obj->description);
  29. }
  30. else
  31. {
  32. printf("You get %s from %s.\n",
  33. obj->description, obj->location->description);
  34. }
  35. }
  36. int moveObject(OBJECT *obj, OBJECT *to)
  37. {
  38. if (obj == NULL)
  39. {
  40. // already handled by getVisible or getPossession
  41. return 0;
  42. }
  43. else if (to == NULL)
  44. {
  45. printf("There is nobody here to give that to.\n");
  46. return 0;
  47. }
  48. else if (obj->location != NULL && obj->location != player &&
  49. obj->location->health > 0 && obj->location->trust <= 0)
  50. {
  51. printf("It seems %s feels reluctant to give you anything.\n",
  52. obj->location->description);
  53. return 1;
  54. }
  55. else if (to->capacity == 0)
  56. {
  57. printf(obj == keyForBox && (to == closedBox || to == lockedBox) ?
  58. "The key seems to fit the lock.\n" :
  59. "It doesn't seem to fit in.\n");
  60. return 1;
  61. }
  62. else if (obj->weight > to->capacity)
  63. {
  64. printf("That is way too heavy.\n");
  65. return 1;
  66. }
  67. else if (obj->weight + weightOfContents(to) > to->capacity)
  68. {
  69. printf("That would become too heavy.\n");
  70. return 1;
  71. }
  72. else
  73. {
  74. describeMove(obj, to);
  75. obj->location = to;
  76. return 1;
  77. }
  78. }

Explanation:

That’s about it as far as game logic goes. To make it a real game, we need more objects (locations, actors, items) and more puzzles. The latter can be done by adding custom code to the various modules we have seen so far.

I am still working on extra chapters, covering some technical topics.


⭳   Download source code 🌀   Run on Repl.it

Next chapter: 21. Multi-player