-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathhtpataic16.html
289 lines (288 loc) · 10.9 KB
/
htpataic16.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
<!DOCTYPE html>
<html>
<head>
<title>16. Savegame</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><b>16. Savegame</b></div>
<div><a href="htpataic17.html">17. Test automation</a></div>
<div><a href="htpataic18.html">18. Abbreviations</a></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>16. Savegame</h2>
<p class="intro">
An adventure with any degree of difficulty
should give the player the opportunity to save his progress,
so he can resume the game at a later time.
</p>
<p>
Typically, adventure games simply save their state to a file on disk.
Basically this means: write every (relevant) variable to a file,
and read them back in again to resume the game.
For reasons of portability and security, it would be wise to
<a href="https://en.wikipedia.org/wiki/Serialization">serialize</a>
the data.
<p>
For a traditional single-player adventure,
an alternative would be for the game to log the player’s input.
When the player wants to resume, do a ‘roll-forward’;
starting from the initial game state, replay every command.
Unusual as it may be, it brings along a few nice advantages.
</p>
<ul>
<li>The player can browse back through the entire transcript of the game.
It can help a player get over that feeling of
“It has been a while since I last played, what was I doing here?”
</li>
<li>Makes it easier for the player to ‘undo’ a command.
For example when stuck in the dark:
exit the game, edit the log file, resume the game.
It would be cruel to demand the player to start all over again.
</li>
<li>Makes it harder for the player to cheat.
There simply is no advantage in hacking the log file;
without the right clue (or a friend’s savegame),
you will never make it to the other side of that locked door.
</li>
<li>Implementation is simple and generic.
We only have to adjust one function: <i>getInput</i> (see chapter 2).
</li>
<li>Portable by nature.
The log file is a straightforward text file; one command per line.
Do be careful with software updates that alter the game’s behavior;
these might invalidate log files created in earlier versions of the game.
</li>
<li>It can help the developer to analyze problems.
After an application crash,
it will be possible to retrace the steps that led to the situation.
</li>
<li>It can help the developer with test automation.
This is explained in the next chapter.
</li>
</ul>
<p>
Of course, this technique also comes with some challenges:
</p>
<ul>
<li>As the player spends more time playing the game,
the log file will grow, and the time it takes to resume the game will increase.
This is discussed in chapter 17.
</li>
<li>Games that use a random generator to bring in an element of surprise,
may go in a totally different direction when resuming a game saved earlier.
This is discussed in chapter 20.
</li>
<li>
This technique is less suitable for online multi-player games.
For those, it is better to use a database.
This will be discussed in chapter 23.
</li>
</ul>
<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="new"><span class="old">#include </span><string.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="new"><span class="old">static bool </span>getFromFP(FILE *fp)</li>
<li class="old">{</li>
<li class="new"><span class="old"> </span>bool ok =<span class="old"> fgets(input, sizeof input, </span>fp)<span class="old"> != NULL;</span></li>
<li class="new"><span class="old"> </span>if (ok) input[strcspn(input, "\n")] = '\0';</li>
<li class="new"> return ok;</li>
<li class="new">}</li>
<li class="new"></li>
<li class="new">static bool getInput(const char *filename)</li>
<li class="new">{</li>
<li class="new"> static FILE *fp = NULL;</li>
<li class="new"> bool ok;</li>
<li class="new"> if (fp == NULL)</li>
<li class="new"> {</li>
<li class="new"> if (filename != NULL) fp = fopen(filename, "rt");</li>
<li class="new"> if (fp == NULL) fp = stdin;</li>
<li class="new"> }</li>
<li class="new"> else if (fp == stdin && filename != NULL)</li>
<li class="new"> {</li>
<li class="new"> FILE *out = fopen(filename, "at");</li>
<li class="new"> if (out != NULL)</li>
<li class="new"> {</li>
<li class="new"> fprintf(out, "%s\n", input);</li>
<li class="new"> fclose(out);</li>
<li class="new"> }</li>
<li class="new"> }</li>
<li class="new"> printf("\n--> ");</li>
<li class="new"> ok = getFromFP(fp);</li>
<li class="new"> if (fp != stdin)</li>
<li class="new"> {</li>
<li class="new"> if (ok)</li>
<li class="new"> {</li>
<li class="new"> printf("%s\n", input);</li>
<li class="new"> }</li>
<li class="new"> else</li>
<li class="new"> {</li>
<li class="new"> fclose(fp);</li>
<li class="new"> ok = getFromFP(fp = stdin);</li>
<li class="new"> }</li>
<li class="new"> }</li>
<li class="new"> return ok;</li>
<li class="old">}</li>
<li class="old"></li>
<li class="new"><span class="old">int </span>main(int argc, char *argv[])</li>
<li class="old">{</li>
<li class="new"><span class="old"> </span>(void)argc;</li>
<li class="old"> printf("Welcome to Little Cave Adventure.\n");</li>
<li class="new"><span class="old"> while (parseAndExecute(input) && </span>getInput(argv[1]));</li>
<li class="old"> printf("\nBye!\n");</li>
<li class="old"> return 0;</li>
<li class="old">}</li>
</ol>
</td>
</tr></table>
<div class="explanation">
<p>
Explanation:
</p>
<ul>
<li>Line 11:
<a href="http://en.wikipedia.org/wiki/Fgets">fgets</a>
has the nasty habit of storing a trailing
<a href="http://en.wikipedia.org/wiki/Newline">newline</a>
character in buffer <i>input</i>.
I never mentioned it, as the parser was smart enough to ignore trailing
<a href="http://en.wikipedia.org/wiki/Whitespace_character">whitespace</a>.
But in this chapter, we are logging the contents of <i>input</i>,
and in chapter 18, we will be manipulating the contents,
making this newline character more of a nuisance.
So from now on, we will be stripping it off.
Credits to
<a href="https://stackoverflow.com/a/28462221/2485966">Tim Čas</a>
for the clever use of <i>strcspn</i>.
</li>
<li>Line 17:
the
<a href="https://en.wikipedia.org/wiki/Static_variable">static variable</a>
<i>fp</i> represents the source of input; it is either
<a href="https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)">stdin</a>
(the program takes commands from the keyboard)
or a file containing the list of commands entered in earlier sessions.
</li>
<li>Line 19-23:
the first time <i>getInput</i> is called (<i>fp</i> is still NULL),
<i>fp</i> will be set,
either to a file or (if no filename was specified) to <i>stdin</i>.
In case of a file, it will be opened now for reading.
</li>
<li>Line 24-32:
when reading input from <i>stdin</i>,
each new command entered by the user will be logged
(if a filename was specified).
Notes:
<ul>
<li>‘Old’ commands read from file
do not need to be written to file again.
</li>
<li>The command logged here is the one entered by the user
during the <i>previous</i> call to <i>getInput</i>.
We deliberately do this
so we will not log commands that cause the program to terminate
(both ‘quit’ and any commands that trigger a crash).
</li>
<li>The ‘else’ at the start of line 24
causes logging to be skipped during the first call of <i>getInput</i>.
This prevents the initial ‘look around’ (line 6)
from being logged.
</li>
<li>We are writing to
the same file that was opened earlier to read input from (line 21).
But writing never starts until
after the file was closed after having been read completely (line 43).
So we need to open it again; this time for ‘appending’
(i.e. writing at the end of the file).
</li>
<li>The file is closed each time we have written a command;
no need to keep it open while the user is thinking about his next move.
If you insist on keeping it open
(to prevent other processes from messing with the file),
then I suggest you flush after each command written.
</li>
</ul>
</li>
<li>Line 34:
here we try to read a command, either from file or from keyboard.
</li>
<li>Line 37-40:
after sucessfully reading a command from file,
we echo the command to screen.
That way, the user will get to see the full dialog of earlier sessions.
</li>
<li>Line 41-45:
once the input file reaches
<a href="https://en.wikipedia.org/wiki/End-of-file">EOF</a>,
we switch over to manual input.
We close the file, set <i>fp</i> to <i>stdin</i>,
and read again using the new file pointer.
</li>
<li>Lines 50 and 54:
the name of the file must be passed as the first argument
when calling the program.
If no name is given, then argv[1] will be NULL.
</li>
<li>Line 52:
dummy statement
to suppress a compiler warning on unused parameter <i>argc</i>.
</li>
</ul>
</div>
<p>
And in case you hadn't noticed:
this is the first time since chapter 2,
that we are making changes to <i>main.c</i>!
</p>
<hr />
<table class="download"><tr><td>
<a class="button" href="code16/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="htpataic17.html">17. Test automation</a>
</p>
</body>
</html>