Magic-number limination. Improve test coverage.
[open-adventure.git] / main.c
1 /*
2  * There used to be a note that said this:
3  *
4  * The author - Don Woods - apologises for the style of the code; it
5  * is a result of running the original Fortran IV source through a
6  * home-brew Fortran-to-C converter.
7  *
8  * Now that the code has been restructured into something much closer
9  * to idiomatic C, the following is more appropriate:
10  *
11  * ESR apologizes for the remaing gotos (now confined to one function
12  * in this file - there used to be over 350 of them, *everywhere*),
13  * and for the offensive globals.  Applying the Structured Program
14  * Theorem can be hard.
15  */
16 #define DEFINE_GLOBALS_FROM_INCLUDES
17 #include <stdlib.h>
18 #include <stdio.h>
19 #include <stdbool.h>
20 #include <getopt.h>
21 #include <signal.h>
22 #include <time.h>
23 #include "advent.h"
24 #include "database.h"
25 #include "linenoise/linenoise.h"
26 #include "newdb.h"
27
28 struct game_t game;
29
30 long LNLENG, LNPOSN, PARMS[MAXPARMS + 1];
31 char rawbuf[LINESIZE], INLINE[LINESIZE + 1];
32
33 long AMBER, AXE, BACK, BATTER, BEAR, BIRD, BLOOD,
34      BOTTLE, CAGE, CAVE, CAVITY, CHAIN, CHASM, CHEST,
35      CLAM, COINS, DOOR, DPRSSN, DRAGON, DWARF, EGGS,
36      EMRALD, ENTER, ENTRNC, FIND, FISSUR, FOOD,
37      GRATE, HINT, INVENT, JADE, KEYS,
38      KNIFE, LAMP, LOCK, LOOK, MAGZIN,
39      MESSAG, MIRROR, NUGGET, NUL, OGRE, OIL, OYSTER,
40      PEARL, PILLOW, PLANT, PLANT2, PYRAM, RESER, ROD, ROD2,
41      RUBY, RUG, SAPPH, SAY, SIGN, SNAKE,
42      STEPS, STREAM, THROW, TRIDNT, TROLL, TROLL2,
43      URN, VASE, VEND, VOLCAN, WATER;
44 long WD1, WD1X, WD2, WD2X;
45
46 FILE  *logfp = NULL, *rfp = NULL;
47 bool oldstyle = false;
48 bool editline = true;
49 bool prompt = true;
50
51 static void sig_handler(int signo)
52 {
53     if (signo == SIGINT) {
54         if (logfp != NULL)
55             fflush(logfp);
56     }
57     exit(0);
58 }
59
60 /*
61  * MAIN PROGRAM
62  *
63  *  Adventure (rev 2: 20 treasures)
64  *
65  *  History: Original idea & 5-treasure version (adventures) by Willie Crowther
66  *           15-treasure version (adventure) by Don Woods, April-June 1977
67  *           20-treasure version (rev 2) by Don Woods, August 1978
68  *              Errata fixed: 78/12/25
69  *           Revived 2017 as Open Advebture.
70  */
71
72 static bool do_command(FILE *);
73
74 int main(int argc, char *argv[])
75 {
76     int ch;
77
78     /*  Options. */
79
80     while ((ch = getopt(argc, argv, "l:or:s")) != EOF) {
81         switch (ch) {
82         case 'l':
83             logfp = fopen(optarg, "w");
84             if (logfp == NULL)
85                 fprintf(stderr,
86                         "advent: can't open logfile %s for write\n",
87                         optarg);
88             signal(SIGINT, sig_handler);
89             break;
90         case 'o':
91             oldstyle = true;
92             editline = prompt = false;
93             break;
94         case 'r':
95             rfp = fopen(optarg, "r");
96             if (rfp == NULL)
97                 fprintf(stderr,
98                         "advent: can't open save file %s for read\n",
99                         optarg);
100             signal(SIGINT, sig_handler);
101             break;
102         case 's':
103             editline = false;
104             break;
105         }
106     }
107
108     linenoiseHistorySetMaxLen(350);
109
110     /* Logical variables:
111      *
112      *  game.closed says whether we're all the way closed
113      *  game.closng says whether it's closing time yet
114      *  game.clshnt says whether he's read the clue in the endgame
115      *  game.lmwarn says whether he's been warned about lamp going dim
116      *  game.novice says whether he asked for instructions at start-up
117      *  game.panic says whether he's found out he's trapped in the cave
118      *  game.wzdark says whether the loc he's leaving was dark */
119
120     /* Initialize our LCG PRNG with parameters tested against
121      * Knuth vol. 2. by the original authors */
122     game.lcg_a = 1093;
123     game.lcg_c = 221587;
124     game.lcg_m = 1048576;
125     srand(time(NULL));
126     long seedval = (long)rand();
127     set_seed(seedval);
128
129     /*  Initialize game variables */
130     initialise();
131
132     /*  Start-up, dwarf stuff */
133     game.zzword = RNDVOC(3, 0);
134     game.newloc = LOC_START;
135     game.loc = LOC_START;
136     game.limit = 330;
137     if (!rfp) {
138         game.novice = YES(stdin, WELCOME_YOU, CAVE_NEARBY, NO_MESSAGE);
139         if (game.novice)game.limit = 1000;
140     } else {
141         restore(rfp);
142     }
143
144     if (logfp)
145         fprintf(logfp, "seed %ld\n", seedval);
146
147     /* interpret commands until EOF or interrupt */
148     for (;;) {
149         if (!do_command(stdin))
150             break;
151     }
152     /* show score and exit */
153     score(quitgame);
154 }
155
156 static bool fallback_handler(char *buf)
157 /* fallback handler for commands not handled by FORTRANish parser */
158 {
159     long sv;
160     if (sscanf(buf, "seed %ld", &sv) == 1) {
161         set_seed(sv);
162         printf("Seed set to %ld\n", sv);
163         // autogenerated, so don't charge user time for it.
164         --game.turns;
165         // here we reconfigure any global game state that uses random numbers
166         game.zzword = RNDVOC(3, 0);
167         return true;
168     }
169     return false;
170 }
171
172 /*  Check if this loc is eligible for any hints.  If been here
173  *  long enough, branch to help section (on later page).  Hints
174  *  all come back here eventually to finish the loop.  Ignore
175  *  "HINTS" < 4 (special stuff, see database notes).
176  */
177 static void checkhints(FILE *cmdin)
178 {
179     if (COND[game.loc] >= game.conds) {
180         for (int hint = 1; hint <= HNTMAX; hint++) {
181             if (game.hinted[hint])
182                 continue;
183             if (!CNDBIT(game.loc, hint + HBASE))
184                 game.hintlc[hint] = -1;
185             ++game.hintlc[hint];
186             /*  Come here if he's been long enough at required loc(s) for some
187              *  unused hint. */
188             if (game.hintlc[hint] >= HINTS[hint][1]) {
189                 int i;
190
191                 switch (hint - 1) {
192                 case 0:
193                     /* cave */
194                     if (game.prop[GRATE] == 0 && !HERE(KEYS))
195                         break;
196                     game.hintlc[hint] = 0;
197                     return;
198                 case 1: /* bird */
199                     if (game.place[BIRD] == game.loc && TOTING(ROD) && game.oldobj == BIRD)
200                         break;
201                     return;
202                 case 2: /* snake */
203                     if (HERE(SNAKE) && !HERE(BIRD))
204                         break;
205                     game.hintlc[hint] = 0;
206                     return;
207                 case 3: /* maze */
208                     if (game.atloc[game.loc] == 0 &&
209                         game.atloc[game.oldloc] == 0 &&
210                         game.atloc[game.oldlc2] == 0 &&
211                         game.holdng > 1)
212                         break;
213                     game.hintlc[hint] = 0;
214                     return;
215                 case 4: /* dark */
216                     if (game.prop[EMRALD] != -1 && game.prop[PYRAM] == -1)
217                         break;
218                     game.hintlc[hint] = 0;
219                     return;
220                 case 5: /* witt */
221                     break;
222                 case 6: /* urn */
223                     if (game.dflag == 0)
224                         break;
225                     game.hintlc[hint] = 0;
226                     return;
227                 case 7: /* woods */
228                     if (game.atloc[game.loc] == 0 &&
229                         game.atloc[game.oldloc] == 0 &&
230                         game.atloc[game.oldlc2] == 0)
231                         break;
232                     return;
233                 case 8: /* ogre */
234                     i = ATDWRF(game.loc);
235                     if (i < 0) {
236                         game.hintlc[hint] = 0;
237                         return;
238                     }
239                     if (HERE(OGRE) && i == 0)
240                         break;
241                     return;
242                 case 9: /* jade */
243                     if (game.tally == 1 && game.prop[JADE] < 0)
244                         break;
245                     game.hintlc[hint] = 0;
246                     return;
247                 default:
248                     BUG(27);
249                     break;
250                 }
251
252                 /* Fall through to hint display */
253                 game.hintlc[hint] = 0;
254                 if (!YES(cmdin, HINTS[hint][3], NO_MESSAGE, OK_MAN))
255                     return;
256                 SETPRM(1, HINTS[hint][2], HINTS[hint][2]);
257                 RSPEAK(HINT_COST);
258                 game.hinted[hint] = YES(cmdin, WANT_HINT, HINTS[hint][4], OK_MAN);
259                 if (game.hinted[hint] && game.limit > WARNTIME)
260                     game.limit += WARNTIME * HINTS[hint][2];
261             }
262         }
263     }
264 }
265
266 static bool spotted_by_pirate(int i)
267 {
268     if (i != PIRATE)
269         return false;
270
271     /*  The pirate's spotted him.  He leaves him alone once we've
272      *  found chest.  K counts if a treasure is here.  If not, and
273      *  tally=1 for an unseen chest, let the pirate be spotted.  Note
274      *  that game.place[CHEST] = NOWHERE might mean that he's thrown
275      *  it to the troll, but in that case he's seen the chest
276      *  (game.prop=0). */
277     if (game.loc == game.chloc || game.prop[CHEST] >= 0)
278         return true;
279     int snarfed = 0;
280     bool movechest = false, robplayer = false;
281     for (int treasure = MINTRS; treasure <= MAXTRS; treasure++) {
282         /*  Pirate won't take pyramid from plover room or dark
283          *  room (too easy!). */
284         if (treasure == PYRAM && (game.loc == PLAC[PYRAM] || game.loc == PLAC[EMRALD])) {
285             continue;
286         }
287         if (TOTING(treasure) || HERE(treasure))
288             ++snarfed;
289         if (TOTING(treasure)) {
290             movechest = true;
291             robplayer = true;
292         }
293     }
294     /* Force chest placement before player finds last treasure */
295     if (game.tally == 1 && snarfed == 0 && game.place[CHEST] == NOWHERE && HERE(LAMP) && game.prop[LAMP] == 1) {
296         RSPEAK(PIRATE_SPOTTED);
297         movechest = true;
298     }
299     /* Do things in this order (chest move before robbery) so chest is listed
300      * last at the maze location. */
301     if (movechest) {
302         MOVE(CHEST, game.chloc);
303         MOVE(MESSAG, game.chloc2);
304         game.dloc[PIRATE] = game.chloc;
305         game.odloc[PIRATE] = game.chloc;
306         game.dseen[PIRATE] = false;
307     } else {
308         /* You might get a hint of the pirate's presence even if the
309          * chest doesn't move... */
310         if (game.odloc[PIRATE] != game.dloc[PIRATE] && PCT(20))
311             RSPEAK(PIRATE_RUSTLES);
312     }
313     if (robplayer) {
314         RSPEAK(PIRATE_POUNCES);
315         for (int treasure = MINTRS; treasure <= MAXTRS; treasure++) {
316             if (!(treasure == PYRAM && (game.loc == PLAC[PYRAM] || game.loc == PLAC[EMRALD]))) {
317                 if (AT(treasure) && game.fixed[treasure] == 0)
318                     CARRY(treasure, game.loc);
319                 if (TOTING(treasure))
320                     DROP(treasure, game.chloc);
321             }
322         }
323     }
324
325     return true;
326 }
327
328 static bool dwarfmove(void)
329 /* Dwarves move.  Return true if player survives, false if he dies. */
330 {
331     int kk, stick, attack;
332     long tk[21];
333
334     /*  Dwarf stuff.  See earlier comments for description of
335      *  variables.  Remember sixth dwarf is pirate and is thus
336      *  very different except for motion rules. */
337
338     /*  First off, don't let the dwarves follow him into a pit or
339      *  a wall.  Activate the whole mess the first time he gets as
340      *  far as the hall of mists (loc 15).  If game.newloc is
341      *  forbidden to pirate (in particular, if it's beyond the
342      *  troll bridge), bypass dwarf stuff.  That way pirate can't
343      *  steal return toll, and dwarves can't meet the bear.  Also
344      *  means dwarves won't follow him into dead end in maze, but
345      *  c'est la vie.  They'll wait for him outside the dead
346      *  end. */
347     if (game.loc == 0 || FORCED(game.loc) || CNDBIT(game.newloc, NOARRR))
348         return true;
349
350     /* Dwarf activity level ratchets up */
351     if (game.dflag == 0) {
352         if (INDEEP(game.loc))
353             game.dflag = 1;
354         return true;
355     }
356
357     /*  When we encounter the first dwarf, we kill 0, 1, or 2 of
358      *  the 5 dwarves.  If any of the survivors is at loc,
359      *  replace him with the alternate. */
360     if (game.dflag == 1) {
361         if (!INDEEP(game.loc) || (PCT(95) && (!CNDBIT(game.loc, NOBACK) || PCT(85))))
362             return true;
363         game.dflag = 2;
364         for (int i = 1; i <= 2; i++) {
365             int j = 1 + randrange(NDWARVES - 1);
366             if (PCT(50))
367                 game.dloc[j] = 0;
368         }
369         for (int i = 1; i <= NDWARVES - 1; i++) {
370             if (game.dloc[i] == game.loc)
371                 game.dloc[i] = DALTLC;
372             game.odloc[i] = game.dloc[i];
373         }
374         RSPEAK(DWARF_RAN);
375         DROP(AXE, game.loc);
376         return true;
377     }
378
379     /*  Things are in full swing.  Move each dwarf at random,
380      *  except if he's seen us he sticks with us.  Dwarves stay
381      *  deep inside.  If wandering at random, they don't back up
382      *  unless there's no alternative.  If they don't have to
383      *  move, they attack.  And, of course, dead dwarves don't do
384      *  much of anything. */
385     game.dtotal = 0;
386     attack = 0;
387     stick = 0;
388     for (int i = 1; i <= NDWARVES; i++) {
389         if (game.dloc[i] == 0)
390             continue;
391         /*  Fill tk array with all the places this dwarf might go. */
392         int j = 1;
393         kk = KEY[game.dloc[i]];
394         if (kk != 0)
395             do {
396                 game.newloc = MOD(labs(TRAVEL[kk]) / 1000, 1000);
397                 /* Have we avoided a dwarf encounter? */
398                 bool avoided = (SPECIAL(game.newloc) ||
399                                 !INDEEP(game.newloc) ||
400                                 game.newloc == game.odloc[i] ||
401                                 (j > 1 && game.newloc == tk[j - 1]) ||
402                                 j >= 20 ||
403                                 game.newloc == game.dloc[i] ||
404                                 FORCED(game.newloc) ||
405                                 (i == PIRATE && CNDBIT(game.newloc, NOARRR)) ||
406                                 labs(TRAVEL[kk]) / 1000000 == 100);
407                 if (!avoided) {
408                     tk[j++] = game.newloc;
409                 }
410                 ++kk;
411             } while
412             (TRAVEL[kk - 1] >= 0);
413         tk[j] = game.odloc[i];
414         if (j >= 2)
415             --j;
416         j = 1 + randrange(j);
417         game.odloc[i] = game.dloc[i];
418         game.dloc[i] = tk[j];
419         game.dseen[i] = (game.dseen[i] && INDEEP(game.loc)) || (game.dloc[i] == game.loc || game.odloc[i] == game.loc);
420         if (!game.dseen[i]) continue;
421         game.dloc[i] = game.loc;
422         if (spotted_by_pirate(i))
423             continue;
424         /* This threatening little dwarf is in the room with him! */
425         ++game.dtotal;
426         if (game.odloc[i] == game.dloc[i]) {
427             ++attack;
428             if (game.knfloc >= 0)
429                 game.knfloc = game.loc;
430             if (randrange(1000) < 95 * (game.dflag - 2))
431                 ++stick;
432         }
433     }
434
435     /*  Now we know what's happening.  Let's tell the poor sucker about it.
436      *  Note that various of the "knife" messages must have specific relative
437      *  positions in the RSPEAK database. */
438     if (game.dtotal == 0)
439         return true;
440     SETPRM(1, game.dtotal, 0);
441     RSPEAK(DWARF_PACK + 1 / game.dtotal);       /* FIXME: Arithmetic on message number */
442     if (attack == 0)
443         return true;
444     if (game.dflag == 2)game.dflag = 3;
445     SETPRM(1, attack, 0);
446     int k = 6;
447     if (attack > 1)k = THROWN_KNIVES;
448     RSPEAK(k);
449     SETPRM(1, stick, 0);
450     RSPEAK(k + 1 + 2 / (1 + stick));    /* FIXME: Arithmetic on message number */
451     if (stick == 0)
452         return true;
453     game.oldlc2 = game.loc;
454     return false;
455 }
456
457 /*  "You're dead, Jim."
458  *
459  *  If the current loc is zero, it means the clown got himself killed.
460  *  We'll allow this maxdie times.  MAXDIE is automatically set based
461  *  on the number of snide messages available.  Each death results in
462  *  a message (81, 83, etc.)  which offers reincarnation; if accepted,
463  *  this results in message 82, 84, etc.  The last time, if he wants
464  *  another chance, he gets a snide remark as we exit.  When
465  *  reincarnated, all objects being carried get dropped at game.oldlc2
466  *  (presumably the last place prior to being killed) without change
467  *  of props.  the loop runs backwards to assure that the bird is
468  *  dropped before the cage.  (this kluge could be changed once we're
469  *  sure all references to bird and cage are done by keywords.)  The
470  *  lamp is a special case (it wouldn't do to leave it in the cave).
471  *  It is turned off and left outside the building (only if he was
472  *  carrying it, of course).  He himself is left inside the building
473  *  (and heaven help him if he tries to xyzzy back into the cave
474  *  without the lamp!).  game.oldloc is zapped so he can't just
475  *  "retreat". */
476
477 static void croak(FILE *cmdin)
478 /*  Okay, he's dead.  Let's get on with it. */
479 {
480     ++game.numdie;
481     if (game.closng) {
482         /*  He died during closing time.  No resurrection.  Tally up a
483          *  death and exit. */
484         RSPEAK(DEATH_CLOSING);
485         score(endgame);
486
487     }
488     /* FIXME: Arithmetic on message numbers */
489     else if (game.numdie == MAXDIE || !YES(cmdin, WATCH_IT + game.numdie * 2, WHICH_WAY + game.numdie * 2, OK_MAN))
490         score(endgame);
491     else {
492         game.place[WATER] = game.place[OIL] = NOWHERE;
493         if (TOTING(LAMP))
494             game.prop[LAMP] = 0;
495         for (int j = 1; j <= NOBJECTS; j++) {
496             int i = NOBJECTS + 1 - j;
497             if (TOTING(i)) {
498                 /* Always leave lamp where it's accessible aboveground */
499                 DROP(i, (i == LAMP) ? LOC_START : game.oldlc2);
500             }
501         }
502         game.loc = LOC_BUILDING;
503         game.oldloc = game.loc;
504     }
505 }
506
507 /*  Given the current location in "game.loc", and a motion verb number in
508  *  "motion", put the new location in "game.newloc".  The current loc is saved
509  *  in "game.oldloc" in case he wants to retreat.  The current
510  *  game.oldloc is saved in game.oldlc2, in case he dies.  (if he
511  *  does, game.newloc will be limbo, and game.oldloc will be what killed
512  *  him, so we need game.oldlc2, which is the last place he was
513  *  safe.) */
514
515 static bool playermove(FILE *cmdin, token_t verb, int motion)
516 {
517     int scratchloc, k2, kk = KEY[game.loc];
518     game.newloc = game.loc;
519     if (kk == 0)
520         BUG(26);
521     if (motion == NUL)
522         return true;
523     else if (motion == BACK) {
524         /*  Handle "go back".  Look for verb which goes from game.loc to
525          *  game.oldloc, or to game.oldlc2 If game.oldloc has forced-motion.
526          *  k2 saves entry -> forced loc -> previous loc. */
527         motion = game.oldloc;
528         if (FORCED(motion))
529             motion = game.oldlc2;
530         game.oldlc2 = game.oldloc;
531         game.oldloc = game.loc;
532         k2 = 0;
533         if (motion == game.loc)k2 = FORGOT_PATH;
534         if (CNDBIT(game.loc, NOBACK))k2 = TWIST_TURN;
535         if (k2 == 0) {
536             for (;;) {
537                 scratchloc = MOD((labs(TRAVEL[kk]) / 1000), 1000);
538                 if (scratchloc != motion) {
539                     if (!SPECIAL(scratchloc)) {
540                         if (FORCED(scratchloc) && MOD((labs(TRAVEL[KEY[scratchloc]]) / 1000), 1000) == motion)
541                             k2 = kk;
542                     }
543                     if (TRAVEL[kk] >= 0) {
544                         ++kk;
545                         continue;
546                     }
547                     kk = k2;
548                     if (kk == 0) {
549                         RSPEAK(NOT_CONNECTED);
550                         return true;
551                     }
552                 }
553
554                 motion = MOD(labs(TRAVEL[kk]), 1000);
555                 kk = KEY[game.loc];
556                 break; /* fall through to ordinary travel */
557             }
558         } else {
559             RSPEAK(k2);
560             return true;
561         }
562     } else if (motion == LOOK) {
563         /*  Look.  Can't give more detail.  Pretend it wasn't dark
564          *  (though it may now be dark) so he won't fall into a
565          *  pit while staring into the gloom. */
566         if (game.detail < 3)
567             RSPEAK(NO_MORE_DETAIL);
568         ++game.detail;
569         game.wzdark = false;
570         game.abbrev[game.loc] = 0;
571         return true;
572     } else if (motion == CAVE) {
573         /*  Cave.  Different messages depending on whether above ground. */
574         RSPEAK((OUTSID(game.loc) && game.loc != LOC_GRATE) ? FOLLOW_STREAM : NEED_DETAIL);
575         return true;
576     } else {
577         /* none of the specials */
578         game.oldlc2 = game.oldloc;
579         game.oldloc = game.loc;
580     }
581
582     /* ordinary travel */
583     for (;;) {
584         scratchloc = labs(TRAVEL[kk]);
585         if (MOD(scratchloc, 1000) == 1 || MOD(scratchloc, 1000) == motion)
586             break;
587         if (TRAVEL[kk] < 0) {
588             /* FIXME: Magic numbers! */
589             /*  Non-applicable motion.  Various messages depending on
590              *  word given. */
591             int spk = CANT_APPLY;
592             if (motion >= 43 && motion <= 50)spk = BAD_DIRECTION;
593             if (motion == 29 || motion == 30)spk = BAD_DIRECTION;
594             if (motion == 7 || motion == 36 || motion == 37)spk = UNSURE_FACING;
595             if (motion == 11 || motion == 19)spk = NO_INOUT_HERE;
596             if (verb == FIND || verb == INVENT)spk = NEreplace;
597             if (motion == 62 || motion == 65)spk = NOTHING_HAPPENS;
598             if (motion == 17)spk = WHICH_WAY;
599             RSPEAK(spk);
600             return true;
601         }
602         ++kk;
603     }
604     scratchloc = scratchloc / 1000;
605
606     do {
607         /*
608          * (ESR) This special-travel loop may have to be repeated if it includes
609          * the plover passage.  Same deal for any future cases wgerw we beed to
610          * block travel and then redo it once the blocking condition has been
611          * removed.
612          */
613         for (;;) {
614             game.newloc = scratchloc / 1000;
615             motion = MOD(game.newloc, 100);
616             if (!SPECIAL(game.newloc)) {
617                 if (game.newloc <= 100) {
618                     if (game.newloc == 0 || PCT(game.newloc))
619                         break;
620                     /* else fall through */
621                 }
622                 if (TOTING(motion) || (game.newloc > 200 && AT(motion)))
623                     break;
624                 /* else fall through */
625             } else if (game.prop[motion] != game.newloc / 100 - 3)
626                 break;
627             do {
628                 if (TRAVEL[kk] < 0)BUG(25);
629                 ++kk;
630                 game.newloc = labs(TRAVEL[kk]) / 1000;
631             } while
632             (game.newloc == scratchloc);
633             scratchloc = game.newloc;
634         }
635
636         game.newloc = MOD(scratchloc, 1000);
637         if (!SPECIAL(game.newloc))
638             return true;
639         if (game.newloc <= 500) {
640             game.newloc = game.newloc - SPECIALBASE;
641             switch (game.newloc) {
642             case 1:
643                 /*  Travel 301.  Plover-alcove passage.  Can carry only
644                  *  emerald.  Note: travel table must include "useless"
645                  *  entries going through passage, which can never be used for
646                  *  actual motion, but can be spotted by "go back". */
647                 /* FIXME: Arithmetic on location numbers */
648                 game.newloc = 99 + 100 - game.loc;
649                 if (game.holdng == 0 || (game.holdng == 1 && TOTING(EMRALD)))
650                     return true;
651                 game.newloc = game.loc;
652                 RSPEAK(MUST_DROP);
653                 return true;
654             case 2:
655                 /*  Travel 302.  Plover transport.  Drop the emerald (only use
656                  *  special travel if toting it), so he's forced to use the
657                  *  plover-passage to get it out.  Having dropped it, go back and
658                  *  pretend he wasn't carrying it after all. */
659                 DROP(EMRALD, game.loc);
660                 do {
661                     if (TRAVEL[kk] < 0)BUG(25);
662                     ++kk;
663                     game.newloc = labs(TRAVEL[kk]) / 1000;
664                 } while
665                 (game.newloc == scratchloc);
666                 continue;       /* back to top of do/while loop */
667             case 3:
668                 /*  Travel 303.  Troll bridge.  Must be done only as special
669                  *  motion so that dwarves won't wander across and encounter
670                  *  the bear.  (They won't follow the player there because
671                  *  that region is forbidden to the pirate.)  If
672                  *  game.prop(TROLL)=1, he's crossed since paying, so step out
673                  *  and block him.  (standard travel entries check for
674                  *  game.prop(TROLL)=0.)  Special stuff for bear. */
675                 if (game.prop[TROLL] == 1) {
676                     PSPEAK(TROLL, 1);
677                     game.prop[TROLL] = 0;
678                     MOVE(TROLL2, 0);
679                     MOVE(TROLL2 + NOBJECTS, 0);
680                     MOVE(TROLL, PLAC[TROLL]);
681                     MOVE(TROLL + NOBJECTS, FIXD[TROLL]);
682                     JUGGLE(CHASM);
683                     game.newloc = game.loc;
684                     return true;
685                 } else {
686                     game.newloc = PLAC[TROLL] + FIXD[TROLL] - game.loc;
687                     if (game.prop[TROLL] == 0)game.prop[TROLL] = 1;
688                     if (!TOTING(BEAR)) return true;
689                     RSPEAK(BRIDGE_COLLAPSE);
690                     game.prop[CHASM] = 1;
691                     game.prop[TROLL] = 2;
692                     DROP(BEAR, game.newloc);
693                     game.fixed[BEAR] = -1;
694                     game.prop[BEAR] = 3;
695                     game.oldlc2 = game.newloc;
696                     croak(cmdin);
697                     return false;
698                 }
699             }
700             BUG(20);
701         }
702     } while
703     (false);
704     /* FIXME: Arithmetic on location number, becoming a message number */
705     RSPEAK(game.newloc - 500);
706     game.newloc = game.loc;
707     return true;
708 }
709
710 static bool closecheck(void)
711 /*  Handle the closing of the cave.  The cave closes "clock1" turns
712  *  after the last treasure has been located (including the pirate's
713  *  chest, which may of course never show up).  Note that the
714  *  treasures need not have been taken yet, just located.  Hence
715  *  clock1 must be large enough to get out of the cave (it only ticks
716  *  while inside the cave).  When it hits zero, we branch to 10000 to
717  *  start closing the cave, and then sit back and wait for him to try
718  *  to get out.  If he doesn't within clock2 turns, we close the cave;
719  *  if he does try, we assume he panics, and give him a few additional
720  *  turns to get frantic before we close.  When clock2 hits zero, we
721  *  branch to 11000 to transport him into the final puzzle.  Note that
722  *  the puzzle depends upon all sorts of random things.  For instance,
723  *  there must be no water or oil, since there are beanstalks which we
724  *  don't want to be able to water, since the code can't handle it.
725  *  Also, we can have no keys, since there is a grate (having moved
726  *  the fixed object!) there separating him from all the treasures.
727  *  Most of these problems arise from the use of negative prop numbers
728  *  to suppress the object descriptions until he's actually moved the
729  *  objects. */
730 {
731     if (game.tally == 0 && INDEEP(game.loc) && game.loc != 33)
732         --game.clock1;
733
734     /*  When the first warning comes, we lock the grate, destroy
735      *  the bridge, kill all the dwarves (and the pirate), remove
736      *  the troll and bear (unless dead), and set "closng" to
737      *  true.  Leave the dragon; too much trouble to move it.
738      *  from now until clock2 runs out, he cannot unlock the
739      *  grate, move to any location outside the cave, or create
740      *  the bridge.  Nor can he be resurrected if he dies.  Note
741      *  that the snake is already gone, since he got to the
742      *  treasure accessible only via the hall of the mountain
743      *  king. Also, he's been in giant room (to get eggs), so we
744      *  can refer to it.  Also also, he's gotten the pearl, so we
745      *  know the bivalve is an oyster.  *And*, the dwarves must
746      *  have been activated, since we've found chest. */
747     if (game.clock1 == 0) {
748         game.prop[GRATE] = 0;
749         game.prop[FISSUR] = 0;
750         for (int i = 1; i <= NDWARVES; i++) {
751             game.dseen[i] = false;
752             game.dloc[i] = 0;
753         }
754         MOVE(TROLL, 0);
755         MOVE(TROLL + NOBJECTS, 0);
756         MOVE(TROLL2, PLAC[TROLL]);
757         MOVE(TROLL2 + NOBJECTS, FIXD[TROLL]);
758         JUGGLE(CHASM);
759         if (game.prop[BEAR] != 3)DESTROY(BEAR);
760         game.prop[CHAIN] = 0;
761         game.fixed[CHAIN] = 0;
762         game.prop[AXE] = 0;
763         game.fixed[AXE] = 0;
764         RSPEAK(CAVE_CLOSING);
765         game.clock1 = -1;
766         game.closng = true;
767         return true;
768     } else if (game.clock1 < 0)
769         --game.clock2;
770     if (game.clock2 == 0) {
771         /*  Once he's panicked, and clock2 has run out, we come here
772          *  to set up the storage room.  The room has two locs,
773          *  hardwired as 115 (ne) and 116 (sw).  At the ne end, we
774          *  place empty bottles, a nursery of plants, a bed of
775          *  oysters, a pile of lamps, rods with stars, sleeping
776          *  dwarves, and him.  At the sw end we place grate over
777          *  treasures, snake pit, covey of caged birds, more rods, and
778          *  pillows.  A mirror stretches across one wall.  Many of the
779          *  objects come from known locations and/or states (e.g. the
780          *  snake is known to have been destroyed and needn't be
781          *  carried away from its old "place"), making the various
782          *  objects be handled differently.  We also drop all other
783          *  objects he might be carrying (lest he have some which
784          *  could cause trouble, such as the keys).  We describe the
785          *  flash of light and trundle back. */
786         game.prop[BOTTLE] = PUT(BOTTLE, LOC_NE, 1);
787         game.prop[PLANT] = PUT(PLANT, LOC_NE, 0);
788         game.prop[OYSTER] = PUT(OYSTER, LOC_NE, 0);
789         OBJTXT[OYSTER] = 3;
790         game.prop[LAMP] = PUT(LAMP, LOC_NE, 0);
791         game.prop[ROD] = PUT(ROD, LOC_NE, 0);
792         game.prop[DWARF] = PUT(DWARF, LOC_NE, 0);
793         game.loc = LOC_NE;
794         game.oldloc = LOC_NE;
795         game.newloc = LOC_NE;
796         /*  Leave the grate with normal (non-negative) property.
797          *  Reuse sign. */
798         PUT(GRATE, LOC_SW, 0);
799         PUT(SIGN, LOC_SW, 0);
800         ++OBJTXT[SIGN];
801         game.prop[SNAKE] = PUT(SNAKE, LOC_SW, 1);
802         game.prop[BIRD] = PUT(BIRD, LOC_SW, 1);
803         game.prop[CAGE] = PUT(CAGE, LOC_SW, 0);
804         game.prop[ROD2] = PUT(ROD2, LOC_SW, 0);
805         game.prop[PILLOW] = PUT(PILLOW, LOC_SW, 0);
806
807         game.prop[MIRROR] = PUT(MIRROR, LOC_NE, 0);
808         game.fixed[MIRROR] = LOC_SW;
809
810         for (int i = 1; i <= NOBJECTS; i++) {
811             if (TOTING(i))
812                 DESTROY(i);
813         }
814
815         RSPEAK(CAVE_CLOSED);
816         game.closed = true;
817         return true;
818     }
819
820     return false;
821 }
822
823 static void lampcheck(void)
824 /* Check game limit and lamp timers */
825 {
826     if (game.prop[LAMP] == 1)
827         --game.limit;
828
829     /*  Another way we can force an end to things is by having the
830      *  lamp give out.  When it gets close, we come here to warn
831      *  him.  First following ar, if the lamp and fresh batteries are
832      *  here, in which case we replace the batteries and continue.
833      *  Second is for other cases of lamp dying.  12400 is when it
834      *  goes out.  Even then, he can explore outside for a while
835      *  if desired. */
836     if (game.limit <= WARNTIME && HERE(BATTER) && game.prop[BATTER] == 0 && HERE(LAMP)) {
837         RSPEAK(REPLACE_BATTERIES);
838         game.prop[BATTER] = 1;
839         if (TOTING(BATTER))
840             DROP(BATTER, game.loc);
841         game.limit = game.limit + 2500;
842         game.lmwarn = false;
843     } else if (game.limit == 0) {
844         game.limit = -1;
845         game.prop[LAMP] = 0;
846         if (HERE(LAMP))
847             RSPEAK(LAMP_OUT);
848     } else if (game.limit <= WARNTIME) {
849         if (!game.lmwarn && HERE(LAMP)) {
850             game.lmwarn = true;
851             int spk = GET_BATTERIES;
852             if (game.place[BATTER] == NOWHERE)spk = LAMP_DIM;
853             if (game.prop[BATTER] == 1)spk = MISSING_BATTERIES;
854             RSPEAK(spk);
855         }
856     }
857 }
858
859 static void listobjects(void)
860 /*  Print out descriptions of objects at this location.  If
861  *  not closing and property value is negative, tally off
862  *  another treasure.  Rug is special case; once seen, its
863  *  game.prop is 1 (dragon on it) till dragon is killed.
864  *  Similarly for chain; game.prop is initially 1 (locked to
865  *  bear).  These hacks are because game.prop=0 is needed to
866  *  get full score. */
867 {
868     if (!DARK(game.loc)) {
869         ++game.abbrev[game.loc];
870         for (int i = game.atloc[game.loc]; i != 0; i = game.link[i]) {
871             long obj = i;
872             if (obj > NOBJECTS)obj = obj - NOBJECTS;
873             if (obj == STEPS && TOTING(NUGGET))
874                 continue;
875             if (game.prop[obj] < 0) {
876                 if (game.closed)
877                     continue;
878                 game.prop[obj] = 0;
879                 if (obj == RUG || obj == CHAIN)
880                     game.prop[obj] = 1;
881                 --game.tally;
882                 /*  Note: There used to be a test here to see whether the
883                  *  player had blown it so badly that he could never ever see
884                  *  the remaining treasures, and if so the lamp was zapped to
885                  *  35 turns.  But the tests were too simple-minded; things
886                  *  like killing the bird before the snake was gone (can never
887                  *  see jewelry), and doing it "right" was hopeless.  E.G.,
888                  *  could cross troll bridge several times, using up all
889                  *  available treasures, breaking vase, using coins to buy
890                  *  batteries, etc., and eventually never be able to get
891                  *  across again.  If bottle were left on far side, could then
892                  *  never get eggs or trident, and the effects propagate.  So
893                  *  the whole thing was flushed.  anyone who makes such a
894                  *  gross blunder isn't likely to find everything else anyway
895                  *  (so goes the rationalisation). */
896             }
897             int kk = game.prop[obj];
898             if (obj == STEPS && game.loc == game.fixed[STEPS])
899                 kk = 1;
900             PSPEAK(obj, kk);
901         }
902     }
903 }
904
905 static bool do_command(FILE *cmdin)
906 /* Get and execute a command */
907 {
908     long verb = 0, V1, V2;
909     long kmod, defn;
910     static long igo = 0;
911     static long obj = 0;
912     enum speechpart part;
913
914     /*  Can't leave cave once it's closing (except by main office). */
915     if (OUTSID(game.newloc) && game.newloc != 0 && game.closng) {
916         RSPEAK(EXIT_CLOSED);
917         game.newloc = game.loc;
918         if (!game.panic)game.clock2 = PANICTIME;
919         game.panic = true;
920     }
921
922     /*  See if a dwarf has seen him and has come from where he
923      *  wants to go.  If so, the dwarf's blocking his way.  If
924      *  coming from place forbidden to pirate (dwarves rooted in
925      *  place) let him get out (and attacked). */
926     if (game.newloc != game.loc && !FORCED(game.loc) && !CNDBIT(game.loc, NOARRR)) {
927         for (size_t i = 1; i <= NDWARVES - 1; i++) {
928             if (game.odloc[i] == game.newloc && game.dseen[i]) {
929                 game.newloc = game.loc;
930                 RSPEAK(DWARF_BLOCK);
931                 break;
932             }
933         }
934     }
935     game.loc = game.newloc;
936
937     if (!dwarfmove())
938         croak(cmdin);
939
940     /*  Describe the current location and (maybe) get next command. */
941
942     for (;;) {
943         if (game.loc == 0)
944             croak(cmdin);
945         const char* msg = locations[game.loc].description.small;
946         if (MOD(game.abbrev[game.loc], game.abbnum) == 0 || msg == 0)
947             msg = locations[game.loc].description.big;
948         if (!FORCED(game.loc) && DARK(game.loc)) {
949             /*  The easiest way to get killed is to fall into a pit in
950              *  pitch darkness. */
951             if (game.wzdark && PCT(35)) {
952                 RSPEAK(PIT_FALL);
953                 game.oldlc2 = game.loc;
954                 croak(cmdin);
955                 continue;       /* back to top of main interpreter loop */
956             }
957             msg = arbitrary_messages[PITCH_DARK];
958         }
959         if (TOTING(BEAR))RSPEAK(TAME_BEAR);
960         newspeak(msg);
961         if (FORCED(game.loc)) {
962             if (playermove(cmdin, verb, 1))
963                 return true;
964             else
965                 continue;       /* back to top of main interpreter loop */
966         }
967         if (game.loc == 33 && PCT(25) && !game.closng)RSPEAK(SAYS_PLUGH);
968
969         listobjects();
970
971 L2012:
972         verb = 0;
973         game.oldobj = obj;
974         obj = 0;
975
976 L2600:
977         checkhints(cmdin);
978
979         /*  If closing time, check for any objects being toted with
980          *  game.prop < 0 and set the prop to -1-game.prop.  This way
981          *  objects won't be described until they've been picked up
982          *  and put down separate from their respective piles.  Don't
983          *  tick game.clock1 unless well into cave (and not at Y2). */
984         if (game.closed) {
985             if (game.prop[OYSTER] < 0 && TOTING(OYSTER))
986                 PSPEAK(OYSTER, 1);
987             for (size_t i = 1; i <= NOBJECTS; i++) {
988                 if (TOTING(i) && game.prop[i] < 0)
989                     game.prop[i] = -1 - game.prop[i];
990             }
991         }
992         game.wzdark = DARK(game.loc);
993         if (game.knfloc > 0 && game.knfloc != game.loc)
994             game.knfloc = 0;
995
996         /* This is where we get a new command from the user */
997         if (!GETIN(cmdin, &WD1, &WD1X, &WD2, &WD2X))
998             return false;
999
1000         /*  Every input, check "game.foobar" flag.  If zero, nothing's
1001          *  going on.  If pos, make neg.  If neg, he skipped a word,
1002          *  so make it zero. */
1003 L2607:
1004         game.foobar = (game.foobar > 0 ? -game.foobar : 0);
1005         ++game.turns;
1006         if (game.turns == game.thresh) {
1007             newspeak(turn_threshold_messages[game.trndex]);
1008             game.trnluz = game.trnluz + TRNVAL[game.trndex] / 100000;
1009             ++game.trndex;
1010             game.thresh = -1;
1011             if (game.trndex <= TRNVLS)
1012                 game.thresh = MOD(TRNVAL[game.trndex], 100000) + 1;
1013         }
1014         if (verb == SAY && WD2 > 0)
1015             verb = 0;
1016         if (verb == SAY) {
1017             part = transitive;
1018             goto Laction;
1019         }
1020         if (closecheck()) {
1021             if (game.closed)
1022                 return true;
1023         } else
1024             lampcheck();
1025
1026         V1 = VOCAB(WD1, -1);
1027         V2 = VOCAB(WD2, -1);
1028         if (V1 == ENTER && (V2 == STREAM || V2 == 1000 + WATER)) {
1029             if (LIQLOC(game.loc) == WATER) {
1030                 RSPEAK(FEET_WET);
1031             } else {
1032                 RSPEAK(WHERE_QUERY);
1033             }
1034             goto L2012;
1035         }
1036         if (V1 == ENTER && WD2 > 0) {
1037             WD1 = WD2;
1038             WD1X = WD2X;
1039             WD2 = 0;
1040         } else {
1041             if (!((V1 != 1000 + WATER && V1 != 1000 + OIL) ||
1042                   (V2 != 1000 + PLANT && V2 != 1000 + DOOR))) {
1043                 if (AT(V2 - 1000))
1044                     WD2 = MAKEWD(16152118);
1045             }
1046             if (V1 == 1000 + CAGE && V2 == 1000 + BIRD && HERE(CAGE) && HERE(BIRD))
1047                 WD1 = MAKEWD(301200308);
1048         }
1049 L2620:
1050         if (WD1 == MAKEWD(23051920)) {
1051             ++game.iwest;
1052             if (game.iwest == 10)
1053                 RSPEAK(W_IS_WEST);
1054         }
1055         if (WD1 == MAKEWD( 715) && WD2 != 0) {
1056             if (++igo == 10)
1057                 RSPEAK(GO_UNNEEDED);
1058         }
1059 Lookup:
1060         defn = VOCAB(WD1, -1);
1061         if (defn == -1) {
1062             /* Gee, I don't understand. */
1063             if (fallback_handler(rawbuf))
1064                 continue;
1065             SETPRM(1, WD1, WD1X);
1066             RSPEAK(DONT_KNOW);
1067             goto L2600;
1068         }
1069         kmod = MOD(defn, 1000);
1070         switch (defn / 1000) {
1071         case 0:
1072             if (playermove(cmdin, verb, kmod))
1073                 return true;
1074             else
1075                 continue;       /* back to top of main interpreter loop */
1076         case 1:
1077             part = unknown;
1078             obj = kmod;
1079             break;
1080         case 2:
1081             part = intransitive;
1082             verb = kmod;
1083             break;
1084         case 3:
1085             RSPEAK(kmod);
1086             goto L2012;
1087         default:
1088             BUG(22);
1089         }
1090
1091 Laction:
1092         switch (action(cmdin, part, verb, obj)) {
1093         case GO_TERMINATE:
1094             return true;
1095         case GO_MOVE:
1096             playermove(cmdin, verb, NUL);
1097             return true;
1098         case GO_TOP:
1099             continue;   /* back to top of main interpreter loop */
1100         case GO_CLEAROBJ:
1101             goto L2012;
1102         case GO_CHECKHINT:
1103             goto L2600;
1104         case GO_CHECKFOO:
1105             goto L2607;
1106         case GO_LOOKUP:
1107             goto Lookup;
1108         case GO_WORD2:
1109             /* Get second word for analysis. */
1110             WD1 = WD2;
1111             WD1X = WD2X;
1112             WD2 = 0;
1113             goto L2620;
1114         case GO_UNKNOWN:
1115             /*  Random intransitive verbs come here.  Clear obj just in case
1116              *  (see attack()). */
1117             SETPRM(1, WD1, WD1X);
1118             RSPEAK(DO_WHAT);
1119             obj = 0;
1120             goto L2600;
1121         case GO_DWARFWAKE:
1122             /*  Oh dear, he's disturbed the dwarves. */
1123             RSPEAK(DWARVES_AWAKEN);
1124             score(endgame);
1125             return true;
1126         default:
1127             BUG(99);
1128         }
1129     }
1130 }
1131
1132 /* end */