* better
[mascara-docs.git] / lang / C / the.ansi.c.programming.language / notes.accompany.ansi.c / homework / PS7.html
blobc02169f31e1f107830fcc8d297a856216ad24535
1 <!DOCTYPE HTML PUBLIC "-//W3O//DTD W3 HTML 2.0//EN">
2 <html>
3 <head>
4 <link rev="owner" href="mailto:scs@eskimo.com">
5 <link rev="made" href="mailto:scs@eskimo.com">
6 <title>Assignment #7</title>
7 </head>
8 <body>
9 <H1>Assignment #7</H1>
15 <B>Intermediate C Programming
16 <br>
17 <br>
18 UW Experimental College
19 </B><br>
20 <br>
21 <B>Assignment #7
22 </B><p><B>Handouts:
23 </B></p><p><a href="PS7.html">Assignment #7</a>
24 <br><a href="PS6a.html">Assignment #6 Answers</a>
25 <br><a href="http://www.eskimo.com/~scs/cclass/int/sx11.html">Class Notes, Chapter 25</a>
26 <p><B>Exercises:
27 </B></p><OL><li>Adding new command verbs to the game quickly becomes cumbersome.
28 As long as they all go into <TT>commands.c</TT>, they can
29 potentially be invoked on any object and with any tool.
30 So if a new command is (or ought to be) specific to some tool
31 or direct object,
32 the code to implement it has to spend most of its time
33 verifying that the overcreative player isn't trying to apply
34 the new command verb in some wildly imaginative but utterly
35 inappropriate way (``sharpen gum with feather'';
36 ``pierce idea with boot''; etc.).
37 It would be far better,
38 in several respects,
39 if scraps of code,
40 for implementing certain command verbs, could be attached
41 directly to objects,
42 so that they would only be invoked if those objects were involved
43 (and, additionally, so that the same verb
44 might possibly
45 perform different
46 actions if applied to different objects).
47 The notion of attaching code to data structures is one
48 of the fundamentals of <dfn>Object Oriented Programming</dfn>,
49 and it's a curious coincidence (or is it?)
50 that the data structures which in this program we'd like to
51 attach code to are in fact what we call ``objects.''
52 <br>
53 <br>
54 We're going
55 to add a new field,
56 of type pointer-to-function,
57 to our <TT>struct object</TT>.
58 This field will contain a pointer to a per-object function
59 which handles one or more command verbs unique to that object.
60 (If an object has no unique verbs it wishes to process,
61 its function pointer will be null.)
62 Here is the new field for <TT>struct object</TT> in <TT>game.h</TT>:
63 <pre>
64 int (*func)(struct actor *, struct object *, struct sentence *);
65 </pre>
66 Notice that <TT>func</TT>
67 takes the same arguments as all the other command functions
68 which we split <TT>commands.c</TT> up into last week.
69 <br>
70 <br>
71 If an object contains some custom verb-handling code,
72 it will be called from
73 <TT>docommand</TT> in
74 <TT>commands.c</TT>.
75 Before trying all of the default commands,
76 it first sees if either of the two objects which a command might reference
77 (the direct object, or the object of the preposition)
78 has its own function.
79 If so, it calls that (those) function(s).
80 <br>
81 <br>
82 Now, those functions might or might not know how to handle the
83 particular verb the user typed.
84 If not,
85 control should flow through to the default, global command functions.
86 (For example, you can still <B>take</B> or <B>drop</B> or <B>examine</B>
87 just about any object,
88 even if it has its own special code for when you try to
89 do other things with it,
90 and
91 the object's function
92 shouldn't have to--and couldn't--repeat
93 all the other code for all the other verbs.)
94 To coordinate things, then,
95 each function that tries to implement one of the user's commands
96 can return various status codes.
97 <br>
98 <br>
99 There are four status codes
100 (which go in <TT>game.h</TT>):
101 <pre>
102 #define FAILURE 0 /* command completed unsuccessfully */
103 #define SUCCESS 1 /* command completed successfully */
104 #define CONTINUE 2 /* command not completed */
105 #define ERROR 3 /* internal error */
106 </pre>
107 <TT>FAILURE</TT> means that the command completed, but unsuccessfully
108 (the player couldn't do what he tried to do).
109 <TT>SUCCESS</TT> means that the command completed, successfully.
110 <TT>CONTINUE</TT> means that the the function which was just called
111 did not implement the command,
112 and that the game system (i.e. the <TT>docommand</TT> function)
113 should keep trying, by calling other functions (if any).
114 Finally, <TT>ERROR</TT> indicates an internal error of some kind.
115 <br>
116 <br>
117 Here
118 is the new code for <TT>docommand</TT>.
119 This goes at the top of the function,
120 before it calls <TT>findcmd</TT>:
121 <pre>
122 if(cmd-&gt;object != NULL &amp;&amp; cmd-&gt;object-&gt;func != NULL)
124 r = (*cmd-&gt;object-&gt;func)(player, cmd-&gt;object, cmd);
125 if(r != CONTINUE)
126 return r;
129 if(cmd-&gt;xobject != NULL &amp;&amp; cmd-&gt;xobject-&gt;func != NULL)
131 r = (*cmd-&gt;xobject-&gt;func)(player, cmd-&gt;xobject, cmd);
132 if(r != CONTINUE)
133 return r;
135 </pre>
136 <br>
137 <br>
138 You'll also
139 need to add
140 the line
141 <pre>
142 objp-&gt;func = NULL;
143 </pre>
144 to the <TT>newobject</TT> function in <TT>object.c</TT>.
145 <br>
146 <br>
147 Next we'll need some actual functions
148 (special-purpose ones)
149 for various objects to use.
150 Here are a few examples:
151 <pre>
152 int hammerfunc(struct actor *player, struct object *objp,
153 struct sentence *cmd)
155 if(strcmp(cmd-&gt;verb, "break") != 0)
156 return CONTINUE;
157 if(objp != cmd-&gt;xobject)
158 return CONTINUE;
160 if(cmd-&gt;object == NULL)
162 printf("You must tell me what to break.\n");
163 return FAILURE;
165 if(!contains(player-&gt;contents, cmd-&gt;xobject))
167 printf("You have no %s.\n", cmd-&gt;xobject-&gt;name);
168 return FAILURE;
171 setattr(cmd-&gt;object, "broken");
172 printf("Oh, dear. Now the %s is broken.\n", cmd-&gt;object-&gt;name);
174 return SUCCESS;
177 int toolfunc(struct actor *player, struct object *objp,
178 struct sentence *cmd)
180 if(strcmp(cmd-&gt;verb, "fix") != 0)
181 return CONTINUE;
182 if(objp != cmd-&gt;xobject)
183 return CONTINUE;
185 if(cmd-&gt;object == NULL)
187 printf("You must tell me what to fix.\n");
188 return FAILURE;
190 if(!hasattr(cmd-&gt;object, "broken"))
192 printf("The %s is not broken.\n", cmd-&gt;object-&gt;name);
193 return FAILURE;
195 if(!contains(player-&gt;contents, cmd-&gt;xobject))
197 printf("You have no %s.\n", cmd-&gt;xobject-&gt;name);
198 return FAILURE;
201 unsetattr(cmd-&gt;object, "broken");
202 printf("Somehow you manage to fix the %s.\n", cmd-&gt;object-&gt;name);
204 return SUCCESS;
207 int cutfunc(struct actor *player, struct object *objp,
208 struct sentence *cmd)
210 if(strcmp(cmd-&gt;verb, "cut") != 0)
211 return CONTINUE;
212 if(objp != cmd-&gt;xobject)
213 return CONTINUE;
215 if(cmd-&gt;object == NULL)
217 printf("You must tell me what to cut.\n");
218 return FAILURE;
220 if(!contains(player-&gt;contents, cmd-&gt;xobject))
222 printf("You have no %s.\n", cmd-&gt;xobject-&gt;name);
223 return FAILURE;
225 if(!hasattr(cmd-&gt;object, "soft"))
227 printf("I don't think you can cut the %s with the %s.\n",
228 cmd-&gt;object-&gt;name, cmd-&gt;xobject-&gt;name);
229 return FAILURE;
232 printf("The %s is now cut in two.\n", cmd-&gt;object-&gt;name);
234 return SUCCESS;
237 int playfunc(struct actor *player, struct object *objp,
238 struct sentence *cmd)
240 if(strcmp(cmd-&gt;verb, "play") != 0)
241 return CONTINUE;
242 if(objp != cmd-&gt;object)
243 return CONTINUE;
245 if(!hasattr(cmd-&gt;object, "immobile"))
247 if(!contains(player-&gt;contents, cmd-&gt;object))
249 printf("You don't have the %s.\n", cmd-&gt;object-&gt;name);
250 return FAILURE;
254 printf("You're not quite ready for Carnegie hall,\n");
255 printf("but you do manage to force out a recognizable tune.\n");
257 return SUCCESS;
259 </pre>
260 I put these functions in a new source file, <TT>objfuncs.c</TT>.
261 (It is not on the disk.)
262 <br>
263 <br>
264 The first three of these are basically simplifications
265 of the <TT>breakcmd</TT>, <TT>fixcmd</TT>, and <TT>cutcmd</TT> functions
266 (or, at any rate,
267 the code for ``break'', ``fix'', and ``cut'')
268 from <TT>commands.c</TT>.
269 Since these functions will be attached to certain objects,
270 and called only when those objects are onvolved in the command,
271 these functions do not have to do quite so much checking.
272 For example, <TT>hammerfunc</TT> does not have to
273 make sure that the implement being used is heavy,
274 because <TT>hammerfunc</TT> will only be attached to the hammer
275 (or perhaps to other objects
276 which it might be appropriate to smash things with).
277 <br>
278 <br>
279 However,
280 there are two new things these functions do need to check.
281 Since they will be called first,
282 before the default functions in <TT>commands.c</TT>,
283 they must defer to those default functions for most verbs.
284 They must check to see that they're being called for a verb they recognize,
285 and if not, return the status code <TT>CONTINUE</TT>
286 indicating that some other code down the line should keep trying.
287 Also, these functions can be called
288 when the object they're attached to is either
289 the direct object of the sentence
290 <em>or</em> the object of a preposition.
291 When one of these functions is called
292 for an object which appears as the direct object,
293 the <TT>objp</TT> parameter is a copy of <TT>cmd-&gt;object</TT>,
294 as before.
295 When they're called for objects which are objects of prepositions,
296 however,
297 <TT>objp</TT> is a copy of that object pointer
298 (see the new code for <TT>docommand</TT> above).
299 So these commands must typically check
300 that they're being used in the way they expect--for example,
301 if <TT>hammerfunc</TT> didn't check to make sure that it was being used
303 as a prepositional object,
304 it might incorrectly attempt to handle a nonsense sentence like
305 <pre>
306 break hammer
307 </pre>
308 <br>
309 <br>
310 The next step is to hook these functions up to the relevant objects.
311 To do that, we'll need to invent
312 a new object descriptor line for the data file,
313 and add some new code to <TT>readdatafile</TT> in <TT>io.c</TT> to parse it.
314 We'll rig it up so that we can say
315 <pre>
316 object hammer
317 attribute heavy
318 func hmmerfunc
319 </pre>
320 (We'll keep the attribute ``heavy'' on the hammer,
321 because it might be useful elsewhere,
322 although we won't be needing it
323 for the old <TT>breakcmd</TT> function any more.)
324 The new code for <TT>io.c</TT> is pretty simple,
325 but before we can write it,
326 we have to remember that there's a significant distinction between
327 the name by which we know a function
328 and the internal address,
329 or function pointer,
330 by which the compiler knows it.
331 When we write
332 <pre>
333 func hammerfunc
334 </pre>
335 in the data file,
336 it's obvious to <em>us</em>
337 that we want the hammer object's <TT>func</TT> pointer
338 to be hooked up to <TT>hammerfunc</TT>,
339 but it is not at all obvious to the compiler.
340 In particular, the compiler is not going to be looking at our data file
341 at all!
342 Code in <TT>io.c</TT> is going to be looking at the data file,
343 and it's going to be doing it as the program is running,
344 well
345 after the compiler has finished its work.
346 So we're going to have to ``play compiler'' just a bit,
347 to match up the name of a function
348 with the function itself.
349 <br>
350 <br>
351 This will actually be rather simple to do,
352 because matching up a name
353 (i.e. a string)
354 with a function is exactly what the <TT>struct cmdtab</TT> structure
355 and <TT>findcmd</TT> function do.
356 So if we build a second array of <TT>cmdtab</TT> structures,
357 holding the names and function pointers
358 for functions which it's appropriate to attach to objects,
359 all we have to add to <TT>readdatafile</TT>
360 is this scrap of code:
361 <pre>
362 else if(strcmp(av[0], "func") == 0)
364 struct cmdtab *cmdtp = findcmd(av[1], objfuncs, nobjfuncs);
365 if(cmdtp != NULL)
366 currentobject-&gt;func = cmdtp-&gt;func;
367 else fprintf(stderr, "unknown object func %s\n", av[1]);
369 </pre>
370 The new array of <TT>cmdtab</TT> structures is called <TT>objfuncs</TT>.
371 I chose to define it at the end of the new source file <TT>objfuncs.c</TT>:
372 <pre>
373 struct cmdtab objfuncs[] =
375 "hammerfunc", hammerfunc,
376 "toolfunc", toolfunc,
377 "cutfunc", cutfunc,
378 "playfunc", playfunc,
381 #define Sizeofarray(a) (sizeof(a) / sizeof(a[0]))
383 int nobjfuncs = Sizeofarray(objfuncs);
384 </pre>
385 So that <TT>readdatafile</TT> can access the <TT>objfuncs</TT> array,
386 we need these two lines at the top of <TT>io.c</TT>:
387 <pre>
388 extern struct cmdtab objfuncs[];
389 extern int nobjfuncs;
390 </pre>
391 An explanation of the <TT>nobjfuncs</TT> variable is in order.
392 When we called <TT>findcmd</TT> in <TT>commands.c</TT>
393 to search our main list of commends,
394 the call looked like
395 <pre>
396 cmdtp = findcmd(cmd-&gt;verb, commands, Sizeofarray(commands));
397 </pre>
398 But our little <TT>Sizeofarray()</TT> macro uses <TT>sizeof</TT>,
399 and <TT>sizeof</TT> works
400 (rather obviously)
401 only for objects which the compiler knows the size of.
402 In <TT>io.c</TT>,
403 where we have
404 <pre>
405 extern struct cmdtab objfuncs[];
406 </pre>
407 the code
408 <TT>sizeof(objfuncs)</TT> would not work,
409 because in that source file,
410 all the compiler knows about <TT>objfuncs</TT>
411 is that it is an array which is defined
412 (and which therefore has its size set)
413 somewhere else.
414 So we need to do
415 the computation of the number of elements in the array
416 within the source file <TT>objfuncs.c</TT>
417 where the array is defined,
418 and store this number in a second global variable, <TT>nobjfuncs</TT>,
419 so that code in <TT>io.c</TT> can get its hands on it.
420 <br>
421 <br>
422 Having made these changes,
423 you should be able to add the line
424 <pre>
425 func hammerfunc
426 </pre>
427 to the description of the hammer in <TT>dungeon.dat</TT>,
428 and the line
429 <pre>
430 func toolfunc
431 </pre>
432 to the description of the pliers or some other tool-like object,
433 and the line
434 <pre>
435 func cutfunc
436 </pre>
437 to the description of the knife or some other sharp object.
438 You can also add the lines
439 <pre>
440 object violin
441 func playfunc
442 object end
444 object piano
445 attribute immobile
446 func playfunc
447 object end
448 </pre>
449 to try out the sample ``play'' function.
450 Notice that <TT>playfunc</TT> makes you
451 pick up the violin before playing it,
452 but since the piano is immobile,
453 you can just walk up to it and start playing.
454 <li>Rewrite the <TT>lockcmd</TT> and <TT>unlockcmd</TT> functions
455 (from Assignment 4)
456 as object functions.
457 <li>Invent some more object functions and some objects for them to be attached to.
458 Examples:
459 weapons,
460 other tools,
461 magic wands...
462 <li>Since object functions (if present)
463 are called before the default functions in <TT>commands.c</TT>,
464 they can not only supplement the commands in <TT>commands.c</TT>,
465 they can also override (on a per-object basis)
466 the default behavior of an existing verb.
467 Invent a shower object.
468 (If your dungeon doesn't already have a bathroom,
469 perhaps you'll need to add that, too.)
470 Rig it up so that if the player types
471 <pre>
472 take shower
473 </pre>
474 the game will print neither ``Taken''
475 nor ``The shower cannot be picked up''
476 but rather something cute like
477 ``After spending 20 luxurious minutes in the shower,
478 singing three full-length show tunes,
479 and using up all the hot water,
480 you emerge clean and refreshed.''
481 <li>Since per-object functions are called before default functions,
482 and since they can defer to those default functions
483 by returning <TT>CONTINUE</TT>,
484 another kind of customization is possible.
485 A per-object function can recognize a verb,
486 do some processing based upon it,
487 but then return <TT>CONTINUE</TT>,
488 so that the default machinery will also act.
489 Implement a magic sword object
490 so that when the player picks it up,
491 the messages
492 ``As you pick it up,
493 the sword briefly glows blue''
494 and ``Taken''
495 are printed.
496 The first message is printed by the sword's custom function,
497 and the second one by the normal ``take'' machinery in <TT>commands.c</TT>.
498 <li>Implement a container of radioactive waste.
499 Arrange that if the player tries to pick it up,
500 the message ``the container is far too dangerous to pick up''
501 is printed,
502 <em>unless</em>
503 the player is wearing a Hazmat suit,
504 in which case the container can be picked up normally.
505 Also implement the Hazmat suit object.
506 (You can either just have the player pick up the Hazmat suit,
507 or for extra credit,
508 arrange that the Hazmat suit implement a custom ``wear'' verb.)
509 </OL><hr>
510 <hr>
512 This page by <a href="http://www.eskimo.com/~scs/">Steve Summit</a>
513 // <a href="copyright.html">Copyright</a> 1995-9
514 // <a href="mailto:scs@eskimo.com">mail feedback</a>
515 </p>
516 </body>
517 </html>