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

18. Abbreviations

Classic adventures like Colossal Cave and Zork have more than a hundred locations for the player to discover. Playing these games involves a lot of “go north/east/south/west”, so it’s no surprise that from the very beginning, text adventures accepted abbreviations like n, e, s, w.

Implementing an abbreviation appears to be trivial. From a coding perspective, an abbreviation is just a synonym for its full-sized counterpart, right? So all we have to do is add an extra row to the list of commands in function parseAndExecute.

There is a downside to that approach. When using text-to-speech to read out loud the progress of a game session, those abbreviations do not come out too well. How is a screen reader supposed to know ‘ne’ means north-east?

So instead, I would recommend a pre-processor that expands abbreviations before sending the user’s input to the parser. Implementation-wise, such a pre-processor is quite similar to the actual parser. We can even re-use function matchCommand.

It may seem like overkill to use matchCommand for recognizing a one-letter command like ‘n’, but why would I want to implement case insensitivity and whitespace trimming all over again?

One may argue that this approach is suboptimal. A simple abbreviation is expanded to something more verbose (e.g. “go north”), which then has to be broken down again by the parser. Please feel free to optimize, if it makes you feel better. Just remember the rules of optimization from the previous chapter.

expand.h
  1. extern char *expand(char *input, int bufsize);
expand.c
  1. #include <stdbool.h>
  2. #include <string.h>
  3. #include "match.h"
  4. typedef struct
  5. {
  6. const char *abbreviated;
  7. const char *expanded;
  8. } SHORTHAND;
  9. static int minimum(int x, int y)
  10. {
  11. return x < y ? x : y;
  12. }
  13. char *expand(char *input, int bufsize)
  14. {
  15. static const SHORTHAND shorthands[] =
  16. {
  17. {"n", "go north"},
  18. {"s", "go south"},
  19. {"w", "go west"},
  20. {"e", "go east"},
  21. {"inv", "inventory"},
  22. {"x A", "examine "},
  23. {"A", NULL}
  24. };
  25. const SHORTHAND *sh;
  26. for (sh = shorthands; !matchCommand(input, sh->abbreviated); sh++);
  27. if (sh->expanded != NULL)
  28. {
  29. const char *moreInput = *paramByLetter('A');
  30. int lengthOfExpanded = strlen(sh->expanded);
  31. memmove(input + lengthOfExpanded, moreInput,
  32. minimum(strlen(moreInput) + 1, bufsize - lengthOfExpanded - 1));
  33. strncpy(input, sh->expanded, lengthOfExpanded);
  34. }
  35. return input;
  36. }

Explanation:

Now all we have to do is call function expand right before calling parseAndExecute.

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

--> e
The guard stops you from walking into the cave.

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

--> inv
You have:
a silver coin

--> x coin
The coin has an eagle on the obverse.

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

--> e
You walk into the cave.

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

--> n
It's too dark.

--> w
You walk out of the cave.

You are in an open field.
You see:
a burly guard
a cave entrance to the east
dense forest all around
a lamp

--> w
Dense forest is blocking the way.

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

Of course, there should be more to an adventure than traversing dozens of locations just to take object A to location B. In the next chapter, we will look at ways to bring more depth into the game.


⭳   Download source code 🌀   Run on Repl.it

Next chapter: 19. Conversations