* remove "\r" nonsense
[mascara-docs.git] / C / the.ansi.c.programming.language / notes.accompany.ansi.c / homework / PS8.html
blobc3cfc8c51f8f83841edb1244caf103134ed19226
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 #8</title>
7 </head>
8 <body>
9 <H1>Assignment #8</H1>
15 <B>Intermediate C Programming
16 <br>
17 <br>
18 UW Experimental College
19 </B><br>
20 <br>
21 <B>Assignment #8
22 </B><p>This handout presents
23 several more
24 potential
25 additions and
26 improvements to the game,
27 not couched
28 so much
29 in terms of an assignment,
30 but more as food for thought.
31 </p><OL><li>Last week we added a <TT>func</TT> field to <TT>struct object</TT>,
32 so that an object could contain a pointer to a function
33 implementing actions specific to that object.
34 Another ability
35 this
37 gives us is to write new, object-specific code
38 which is intended to be fired up not directly,
39 in response to a user's typed command,
40 but rather, indirectly,
41 at other spots in the game where we're working with objects.
42 We'll do this by letting objects optionally define special ``verbs''
43 (with names beginning with periods, by convention)
44 which we'll ``call'' when we need to.
45 <br>
46 <br>
47 Suppose we wanted the name or description of an object
48 (printed in the listing of a room's contents or the actor's possessions,
49 or in response to the ``examine'' command)
50 to vary, depending on the state of the object.
51 (We've already done that, in a crude way,
52 by having the ``examine'' command
53 look at a few of the object attributes we've defined.)
54 For example,
55 we might want the player to be able to find an object
56 which is described only as
57 <pre>
58 a wad of rubberized fabric
59 </pre>
60 until the player also finds the ``air pump'' object,
61 and thinks to type
62 <pre>
63 inflate wad with pump
64 </pre>
65 at which point the object will be described as
66 <pre>
67 an inflatable boat
68 </pre>
69 To do this,
70 we'll let an object define a pseudoverb ``.list''.
71 Then, any time we would have
72 printed the object's <TT>name</TT> field in a list of objects,
73 we'll let it print its own name,
74 if it wants to.
75 For example,
76 the hypothetical rubber boat
77 might have object-specific code like this:
78 <pre>
79 int boatfunc(struct actor *actp, struct object *objp,
80 struct sentence *cmd)
82 if(strcmp(cmd-&gt;verb, ".list") == 0)
84 if(hasattr(objp, "inflated"))
85 printf("an inflatable boat");
86 else printf("a wad of rubberized fabric");
87 return SUCCESS;
90 return CONTINUE;
92 </pre>
93 When we're printing a list of objects,
94 we'll have to ``call'' the ``.list'' command
95 (more specifically,
96 call the object's <TT>func</TT>,
97 if it has one,
98 passing a command verb of ``.list'').
99 Setting up one of these special-purpose commands
100 as if it were a ``sentence'' the user typed
101 will be a tiny bit of a nuisance,
102 so we'll write an intermediate function to do it:
103 <pre>
104 int objcall(struct actor *actp, struct object *objp, char *command)
106 struct sentence cmd;
107 if(objp-&gt;func == NULL)
108 return ERROR;
109 cmd.verb = command;
110 cmd.object = objp;
111 cmd.preposition = NULL;
112 cmd.xobject = NULL;
113 return (*objp-&gt;func)(actp, objp, &amp;cmd);
115 </pre>
116 Now we can rewrite the object-listing code
117 in <TT>object.c</TT>.
118 Where it used to say
119 something like
120 <pre>
121 printf("%s\n", lp-&gt;name);
122 </pre>
123 it can now say
124 <pre>
125 /* if obj has a function, try letting it list itself */
126 if(lp-&gt;func != NULL &amp;&amp; objcall(actp, lp, ".list") == SUCCESS)
127 printf("\n");
128 else printf("%s\n", lp-&gt;name);
129 </pre>
130 If <TT>objcall</TT> returns SUCCESS,
131 the object has printed itself,
132 and all we have to do is append the trailing newline.
133 Otherwise, we print the object's name, as before.
134 <br>
135 <br>
136 (This modification to the object-listing code has one problem.
137 If the <TT>listobjects</TT> function in <TT>object.c</TT>
138 is going to call <TT>objcall</TT> in this way,
139 it needs a pointer to the actor,
140 but it doesn't have it.
141 So we're going to have to rewrite the <TT>listobjects</TT> function,
142 and each of the places it's called,
143 to pass the actor pointer as an additional argument.
144 As it happens,
145 two of
146 the other improvements suggested in this assignment
147 end up requiring exactly the same change.
148 It turns out that <TT>listobjects</TT>
149 probably should have been accepting the actor pointer
150 as an extra argument all along,
151 if there are so many good reasons why it ends up needing it.)
152 <br>
153 <br>
154 There are several other situations where we can use
155 custom, ``internal'' functions like these.
156 We can arrange that an object also be able to print
157 its long description,
158 by interpreting a ``.describe'' verb.
159 (We'd rewrite the ``examine'' command
160 to print an object's <TT>desc</TT> field
161 only if the object didn't succeed
162 at performing the ``.describe'' verb.)
163 We could also implement customized ``hooks''
164 into the action of picking up an object.
165 In last week's assignment
166 (exercise
169 we implemented an object with a custom ``take'' command
170 which printed some text
171 but then returned <TT>CONTINUE</TT>,
172 so that the rest of the default ``take'' machinery
173 would handle the actual picking up of the object.
174 But we might want the custom ``take'' action
175 (and any messages it prints)
176 to happen <em>after</em> the default machinery
177 has performed most of the picking up of the object.
178 To do this, we could define a new special verb ``.take2'',
179 and rewrite the ``take'' code in <TT>commands.c</TT>
180 to call .take2 at the very end,
181 after the call to <TT>takeobject</TT>.
182 <li>When we started writing object-specific functions last week,
183 we placed them in a file called <TT>objfuncs.c</TT>.
184 Although
185 (as we've begun to see)
186 it can be a fantastically powerful ability
187 to allow objects to ``point at''
188 arbitrarily-complicated functions written just for them,
189 in practice,
190 writing these functions will still be a nuisance.
191 Suppose
192 we've just been playing with the data file,
193 and we've come up with a fun new object to put in the dungeon for
194 the player to play with,
195 and the object needs some new, special-purpose code to make it do its stuff.
196 We'd have to write that code
197 (in C, of course),
198 and then
199 recompile the
200 whole game,
201 before we could
202 hook the new function
203 up to the new object.
204 The whole point of the data file is that the information about
205 objects (and the rest of the dungeon)
206 can be read out of it;
207 we stopped editing the source files to change the dungeon
208 way back during the first week,
209 when we introduced the data file in the first place.
210 <br>
211 <br>
212 So, another major leap will be
213 to put actual code
214 (not just a pointer to a function)
215 <em>in</em> objects.
216 This code will, however, not be written in C,
217 for a variety of reasons.
218 (A sufficient reason is
219 that there's no easy way to get the C
220 compiler to compile scraps of source code we're reading from a data file
221 and to attach the resultant object code to some data structures
222 in the already-running program.)
223 So, we'll take the (seemingly big, but it's not that bad) step
224 of defining our own little miniature language for describing
225 what objects can do,
226 writing an interpreter (not a compiler)
227 which reads and implements this language,
228 and then writing the code scraps for objects in the little language.
229 <br>
230 <br>
231 Therefore, we'll add
232 another field
233 to <TT>struct object</TT>,
234 right next to the <TT>func</TT> field we added last week:
235 <pre>
236 char *script;
237 </pre>
238 For objects which contain these new, ``scripted'' functions,
239 <TT>func</TT> will be a pointer to
240 the script-interpreting function
241 (one function, the same function, for all objects which are scripted),
243 the <TT>script</TT> field
244 will contain
245 the text of the script to be
246 interpreted.
247 <br>
248 <br>
249 First, a description of the little language.
250 It is optimized for the kinds of things that we want our object
251 functions
252 (at least the simple ones) to be able to do:
253 check attributes of the tool or direct object,
254 set attributes of the tool or direct object,
255 and print messages.
256 Here is a sample script:
257 <pre>
258 entry "break"
259 chktoolqual "heavy"
260 csetobjstate "broken"
261 message "It is broken."
262 return 1
263 </pre>
264 This is some code which might be suitable for the hammer object.
265 The <TT>entry</TT> line says that this is code for the verb ``break''.
266 (There can potentially be many <TT>entry</TT> lines,
267 if a tool can be used in multiple ways.)
268 The <TT>chktoolqual</TT> line makes sure that the tool has
269 the given attribute,
270 and prints an appropriate message (and fails) if it does not.
271 The <TT>csetobjstate</TT> <B>c</B>onditionally sets the state of
272 the direct object,
273 unless the object already has that state,
274 in which case it prints a message like ``The <I>x</I> is
275 already broken'', and fails.
276 Finally, the <TT>message</TT> line prints a message,
277 and the <TT>return</TT> line returns.
278 <br>
279 <br>
280 The code for the interpreter which will execute these little
281 scripts is too large to print out,
282 but it's in the <TT>week8</TT> subdirectory of the distributed
283 source code.
284 It interfaces with the rest of the game in two places.
285 We must arrange for the interpreter to be called at the right time(s),
286 but that's done already:
287 if an object's <TT>func</TT> pointer points to the new interpreter function,
288 the code we added to <TT>docommand</TT> last week
289 will call it.
290 But we must also
291 be able to read in a script
292 (from the data file)
293 along with the rest
294 of a description of an object.
295 Here is a new case for <TT>parsedatafile</TT>:
296 <pre>
297 else if(strcmp(av[0], "script") == 0)
299 /* XXX cumbersome un-getwords required */
300 int i, l = 0;
301 if(currentobject == NULL)
302 continue;
303 for(i = 1; i &lt; ac; i++)
304 l += strlen(av[i]) + 1;
305 currentobject-&gt;script = chkmalloc(l);
306 *currentobject-&gt;script = '\0';
307 for(i = 1; i &lt; ac; i++)
309 if(i &gt; 1)
310 strcat(currentobject-&gt;script, " ");
311 strcat(currentobject-&gt;script, av[i]);
314 currentobject-&gt;func = interp;
316 </pre>
317 The script is read all from one line
318 (which is a nuisance, but this is a preliminary implementation).
319 The long description reading code faced the same problem;
320 this code uses a different solution,
321 by un-doing the action of <TT>getwords</TT> and rebuilding a
322 single string
323 (in <TT>malloc</TT>'ed memory)
324 which the <TT>script</TT> field can point to.
325 <br>
326 <br>
327 Integrating the interpreter code requires paying attention to
328 a few other details.
329 You'll need
330 to add the prototype
331 <pre>
332 extern int interp(struct actor *, struct object *, struct sentence *);
333 </pre>
334 to <TT>game.h</TT> if it's not there already,
335 and the line
336 <pre>
337 objp-&gt;script = NULL;
338 </pre>
339 to the <TT>newobject</TT> function in <TT>object.c</TT>.
340 Also,
341 for the moment,
342 we'll have to pay attention to the numeric values
343 of the status codes
344 (<TT>SUCCESS</TT>, <TT>FAILURE</TT>, <TT>CONTINUE</TT>, <TT>ERROR</TT>)
345 we defined last week,
346 because the simple interpreter doesn't know how to handle
347 the symbolic names.
348 Instead, we'll
349 have to say <TT>return 1</TT> for <TT>SUCCESS</TT>, etc.
350 <li>This game
351 is a prime candidate for moving from C to C++.
352 Actors and rooms are really special cases of objects:
353 Rooms contain objects,
354 just like actors and container objects do,
355 and rooms also contain actors.
356 Rather than having separate structures for rooms, actors, and objects,
357 we'd like to say things like ``a room is just like an object,
358 but it also has exits.''
359 If we rigged it up right
360 (and cleaned up a number of messes which we've perpetrated
361 along the way because of the fact that we hadn't been using this
362 structure)
364 we could have a single piece of code
365 which would transfer objects between rooms and actors (``take''),
366 between actors and rooms (``drop''),
367 between actors and (container) objects (``put in'')
368 and which would even transfer actors between rooms
369 (when the actor moved from one room to another).
370 This function (and much of the rest of the game)
371 could operate on generic ``objects,''
372 without worrying about whether they were simple objects, actors,
373 or rooms.
374 Only those portions of the game specific to operating on actors
375 or rooms would look at the additional fields differentiating an
376 actor or room from an object.
377 <br>
378 <br>
379 The notion that some data structures are extensions of others,
380 that an ``x'' is just like a ``y'' except for
381 some extra stuff,
382 is the another cornerstone (perhaps <em>the</em> cornerstone)
383 of Object-Oriented Programming.
384 The formal term for this idea is <dfn>inheritance</dfn>,
385 and the data structures are usually spoken of as being
386 <dfn>objects</dfn>, which is precisely what the word
387 ``object'' is doing in ``Object-Oriented
388 Programming.''
389 (It's again more than an coincidence,
390 but rather an indication that our game is a perfect application of C++ or
391 another object-oriented language,
392 that we were already calling our fundamental data structures
393 ``objects.'')
394 <br>
395 <br>
396 The changes to the game to let it use C++ are extensive,
397 and I'm not going to try to present them all here.
398 (I've completed many of the changes, however,
399 and you can find them in
400 subdirectories of the
401 <a href="ftp://ftp.eskimo.com/u/s/scs/cclass/week8/">week8</a>
402 directory
403 associated with
404 the on-line web pages
405 for this class.)
406 The basic idea is that our old <TT>struct object</TT>
407 is no longer just a structure;
408 it is a <dfn>class</dfn>:
409 <pre>
410 class object
412 public:
413 object(const char *);
414 ~object(void);
416 char name[MAXNAME];
417 struct list *attrs;
418 object *contents;
419 object *container;
420 object *lnext; /* next in list of contained objects */
421 /* (i.e. in this object's container) */
422 char *desc; /* long description */
424 </pre>
425 (Among other things,
426 all objects now contain pointers back to their containers,
427 analogous to the way <TT>struct actor</TT> used to contain
428 a pointer back to its location.)
429 <br>
430 <br>
431 We write a <dfn>constructor</dfn> for new instances of this class,
432 replacing our old <TT>newobject</TT> function in <TT>object.c</TT>:
433 <pre>
434 object::object(const char *newname)
436 strcpy(name, newname);
437 lnext = NULL;
438 attrs = NULL;
439 contents = NULL;
440 container = NULL;
441 desc = NULL;
443 </pre>
444 Wherever we used to write
445 something like
446 <pre>
447 objp = newobject(name);
448 </pre>
449 we instead use the C++ <TT>new</TT> operator:
450 <pre>
451 objp = new object(name);
452 </pre>
453 (Actually, the only place we called <TT>newobject</TT>
454 was in <TT>readdatafile</TT> in <TT>io.c</TT>,
455 when setting <TT>currentobject</TT>.
456 It is entirely a coincidence,
457 though not a surprising one,
458 that the use of the C++ <TT>new</TT> operator here
459 looks so eerily similar to the old <TT>newobject</TT> call.)
460 <br>
461 <br>
462 Going back to <TT>game.h</TT>,
463 we define the <TT>actor</TT> and <TT>room</TT> classes
464 as being derived from <TT>class object</TT>:
465 <pre>
466 class actor : public object
468 public:
469 actor();
472 class room : public object
474 public:
475 room(const char *);
477 room *exits[NEXITS];
478 struct room *next; /* list of all rooms */
480 </pre>
481 These say that an actor is just like an object
482 (we don't actually have any actor-specific information at the moment),
483 and that a room is just like an object
484 except that it has an exits array
485 and an extra next pointer so that we can construct a list of all rooms.
486 <br>
487 <br>
488 Here is the new,
489 general-purpose object-transferring function, for <TT>object.c</TT>:
490 <pre>
491 /* transfer object from one general container to another */
493 transferobject(object *objp, object *newcontainer)
495 object *lp;
496 object *prevlp = NULL;
498 if(objp-&gt;container != NULL)
500 object *oldc = objp-&gt;container;
501 for(lp = oldc-&gt;contents; lp != NULL; lp = lp-&gt;lnext)
503 if(lp == objp) /* found it */
505 /* splice out of old container's list */
506 if(lp == oldc-&gt;contents) /* head of list */
507 oldc-&gt;contents = lp-&gt;lnext;
508 else prevlp-&gt;lnext = lp-&gt;lnext;
509 break;
511 prevlp = lp;
515 /* splice into new container's list */
517 if(newcontainer != NULL)
519 objp-&gt;lnext = newcontainer-&gt;contents;
520 newcontainer-&gt;contents = objp;
523 objp-&gt;container = newcontainer;
525 return TRUE;
527 </pre>
528 Notice that the parameters are declared
529 as being of type ``<TT>object *</TT>''.
530 In C++,
531 every structure and class you declare
532 has its tag name implicitly defined as a typedef,
533 so that the keyword <TT>struct</TT> or <TT>class</TT>
534 is no longer needed in later declarations.
535 (Theoretically, we should seek out every instance
536 of <TT>struct object</TT> in the game
537 and replace
539 them with <TT>object</TT> or perhaps <TT>class object</TT>,
540 and similarly for every <TT>struct actor</TT> and <TT>struct room</TT>.
541 The compiler I'm using,
542 the C++ version of the GNU C Compiler,
543 namely <TT>g++</TT>,
544 doesn't seem to be complaining about stray <TT>struct</TT> keywords
545 referring to what I've actually redefined as classes,
546 but I'm not sure what the formal rules of C++ say.)
547 <br>
548 <br>
549 In any case,
550 even though <TT>transferobject</TT> looks like it's defined
551 only for use on objects,
552 since actors and rooms are now also objects,
553 we can also use <TT>transferobject</TT>
554 to move objects to and from the actor,
555 and even to move the actor between rooms.
556 That is,
557 we can rewrite the other transfer functions from
558 <TT>object.c</TT> and <TT>rooms.c</TT>
559 very simply:
560 <pre>
561 takeobject(actor *actp, object *objp)
563 return transferobject(objp, actp);
566 dropobject(actor *actp, object *objp)
568 return transferobject(objp, actp-&gt;container);
571 putobject(actor *actp, object *objp, object *container)
573 return transferobject(objp, container);
577 gotoroom(actor *actor, room *room)
579 return transferobject(actor, room);
581 </pre>
582 <li>Another improvement which you might be interested in
583 (and another sweeping one)
584 would be to rewrite the game so that several people could play it at once,
585 over the network.
586 Instead of one instance of <TT>struct actor</TT>,
587 representing one player sitting at one keyboard
588 and viewing output on one screen,
589 we could have arbitrarily many <TT>actor</TT> structures
590 (just as we now have arbitrarily many objects and rooms),
591 with each actor representing a player
592 sitting somewhere on the network,
593 typing input and receiving output over a network connection.
594 <br>
595 <br>
596 The changes to make a multiplayer version of the game
597 are actually rather straightforward and self-contained,
598 with the glaring exception of the fact that
599 <em>everywhere</em> we
600 used to
601 call <TT>printf</TT> to print some text to the user's screen,
602 we must instead call some special output function of our own
603 which knows how to send the text
604 to the network connection of the appropriate player.
605 <br>
606 <br>
607 To represent the network connection,
608 we'll add a file descriptor to the <TT>actor</TT> structure.
609 If we're using Unix-style networking,
610 the file descriptor will just be an integer:
611 <pre>
612 int remotefd;
613 </pre>
614 If we've gone over to the C++ style of doing things,
615 this means that <TT>class actor</TT> now does have something
616 to distinguish it from a plain object:
617 <pre>
618 class actor : public object
620 public:
621 actor();
622 int remotefd;
623 struct actor *next; /* list of all actors */
625 </pre>
626 (It turns out we're also going to need a list of all players,
627 just like we need a list of all rooms.)
628 <br>
629 <br>
630 Then, using what we learned about variable-length argument lists
633 chapter 25 of the class notes,
634 we can write an <TT>output</TT> function
635 which is like <TT>printf</TT>
636 except that it writes the message
637 to a particular player's network connection:
638 <pre>
639 void output(struct actor *actp, char *msg, ...)
641 char tmpbuf[200]; /* XXX */
642 va_list argp;
643 va_start(argp, msg);
644 vsprintf(tmpbuf, msg, argp);
645 va_end(argp);
646 write(actp-&gt;remotefd, tmpbuf, strlen(tmpbuf));
648 </pre>
649 We call <TT>vsprintf</TT> to ``print''
650 the <TT>printf</TT>-style message
651 to a temporary string buffer,
652 then use the low-level Unix <TT>write</TT> function
653 to write the string to the network connection.
654 (This function has a significant limitation as written:
655 if a single message is ever more than 200 characters long,
656 the <TT>tmpbuf</TT> array will overflow,
657 with results which might range from annoying to catastrophic.
658 It's unfortunately tricky to this sort of thing right;
659 the comment <TT>/* XXX */</TT> is a reminder
660 that the <TT>tmpbuf</TT> array with its fixed size of 200
661 is a weakness in the program.)
662 <br>
663 <br>
664 The rest of the code for the multiplayer version of the game
665 is too bulky to include here,
666 but I've placed it in
667 subdirectories of the
668 <a href="ftp://ftp.eskimo.com/u/s/scs/cclass/week8/">week8</a>
669 directory, too.
670 See the
671 <a href="ftp://ftp.eskimo.com/u/s/scs/cclass/week8/multiplayer/README">README</a>
672 file there for more information.
673 </OL><hr>
674 <hr>
676 This page by <a href="http://www.eskimo.com/~scs/">Steve Summit</a>
677 // <a href="copyright.html">Copyright</a> 1995-9
678 // <a href="mailto:scs@eskimo.com">mail feedback</a>
679 </p>
680 </body>
681 </html>