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