-
Notifications
You must be signed in to change notification settings - Fork 6
/
htpataic11.html
604 lines (593 loc) · 22.6 KB
/
htpataic11.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
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
<!DOCTYPE html>
<html>
<head>
<title>11. Conditions</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><b>11. Conditions</b></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><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:[email protected]">[email protected]</a>>
</p>
<p>
Licensed under
<a href="https://github.com/helderman/htpataic/blob/master/LICENSE">MIT License</a>
</p>
<h2>11. Conditions</h2>
<p class="intro">
So far, all the objects’ attributes were data: text, numbers.
But attributes may just as well be code.
</p>
<p>
In the previous chapter, we limited the player’s freedom of movement
by closing the cave entrance (passage <i>intoCave</i>).
This already makes the game a lot more challenging,
but it does not make much of a puzzle,
unless we offer a tiny possibility for the player to <b>open the passage</b>.
The real challenge should be for the player
to find the <b>condition</b> under which the passage opens.
</p>
<p>
Let’s take a simple example.
To get past the guard and enter the cave,
the player has to either kill or bribe the guard
(or both, for what it’s worth).
In other words:
<ul>
<li>when the guard is dead (health = 0), the entrance is <b>open</b></li>
<li>when the guard is holding the silver coin, the entrance is <b>open</b></li>
<li>when neither is the case, the entrance is <b>closed</b></li>
</ul>
</p>
<p>
Opening a closed passage (in this case <i>intoCave</i>)
involves changing a number of attribute values:
<ul>
<li><b>destination</b>
changes from <i>NULL</i> to <i>cave</i>
</li>
<li><b>textGo</b>
changes from
<i>‘the guard stops you...’</i>
to
<i>‘you walk into the cave’</i>
</li>
<li><b>description</b> and <b>detail</b>
need not change in this particular case;
but for a doorway or grating,
either one (or both) would typically contain some text that changes
from <i>‘open’</i> to <i>‘closed’</i>
</li>
</ul>
</p>
<p>
There are a number of ways to accomplish this.
Here, I will discuss an approach that is simple, maintainable and versatile.
</p>
<p>
First of all, we define two separate passages:
one that represents the <i>open</i> passage,
and the other representing the <i>closed</i> passage.
The passages are identical in every attribute except for the ones listed above.
(In the generated map you see below,
notice the two arrows leading into the cave; one solid, one dashed.)
</p>
<p>
Next, we introduce a new attribute named <b>condition</b>
that determines whether or not a certain object exists.
The two passages will be given
<a href="http://en.wikipedia.org/wiki/Mutually_exclusive#Logic">mutually exclusive</a>
conditions, so that only one of them can exist at any given time.
</p>
<p>
Each condition will be implemented as a
<a href="http://en.wikipedia.org/wiki/Boolean-valued_function">boolean-valued function</a>;
<i>true</i> means the object exists, <i>false</i> means it does not.
</p>
<img class="genmap" src="code11/map.png" />
<table><tr>
<td class="snippet">bool intoCaveIsOpen(void)
{
return guard->health == 0 || silver->location == guard;
}
bool intoCaveIsClosed(void)
{
return guard->health > 0 && silver->location != guard;
}
</td>
</tr></table>
<p>
The new attribute <i>condition</i> is a pointer to such a function:
</p>
<table><tr>
<td class="snippet">bool (*condition)(void);
</td>
</tr></table>
<p>
After some small modifications to <i>object.awk</i>,
similar to those made in the previous chapter,
we can immediately start assigning functions to the new attribute
in <i>object.txt</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 />
<br />
--> go entrance<br />
The guard stops you from walking into the cave.<br />
<br />
--> get coin<br />
You pick up a silver coin.<br />
<br />
--> give coin<br />
You give a silver coin to a burly guard.<br />
<br />
--> go entrance<br />
You walk into the cave.<br />
<br />
You are in a little cave.<br />
You see:<br />
a gold coin<br />
an exit to the west<br />
solid rock all around<br />
<br />
--> quit<br />
<br />
Bye!<br />
</td></tr>
</table>
<table><tr>
<td class="snippet">- intoCave
condition <span class="red">intoCaveIsOpen</span>
description "a cave entrance to the east"
tags "east", "entrance"
location field
<span class="red">destination</span> cave
detail "The entrance is just a narrow opening in a small outcrop.\n"
textGo <span class="red">"You walk into the cave.\n"</span>
- intoCaveBlocked
condition <span class="red">intoCaveIsClosed</span>
description "a cave entrance to the east"
tags "east", "entrance"
location field
<span class="red">prospect</span> cave
detail "The entrance is just a narrow opening in a small outcrop.\n"
textGo <span class="red">"The guard stops you from walking into the cave.\n"</span>
</td>
</tr></table>
<p>
The two ‘condition’ functions are so specific,
each of them is used just this once.
Now, wouldn’t it be nice to define the functions right where we need them?
Many programming languages support
<a href="http://en.wikipedia.org/wiki/Anonymous_function">anonymous functions</a>;
something like this:
</p>
<table><tr>
<td class="snippet">- intoCave
condition <span class="red">{ return guard->health == 0 || silver->location == guard; }</span>
...
- intoCaveBlocked
condition <span class="red">{ return guard->health > 0 && silver->location != guard; }</span>
...
</td>
</tr></table>
<p>
Plain C does not allow this,
but since <i>object.txt</i> is a product of our own
<a href="http://en.wikipedia.org/wiki/Domain-specific_language">domain-specific language</a>
(see chapter 9),
we can do anything we like!
That is, if we can make the code generator
turn it into something the C compiler will swallow.
The following adjustments to <i>object.awk</i> will do just that.
</p>
<table class="code"><tr>
<th>object.awk</th>
</tr><tr>
<td>
<ol>
<li class="old">BEGIN {</li>
<li class="old"> count = 0;</li>
<li class="old"> obj = "";</li>
<li class="old"> if (pass == "c2") {</li>
<li class="new"> print "\nstatic bool alwaysTrue(void) { return true; }";</li>
<li class="old"> print "\nOBJECT objs[] = {";</li>
<li class="old"> }</li>
<li class="old">}</li>
<li class="old"></li>
<li class="old">/^- / {</li>
<li class="old"> outputRecord(",");</li>
<li class="old"> obj = $2;</li>
<li class="new"> prop["condition"] = "alwaysTrue";</li>
<li class="old"> prop["description"] = "NULL";</li>
<li class="old"> prop["tags"] = "";</li>
<li class="old"> prop["location"] = "NULL";</li>
<li class="old"> prop["destination"] = "NULL";</li>
<li class="old"> prop["prospect"] = "";</li>
<li class="old"> prop["details"] = "\"You see nothing special.\"";</li>
<li class="old"> prop["contents"] = "\"You see\"";</li>
<li class="old"> prop["textGo"] = "\"You can't get much closer than this.\"";</li>
<li class="old"> prop["weight"] = "99";</li>
<li class="old"> prop["capacity"] = "0";</li>
<li class="old"> prop["health"] = "0";</li>
<li class="old">}</li>
<li class="old"></li>
<li class="old">obj && /^[ \t]+[a-z]/ {</li>
<li class="old"> name = $1;</li>
<li class="old"> $1 = "";</li>
<li class="old"> if (name in prop) {</li>
<li class="old"> prop[name] = $0;</li>
<li class="new"> if (/^[ \t]*\{/) {</li>
<li class="new"> prop[name] = name count;</li>
<li class="new"> if (pass == "c1") print "static bool " prop[name] "(void) " $0;</li>
<li class="new"> }</li>
<li class="old"> }</li>
<li class="old"> else if (pass == "c2") {</li>
<li class="old"> print "#error \"" FILENAME " line " NR ": unknown attribute '" name "'\"";</li>
<li class="old"> }</li>
<li class="old">}</li>
<li class="old"></li>
<li class="old">!obj && pass == (/^#include/ ? "c1" : "h") {</li>
<li class="old"> print;</li>
<li class="old">}</li>
<li class="old"></li>
<li class="old">END {</li>
<li class="old"> outputRecord("\n};");</li>
<li class="old"> if (pass == "h") {</li>
<li class="old"> print "\n#define endOfObjs\t(objs + " count ")";</li>
<li class="new"> print "\n#define validObject(obj)\t" \</li>
<li class="new"> "((obj) != NULL && (*(obj)->condition)())";</li>
<li class="old"> }</li>
<li class="old">}</li>
<li class="old"></li>
<li class="old">function outputRecord(separator)</li>
<li class="old">{</li>
<li class="old"> if (obj) {</li>
<li class="old"> if (pass == "h") {</li>
<li class="old"> print "#define " obj "\t(objs + " count ")";</li>
<li class="old"> }</li>
<li class="old"> else if (pass == "c1") {</li>
<li class="old"> print "static const char *tags" count "[] = {" prop["tags"] ", NULL};";</li>
<li class="old"> }</li>
<li class="old"> else if (pass == "c2") {</li>
<li class="old"> print "\t{\t/* " count " = " obj " */";</li>
<li class="new"> print "\t\t" prop["condition"] ",";</li>
<li class="old"> print "\t\t" prop["description"] ",";</li>
<li class="old"> print "\t\ttags" count ",";</li>
<li class="old"> print "\t\t" prop["location"] ",";</li>
<li class="old"> print "\t\t" prop["destination"] ",";</li>
<li class="old"> print "\t\t" prop[prop["prospect"] ? "prospect" : "destination"] ",";</li>
<li class="old"> print "\t\t" prop["details"] ",";</li>
<li class="old"> print "\t\t" prop["contents"] ",";</li>
<li class="old"> print "\t\t" prop["textGo"] ",";</li>
<li class="old"> print "\t\t" prop["weight"] ",";</li>
<li class="old"> print "\t\t" prop["capacity"] ",";</li>
<li class="old"> print "\t\t" prop["health"];</li>
<li class="old"> print "\t}" separator;</li>
<li class="old"> delete prop;</li>
<li class="old"> }</li>
<li class="old"> count++;</li>
<li class="old"> }</li>
<li class="old">}</li>
</ol>
</td>
</tr></table>
<div class="explanation">
<p>
Explanation:
</p>
<ul>
<li>Line 5, 13:
most objects are unconditional;
their condition is a trivial function that always returns <i>true</i>.
</li>
<li>Line 32-35:
an anonymous function (anything starting with a curly brace)
is replaced by a function with a generated name;
the name is the attribute name (in this case <i>condition</i>)
followed by a number (the object count).
This technique is similar to the way we handle tags (lines 62 and 68).
</li>
</ul>
</div>
<p>
So now we can add the extra passage and the conditions to <i>object.txt</i>,
as explained earlier.
</p>
<table class="code"><tr>
<th>object.txt</th>
</tr><tr>
<td>
<ol>
<li class="new">#include <stdbool.h></li>
<li class="old">#include <stdio.h></li>
<li class="old">#include "object.h"</li>
<li class="old"></li>
<li class="old">typedef struct object {</li>
<li class="new"> bool (*condition)(void);</li>
<li class="old"> const char *description;</li>
<li class="old"> const char **tags;</li>
<li class="old"> struct object *location;</li>
<li class="old"> struct object *destination;</li>
<li class="old"> struct object *prospect;</li>
<li class="old"> const char *details;</li>
<li class="old"> const char *contents;</li>
<li class="old"> const char *textGo;</li>
<li class="old"> int weight;</li>
<li class="old"> int capacity;</li>
<li class="old"> int health;</li>
<li class="old">} OBJECT;</li>
<li class="old"></li>
<li class="old">extern OBJECT objs[];</li>
<li class="old"></li>
<li class="old">- field</li>
<li class="old"> description "an open field"</li>
<li class="old"> tags "field"</li>
<li class="old"> details "The field is a nice and quiet place under a clear blue sky."</li>
<li class="old"> capacity 9999</li>
<li class="old"></li>
<li class="old">- cave</li>
<li class="old"> description "a little cave"</li>
<li class="old"> tags "cave"</li>
<li class="old"> details "The cave is just a cold, damp, rocky chamber."</li>
<li class="old"> capacity 9999</li>
<li class="old"></li>
<li class="old">- silver</li>
<li class="old"> description "a silver coin"</li>
<li class="old"> tags "silver", "coin", "silver coin"</li>
<li class="old"> location field</li>
<li class="old"> details "The coin has an eagle on the obverse."</li>
<li class="old"> weight 1</li>
<li class="old"></li>
<li class="old">- gold</li>
<li class="old"> description "a gold coin"</li>
<li class="old"> tags "gold", "coin", "gold coin"</li>
<li class="old"> location cave</li>
<li class="old"> details "The shiny coin seems to be a rare and priceless artefact."</li>
<li class="old"> weight 1</li>
<li class="old"></li>
<li class="old">- guard</li>
<li class="old"> description "a burly guard"</li>
<li class="old"> tags "guard", "burly guard"</li>
<li class="old"> location field</li>
<li class="old"> details "The guard is a really big fellow."</li>
<li class="old"> contents "He has"</li>
<li class="old"> health 100</li>
<li class="old"> capacity 20</li>
<li class="old"></li>
<li class="old">- player</li>
<li class="old"> description "yourself"</li>
<li class="old"> tags "yourself"</li>
<li class="old"> location field</li>
<li class="old"> details "You would need a mirror to look at yourself."</li>
<li class="old"> contents "You have"</li>
<li class="old"> health 100</li>
<li class="old"> capacity 20</li>
<li class="old"></li>
<li class="old">- intoCave</li>
<li class="new"> condition { return guard->health == 0 || silver->location == guard; }</li>
<li class="new"> description "a cave entrance to the east"</li>
<li class="new"> tags "east", "entrance"</li>
<li class="new"> location field</li>
<li class="new"> destination cave</li>
<li class="new"> details "The entrance is just a narrow opening in a small outcrop."</li>
<li class="new"> textGo "You walk into the cave."</li>
<li class="new"></li>
<li class="new">- intoCaveBlocked</li>
<li class="new"> condition { return guard->health > 0 && silver->location != guard; }</li>
<li class="old"> description "a cave entrance to the east"</li>
<li class="old"> tags "east", "entrance"</li>
<li class="old"> location field</li>
<li class="old"> prospect cave</li>
<li class="old"> details "The entrance is just a narrow opening in a small outcrop."</li>
<li class="old"> textGo "The guard stops you from walking into the cave."</li>
<li class="old"></li>
<li class="old">- exitCave</li>
<li class="old"> description "an exit to the west"</li>
<li class="old"> tags "west", "exit"</li>
<li class="old"> location cave</li>
<li class="old"> destination field</li>
<li class="old"> details "Sunlight pours in through an opening in the cave's wall."</li>
<li class="old"> textGo "You walk out of the cave."</li>
<li class="old"></li>
<li class="old">- wallField</li>
<li class="old"> description "dense forest all around"</li>
<li class="old"> tags "west", "north", "south", "forest"</li>
<li class="old"> location field</li>
<li class="old"> details "The field is surrounded by trees and undergrowth."</li>
<li class="old"> textGo "Dense forest is blocking the way."</li>
<li class="old"></li>
<li class="old">- wallCave</li>
<li class="old"> description "solid rock all around"</li>
<li class="old"> tags "east", "north", "south", "rock"</li>
<li class="old"> location cave</li>
<li class="old"> details "Carved in stone is a secret password 'abccb'."</li>
<li class="old"> textGo "Solid rock is blocking the way."</li>
</ol>
</td>
</tr></table>
<p>
To make the conditions work, we need to adjust functions
<i>isHolding</i> and <i>getDistance</i>.
</p>
<table class="code"><tr>
<th>misc.c</th>
</tr><tr>
<td>
<ol>
<li class="old">#include <stdbool.h></li>
<li class="old">#include <stdio.h></li>
<li class="old">#include "object.h"</li>
<li class="old">#include "misc.h"</li>
<li class="old"></li>
<li class="old">bool isHolding(OBJECT *container, OBJECT *obj)</li>
<li class="old">{</li>
<li class="new"><span class="old"> return </span>validObject(obj)<span class="old"> && obj->location == container;</span></li>
<li class="old">}</li>
<li class="old"></li>
<li class="old">OBJECT *getPassage(OBJECT *from, OBJECT *to)</li>
<li class="old">{</li>
<li class="old"> if (from != NULL && to != NULL)</li>
<li class="old"> {</li>
<li class="old"> OBJECT *obj;</li>
<li class="old"> for (obj = objs; obj < endOfObjs; obj++)</li>
<li class="old"> {</li>
<li class="old"> if (isHolding(from, obj) && obj->prospect == to)</li>
<li class="old"> {</li>
<li class="old"> return obj;</li>
<li class="old"> }</li>
<li class="old"> }</li>
<li class="old"> }</li>
<li class="old"> return NULL;</li>
<li class="old">}</li>
<li class="old"></li>
<li class="old">DISTANCE getDistance(OBJECT *from, OBJECT *to)</li>
<li class="old">{</li>
<li class="new"><span class="old"> return to == NULL ? distUnknownObject </span>:</li>
<li class="new"> !validObject(to) ? distNotHere<span class="old"> :</span></li>
<li class="old"> to == from ? distSelf :</li>
<li class="old"> isHolding(from, to) ? distHeld :</li>
<li class="old"> isHolding(to, from) ? distLocation :</li>
<li class="old"> isHolding(from->location, to) ? distHere :</li>
<li class="old"> isHolding(from, to->location) ? distHeldContained :</li>
<li class="old"> isHolding(from->location, to->location) ? distHereContained :</li>
<li class="old"> getPassage(from->location, to) != NULL ? distOverthere :</li>
<li class="old"> distNotHere;</li>
<li class="old">}</li>
<li class="old"></li>
<li class="old">OBJECT *actorHere(void)</li>
<li class="old">{</li>
<li class="old"> OBJECT *obj;</li>
<li class="old"> for (obj = objs; obj < endOfObjs; obj++)</li>
<li class="old"> {</li>
<li class="old"> if (isHolding(player->location, obj) && obj != player &&</li>
<li class="old"> obj->health > 0)</li>
<li class="old"> {</li>
<li class="old"> return obj;</li>
<li class="old"> }</li>
<li class="old"> }</li>
<li class="old"> return NULL;</li>
<li class="old">}</li>
<li class="old"></li>
<li class="old">int listObjectsAtLocation(OBJECT *location)</li>
<li class="old">{</li>
<li class="old"> int count = 0;</li>
<li class="old"> OBJECT *obj;</li>
<li class="old"> for (obj = objs; obj < endOfObjs; obj++)</li>
<li class="old"> {</li>
<li class="old"> if (obj != player && isHolding(location, obj))</li>
<li class="old"> {</li>
<li class="old"> if (count++ == 0)</li>
<li class="old"> {</li>
<li class="old"> printf("%s:\n", location->contents);</li>
<li class="old"> }</li>
<li class="old"> printf("%s\n", obj->description);</li>
<li class="old"> }</li>
<li class="old"> }</li>
<li class="old"> return count;</li>
<li class="old">}</li>
</ol>
</td>
</tr></table>
<p>
Notes:
</p>
<ul>
<li>Right now, there is no way the guard will die.
So you might say
the ‘health check’ in our condition functions is quite useless.
Of course, this is easily fixed by adding a <i>kill</i> command;
see chapter 20.
</li>
<li>The two condition functions are complementary; they qualify as
<a href="http://en.wikipedia.org/wiki/Duplicate_code">duplicate code</a>.
To eliminate that, we might decide to make one function call the other
(with the ‘!’ operator to negate the result).
An anonymous function has no (stable) name,
but we can refer to it by its object.
We could replace the condition function of <i>intoCaveBlocked</i> by:
<table><tr>
<td class="snippet">{ return <span class="red">!</span>(*intoCave->condition)(); }
</td>
</tr></table>
</li>
<li>For simplicity, the condition functions have no parameter.
It might actually be better to pass a parameter <i>OBJECT *obj</i>;
that makes it possible to write more generic condition functions
that can be re-used across multiple objects.
</li>
<li>In theory, any object can be made ‘conditional’.
In practice, its main benefit lies with passages.
But in the next chapter,
you can see a similar technique being applied to an item.
</li>
</ul>
<hr />
<table class="download"><tr><td>
<a class="button" href="code11/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="htpataic12.html">12. Open and close</a>
</p>
</body>
</html>