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

17. Test automation

Testing is important in any software development project. Games are no exception. Dutch games developer John Vanderaart made it very clear in an interview in 2007: “test, test, test.”

You could test everything manually; play your own game to see if everything still works. Over and over again. Every change you make, no matter how small, should be tested. This quickly becomes tiresome.

Fortunately, text adventures are very suitable for test automation. In its simplest form, this works a follows.

  1. Test the game manually once, while letting the program log every command you enter.
  2. Run the game again, but this time, use the log file as input. Capture the output in a file.
  3. Whenever you want to test the program, just repeat step 2, and compare the output from both sessions. Any differences that cannot be justified, should be considered bugs.

Step 1 is easy; we implemented this in the previous chapter. All we have to do is run the program with a filename as argument.

./lilcave testscript.txt

Now start testing the game manually. Try as many scenarios as you can. Just a quick walk-through is not enough; you really need to test the dead ends and pitfalls. Try to get a good code coverage; use tooling like gcov where possible. When you are done, exit the program.

Keep the resulting log file in a safe place; it is your test script.

There is one more thing to do here: add the command ‘quit’ to the end of the file.

echo quit >> testscript.txt

We are a bit cheating here; normally a log file never contains the command ‘quit’, because that would make it impossible for the player to continue the game. But in an automated test, we really want the program to quit once the test script has been finished.

testscript.txt
  1. mumble
  2. inventory
  3. get whatever
  4. get yourself
  5. get silver coin
  6. get coin
  7. get guard
  8. get cave
  9. look cave
  10. inventory
  11. look at coin
  12. get lamp
  13. inventory
  14. go east
  15. ask coin
  16. put coin in cave
  17. give coin
  18. give lamp to guard
  19. look lamp
  20. get lamp from guard
  21. ask lamp from guard
  22. open yourself
  23. open coin
  24. open cave
  25. go cave
  26. go east
  27. turn lamp on
  28. look around
  29. get key from cave
  30. ask coin
  31. give coin
  32. go south
  33. open door
  34. go south
  35. open box
  36. put key in box
  37. unlock box
  38. unlock box
  39. open box
  40. look box
  41. get coin from box
  42. put coin in box
  43. lock box
  44. close box
  45. lock box
  46. lock box
  47. drop key
  48. lock box
  49. unlock box
  50. get key
  51. go north
  52. drop key
  53. go west
  54. turn lamp on
  55. turn lamp off
  56. turn lamp off
  57. drop lamp
  58. ask coin
  59. turn coin on
  60. turn coin off
  61. open coin
  62. close coin
  63. lock coin
  64. unlock coin
  65. drop coin
  66. drop coin
  67. look backroom
  68. go backroom
  69. quit

This test script is full of weird (combinations of) commands. I did my best to let the program give as many different responses as possible. The result is a code coverage of more than 90%, which is pretty good. Please note that this does not imply that we have tested 90% of all possible verb/noun combinations. Code coverage says little to nothing about the number of objects that we interacted with, due to the data-driven nature of our program.

In step 2, we just call the program again, with the same filename as argument. Effectively, this repeats the entire session of step 1, without any user intervention. The difference is, this time we redirect the output of the program to a file.

./lilcave testscript.txt > baseline.txt

This file is a transcript of the entire test session. Keep it in a safe place as well; it is our baseline.

baseline.txt
  1. Welcome to Little Cave Adventure.
  2. You are in an open field.
  3. You see:
  4. a silver coin
  5. a burly guard
  6. a cave entrance to the east
  7. dense forest all around
  8. a lamp
  9. --> mumble
  10. I don't know how to 'mumble'.
  11. --> inventory
  12. You are empty-handed.
  13. --> get whatever
  14. I don't understand what you want to get.
  15. --> get yourself
  16. You should not be doing that to yourself.
  17. --> get silver coin
  18. You pick up a silver coin.
  19. --> get coin
  20. You already have a silver coin.
  21. --> get guard
  22. That is way too heavy.
  23. --> get cave
  24. Too far away, move closer please.
  25. --> look cave
  26. Too far away, move closer please.
  27. --> inventory
  28. You have:
  29. a silver coin
  30. --> look at coin
  31. The coin has an eagle on the obverse.
  32. --> get lamp
  33. You pick up a lamp.
  34. --> inventory
  35. You have:
  36. a silver coin
  37. a lamp
  38. --> go east
  39. The guard stops you from walking into the cave.
  40. --> ask coin
  41. There appears to be no coin you can get from a burly guard.
  42. --> put coin in cave
  43. Too far away, move closer please.
  44. --> give coin
  45. You give a silver coin to a burly guard.
  46. --> give lamp to guard
  47. You give a lamp to a burly guard.
  48. --> look lamp
  49. Hard to see, try to get it first.
  50. --> get lamp from guard
  51. You should ask a burly guard nicely.
  52. --> ask lamp from guard
  53. You get a lamp from a burly guard.
  54. --> open yourself
  55. You should not be doing that to yourself.
  56. --> open coin
  57. You would have to get it from a burly guard first.
  58. --> open cave
  59. Too far away, move closer please.
  60. --> go cave
  61. You walk into the cave.
  62. It is very dark in here.
  63. You see:
  64. an exit to the west
  65. --> go east
  66. It's too dark.
  67. --> turn lamp on
  68. You turn on a lamp.
  69. You are in a little cave.
  70. You see:
  71. an exit to the west
  72. solid rock all around
  73. a closed door to the south
  74. a tiny key
  75. --> look around
  76. You are in a little cave.
  77. You see:
  78. an exit to the west
  79. solid rock all around
  80. a closed door to the south
  81. a tiny key
  82. --> get key from cave
  83. You pick up a tiny key.
  84. --> ask coin
  85. I don't understand who you want to ask.
  86. --> give coin
  87. You are not holding any coin.
  88. --> go south
  89. The door is closed.
  90. --> open door
  91. You open a closed door to the south.
  92. --> go south
  93. You walk through the door into a backroom.
  94. You are in a backroom.
  95. You see:
  96. solid rock all around
  97. an open door to the north
  98. a wooden box
  99. --> open box
  100. That is locked.
  101. --> put key in box
  102. The key seems to fit the lock.
  103. --> unlock box
  104. You unlock a wooden box.
  105. --> unlock box
  106. That is already unlocked.
  107. --> open box
  108. You open a wooden box.
  109. --> look box
  110. The box is open.
  111. You see:
  112. a gold coin
  113. --> get coin from box
  114. You get a gold coin from a wooden box.
  115. --> put coin in box
  116. You put a gold coin in a wooden box.
  117. --> lock box
  118. That is still open.
  119. --> close box
  120. You close a wooden box.
  121. --> lock box
  122. You lock a wooden box.
  123. --> lock box
  124. That is already locked.
  125. --> drop key
  126. You drop a tiny key.
  127. --> lock box
  128. That is already locked.
  129. --> unlock box
  130. You don't have a key.
  131. --> get key
  132. You pick up a tiny key.
  133. --> go north
  134. You walk through the door into the cave.
  135. You are in a little cave.
  136. You see:
  137. an exit to the west
  138. solid rock all around
  139. an open door to the south
  140. --> drop key
  141. You drop a tiny key.
  142. --> go west
  143. You walk out of the cave.
  144. You are in an open field.
  145. You see:
  146. a burly guard
  147. a cave entrance to the east
  148. dense forest all around
  149. --> turn lamp on
  150. The lamp is already on.
  151. --> turn lamp off
  152. You turn off a lamp.
  153. --> turn lamp off
  154. The lamp is already off.
  155. --> drop lamp
  156. You drop a lamp.
  157. --> ask coin
  158. You get a silver coin from a burly guard.
  159. --> turn coin on
  160. You cannot turn that on.
  161. --> turn coin off
  162. You cannot turn that off.
  163. --> open coin
  164. That cannot be opened.
  165. --> close coin
  166. That cannot be closed.
  167. --> lock coin
  168. That cannot be locked.
  169. --> unlock coin
  170. That cannot be unlocked.
  171. --> drop coin
  172. You drop a silver coin.
  173. --> drop coin
  174. You are not holding any coin.
  175. --> look backroom
  176. You don't see any backroom here.
  177. --> go backroom
  178. You don't see any backroom here.
  179. --> quit
  180. Bye!

We now have two files, the test script and the baseline, that we will be using to test every change we make to the program. Testing is easy:

./lilcave testscript.txt > transcript.txt diff baseline.txt transcript.txt

Above, I used diff to compare the actual output of the game (transcript.txt) with the output that was last considered correct (baseline.txt). Please feel free to use your favorite diff utility instead.

Now there are three possible outcomes.

  1. There are no differences. Apparently, your changes did not result in a regression.
  2. All differences were intentional. For example, after fixing a typo, the game’s output should only change for the better.
  3. There was an unintentional difference. This is a potential bug; it should be analyzed and if necessary, fixed.

Please note that case 2 is a reason for your baseline to be updated; otherwise your intentional differences will keep piling up, making it increasingly hard to spot the unintentional differences. If there are no unintended changes, then updating the baseline is straightforward:

cp transcript.txt baseline.txt

Obviously, changing your test script (e.g. because you introduced new code or new objects not covered by the original test script) will always break the test. This warrants a new manual test, resulting in a new baseline. Of course, you can use (part of) the existing test script as a starting point; rarely will it be necessary to start all over.

Consider including test automation in your build process. Here is an example for make:

success.txt: lilcave testscript.txt baseline.txt ./lilcave testscript.txt > transcript.txt cmp baseline.txt transcript.txt mv -f transcript.txt success.txt

Here, make will only execute the final move (mv) after a successful comparison (cmp). That way, an invalid transcript can never be mistaken for a successful update, and make will always re-run a test that failed earlier.

Performance testing

Performance is hardly ever an issue in text adventures, for a number of reasons.

Still, it is always good to take performance seriously. Looking back at our code so far, you may have noticed some parts that are potentially suboptimal.

In a big adventure, with many locations, items, actors, and with a comprehensive vocabulary, these loops will have to traverse some pretty long arrays. To make matters worse, the 'log and roll forward' technique proposed in the previous chapter means the player will have to wait for hundreds, maybe thousands of commands to be parsed and executed before being able to resume a game saved earlier. How long is that going to take?

Many programmers will not wait for the bomb to burst, and immediately start replacing the loops by more advanced techniques, for example hash tables. This could make your code more efficient, but possibly also more complex. I strongly recommend every developer to take some time to study the rules of optimization first:

The simplest way to profile your code, is to run an automated test while keeping track of time. In most operating systems, you can use time for that. If you want a more detailed insight, then you can use gprof; it can tell you exactly what functions eat up the majority of CPU time.

I leave it up to you to decide whether or not to make performance testing an integral part of your test automation.


⭳   Download source code 🌀   Run on Repl.it

Next chapter: 18. Abbreviations