-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathhtpataic18.html
355 lines (354 loc) · 12.2 KB
/
htpataic18.html
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
<!DOCTYPE html>
<html>
<head>
<title>18. Abbreviations</title>
<link rel="stylesheet" href="htpataic.css" type="text/css" />
</head>
<body>
<table class="contents"><tr><td>
<h4>Contents</h4>
<div><a href="htpataic01.html">1. Introduction</a></div>
<div><a href="htpataic02.html">2. The main loop</a></div>
<div><a href="htpataic03.html">3. Locations</a></div>
<div><a href="htpataic04.html">4. Objects</a></div>
<div><a href="htpataic05.html">5. Inventory</a></div>
<div><a href="htpataic06.html">6. Passages</a></div>
<div><a href="htpataic07.html">7. Distance</a></div>
<div><a href="htpataic08.html">8. North, east, south, west</a></div>
<div><a href="htpataic09.html">9. Code generation</a></div>
<div><a href="htpataic10.html">10. More attributes</a></div>
<div><a href="htpataic11.html">11. Conditions</a></div>
<div><a href="htpataic12.html">12. Open and close</a></div>
<div><a href="htpataic13.html">13. The parser</a></div>
<div><a href="htpataic14.html">14. Multiple nouns</a></div>
<div><a href="htpataic15.html">15. Light and dark</a></div>
<div><a href="htpataic16.html">16. Savegame</a></div>
<div><a href="htpataic17.html">17. Test automation</a></div>
<div><b>18. Abbreviations</b></div>
<div><a href="htpataic19.html">19. Conversations</a></div>
<div><a href="htpataic20.html">20. Combat</a></div>
<div><a href="htpataic21.html">21. Multi-player</a></div>
<div><a href="htpataic22.html">22. Client-server</a></div>
<div><a href="htpataic23.html">23. Database</a></div>
<div><a href="htpataic24.html">24. Speech</a></div>
<div><a href="htpataic25.html">25. JavaScript</a></div>
</td></tr></table>
<h1>How to program a text adventure in C</h1>
<p>
by Ruud Helderman
<<a href="mailto:r.helderman@hccnet.nl">r.helderman@hccnet.nl</a>>
</p>
<p>
Licensed under
<a href="https://github.com/helderman/htpataic/blob/master/LICENSE">MIT License</a>
</p>
<h2>18. Abbreviations</h2>
<p class="intro">
Classic adventures like
<a href="https://en.wikipedia.org/wiki/Colossal_Cave_Adventure">Colossal Cave</a>
and
<a href="https://en.wikipedia.org/wiki/Zork">Zork</a>
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.
</p>
<p>
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 <i>parseAndExecute</i>.
</p>
<p>
There is a downside to that approach. When using
<a href="https://en.wikipedia.org/wiki/Speech_synthesis">text-to-speech</a>
to read out loud the progress of a game session,
those abbreviations do not come out too well.
How is a
<a href="https://en.wikipedia.org/wiki/Screen_reader">screen reader</a>
supposed to know ‘ne’ means north-east?
</p>
<p>
So instead, I would recommend a pre-processor that
<b>expands</b> 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 <i>matchCommand</i>.
</p>
<p>
It may seem like overkill to use <i>matchCommand</i>
for recognizing a one-letter command like ‘n’,
but why would I want to implement
case insensitivity and whitespace trimming all over again?
</p>
<p>
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
<a href="http://wiki.c2.com/?RulesOfOptimization">rules of optimization</a>
from the previous chapter.
</p>
<table class="code"><tr>
<th>expand.h</th>
</tr><tr>
<td>
<ol>
<li class="new">extern char *expand(char *input, int bufsize);</li>
</ol>
</td>
</tr><tr>
<th>expand.c</th>
</tr><tr>
<td>
<ol>
<li class="new">#include <stdbool.h></li>
<li class="new">#include <string.h></li>
<li class="new">#include "match.h"</li>
<li class="new"></li>
<li class="new">typedef struct</li>
<li class="new">{</li>
<li class="new"> const char *abbreviated;</li>
<li class="new"> const char *expanded;</li>
<li class="new">} SHORTHAND;</li>
<li class="new"></li>
<li class="new">static int minimum(int x, int y)</li>
<li class="new">{</li>
<li class="new"> return x < y ? x : y;</li>
<li class="new">}</li>
<li class="new"></li>
<li class="new">char *expand(char *input, int bufsize)</li>
<li class="new">{</li>
<li class="new"> static const SHORTHAND shorthands[] =</li>
<li class="new"> {</li>
<li class="new"> {"n", "go north"},</li>
<li class="new"> {"s", "go south"},</li>
<li class="new"> {"w", "go west"},</li>
<li class="new"> {"e", "go east"},</li>
<li class="new"> {"inv", "inventory"},</li>
<li class="new"> {"x A", "examine "},</li>
<li class="new"> {"A", NULL}</li>
<li class="new"> };</li>
<li class="new"> const SHORTHAND *sh;</li>
<li class="new"> for (sh = shorthands; !matchCommand(input, sh->abbreviated); sh++);</li>
<li class="new"> if (sh->expanded != NULL)</li>
<li class="new"> {</li>
<li class="new"> const char *moreInput = *paramByLetter('A');</li>
<li class="new"> int lengthOfExpanded = strlen(sh->expanded);</li>
<li class="new"> memmove(input + lengthOfExpanded, moreInput,</li>
<li class="new"> minimum(strlen(moreInput) + 1, bufsize - lengthOfExpanded - 1));</li>
<li class="new"> strncpy(input, sh->expanded, lengthOfExpanded);</li>
<li class="new"> }</li>
<li class="new"> return input;</li>
<li class="new">}</li>
</ol>
</td>
</tr></table>
<div class="explanation">
<p>
Explanation:
</p>
<ul>
<li>Line 5-9:
struct <i>SHORTHAND</i> is similar to struct <i>COMMAND</i>
(defined in <i>parsexec.c</i>).
It maps a command pattern not to a function
but to another (more verbose) command string.
</li>
<li>Line 16:
this is the function that expands the abbreviation (if any).
Expansion takes place directly in the input buffer.
To be able to prevent
<a href="http://en.wikipedia.org/wiki/Buffer_overflow">buffer overflow</a>,
the function must be aware of the size of the buffer
(parameter <i>bufsize</i>).
</li>
<li>Line 25:
an example of an expansion that is slightly less trivial.
When expanding ‘x’ to ‘examine’,
we should preserve whatever follows the verb.
For example,
“x coin” should be expanded to “examine coin”.
The trailing space in the string “examine ” is there on purpose;
it helps to keep the code simple.
</li>
<li>Line 26:
just like in the parser, the final pattern “A”
should catch every command that did not match any of the abbreviations.
</li>
<li>Line 29:
this loop tries to find the abbreviation that matches the user’s input.
</li>
<li>Line 30:
if the loop stopped at the final ‘sure-match’ pattern (line 26),
then there was no matching abbreviation.
We will skip the rest of the function; no expansion will take place.
</li>
<li>Line 32:
retrieves the value of parameter <i>A</i>
(e.g. “coin” if the user entered “x coin”),
or an empty string if the abbreviation is parameterless.
</li>
<li>Line 34:
moving the noun (the parameter <i>A</i>, if any) away from the verb,
to make room for the expanded verb.
Deliberately using <i>memmove</i> instead of <i>strcpy</i>
(from <a href="http://en.wikipedia.org/wiki/String.h">string.h</a>),
as we are (possibly) moving data within the same input buffer.
</li>
<li>Line 35:
here <i>bufsize</i> is taken into account, to prevent
<a href="http://en.wikipedia.org/wiki/Buffer_overflow">buffer overflow</a>.
For very long commands, this may truncate the string,
but assuming the buffer has a reasonable size,
that is unlikely to bother the player.
</li>
<li>Line 36:
now we can finally copy the expanded command to the front of the input buffer.
</li>
</ul>
</div>
<p>
Now all we have to do is call function <i>expand</i>
right before calling <i>parseAndExecute</i>.
</p>
<table class="demo">
<tr><th>Sample output</th></tr>
<tr><td>
Welcome to Little Cave Adventure.<br />
You are in an open field.<br />
You see:<br />
a silver coin<br />
a burly guard<br />
a cave entrance to the east<br />
dense forest all around<br />
a lamp<br />
<br />
--> e<br />
The guard stops you from walking into the cave.<br />
<br />
--> get coin<br />
You pick up a silver coin.<br />
<br />
--> inv<br />
You have:<br />
a silver coin<br />
<br />
--> x coin<br />
The coin has an eagle on the obverse.<br />
<br />
--> give coin<br />
You give a silver coin to a burly guard.<br />
<br />
--> e<br />
You walk into the cave.<br />
<br />
It is very dark in here.<br />
You see:<br />
an exit to the west<br />
<br />
--> n<br />
It's too dark.<br />
<br />
--> w<br />
You walk out of the cave.<br />
<br />
You are in an open field.<br />
You see:<br />
a burly guard<br />
a cave entrance to the east<br />
dense forest all around<br />
a lamp<br />
<br />
--> w<br />
Dense forest is blocking the way.<br />
<br />
--> quit<br />
<br />
Bye!<br />
</td></tr>
</table>
<table class="code"><tr>
<th>main.c</th>
</tr><tr>
<td>
<ol>
<li class="old">#include <stdbool.h></li>
<li class="old">#include <stdio.h></li>
<li class="old">#include <string.h></li>
<li class="new"><span class="old">#include </span>"expand.h"</li>
<li class="new">#include<span class="old"> "parsexec.h"</span></li>
<li class="old"></li>
<li class="old">static char input[100] = "look around";</li>
<li class="old"></li>
<li class="old">static bool getFromFP(FILE *fp)</li>
<li class="old">{</li>
<li class="old"> bool ok = fgets(input, sizeof input, fp) != NULL;</li>
<li class="old"> if (ok) input[strcspn(input, "\n")] = '\0';</li>
<li class="old"> return ok;</li>
<li class="old">}</li>
<li class="old"></li>
<li class="old">static bool getInput(const char *filename)</li>
<li class="old">{</li>
<li class="old"> static FILE *fp = NULL;</li>
<li class="old"> bool ok;</li>
<li class="old"> if (fp == NULL)</li>
<li class="old"> {</li>
<li class="old"> if (filename != NULL) fp = fopen(filename, "rt");</li>
<li class="old"> if (fp == NULL) fp = stdin;</li>
<li class="old"> }</li>
<li class="old"> else if (fp == stdin && filename != NULL)</li>
<li class="old"> {</li>
<li class="old"> FILE *out = fopen(filename, "at");</li>
<li class="old"> if (out != NULL)</li>
<li class="old"> {</li>
<li class="old"> fprintf(out, "%s\n", input);</li>
<li class="old"> fclose(out);</li>
<li class="old"> }</li>
<li class="old"> }</li>
<li class="old"> printf("\n--> ");</li>
<li class="old"> ok = getFromFP(fp);</li>
<li class="old"> if (fp != stdin)</li>
<li class="old"> {</li>
<li class="old"> if (ok)</li>
<li class="old"> {</li>
<li class="old"> printf("%s\n", input);</li>
<li class="old"> }</li>
<li class="old"> else</li>
<li class="old"> {</li>
<li class="old"> fclose(fp);</li>
<li class="old"> ok = getFromFP(fp = stdin);</li>
<li class="old"> }</li>
<li class="old"> }</li>
<li class="old"> return ok;</li>
<li class="old">}</li>
<li class="old"></li>
<li class="old">int main(int argc, char *argv[])</li>
<li class="old">{</li>
<li class="old"> (void)argc;</li>
<li class="old"> printf("Welcome to Little Cave Adventure.\n");</li>
<li class="new"><span class="old"> while </span>(parseAndExecute(expand(input, sizeof input))<span class="old"> && getInput(argv[1]));</span></li>
<li class="old"> printf("\nBye!\n");</li>
<li class="old"> return 0;</li>
<li class="old">}</li>
</ol>
</td>
</tr></table>
<p>
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.
</p>
<hr />
<table class="download"><tr><td>
<a class="button" href="code18/src.zip">⭳ Download source code</a>
<a class="button" href="https://repl.it/github/helderman/htpataic">🌀 Run on Repl.it</a>
</td></tr></table>
<p>
Next chapter: <a href="htpataic19.html">19. Conversations</a>
</p>
</body>
</html>