Reduce include complexity.
[open-adventure.git] / main.c
1 /*
2  * SPDX-FileCopyrightText: 1977, 2005 by Will Crowther and Don Woods
3  * SPDX-FileCopyrightText: 2017 by Eric S. Raymond
4  * SPDX-License-Identifier: BSD-2-Clause
5  */
6
7 #include <stdlib.h>
8 #include <stdio.h>
9 #include <stdbool.h>
10 #include <getopt.h>
11 #include <signal.h>
12 #include <string.h>
13 #include <ctype.h>
14 #include <unistd.h>
15 #include <editline/readline.h>
16 #include "advent.h"
17
18 #define DIM(a) (sizeof(a)/sizeof(a[0]))
19
20 #if defined ADVENT_AUTOSAVE
21 static FILE* autosave_fp;
22 void autosave(void)
23 {
24     if (autosave_fp != NULL) {
25         rewind(autosave_fp);
26         savefile(autosave_fp);
27         fflush(autosave_fp);
28     }
29 }
30 #endif
31
32 // LCOV_EXCL_START
33 // exclude from coverage analysis because it requires interactivity to test
34 static void sig_handler(int signo)
35 {
36     if (signo == SIGINT) {
37         if (settings.logfp != NULL)
38             fflush(settings.logfp);
39     }
40
41 #if defined ADVENT_AUTOSAVE
42     if (signo == SIGHUP || signo == SIGTERM)
43         autosave();
44 #endif
45     exit(EXIT_FAILURE);
46 }
47 // LCOV_EXCL_STOP
48
49 char *myreadline(const char *prompt)
50 {
51     /*
52      * This function isn't required for gameplay, readline() straight
53      * up would suffice for that.  It's where we interpret command-line
54      * logfiles for testing purposes.
55      */
56     /* Normal case - no script arguments */
57     if (settings.argc == 0) {
58         char *ln = readline(prompt);
59         if (ln == NULL) {
60             fputs(prompt, stdout);
61         }
62         return ln;
63     }
64
65     char *buf = malloc(LINESIZE + 1);
66     for (;;) {
67         if (settings.scriptfp == NULL || feof(settings.scriptfp)) {
68             if (settings.optind >= settings.argc) {
69                 free(buf);
70                 return NULL;
71             }
72
73             char *next = settings.argv[settings.optind++];
74
75             if (settings.scriptfp != NULL && feof(settings.scriptfp))
76                 fclose(settings.scriptfp);
77             if (strcmp(next, "-") == 0)
78                 settings.scriptfp = stdin; // LCOV_EXCL_LINE
79             else
80                 settings.scriptfp = fopen(next, "r");
81         }
82
83         if (isatty(fileno(settings.scriptfp))) {
84             free(buf); // LCOV_EXCL_LINE
85             return readline(prompt); // LCOV_EXCL_LINE
86         } else {
87             char *ln = fgets(buf, LINESIZE, settings.scriptfp);
88             if (ln != NULL) {
89                 fputs(prompt, stdout);
90                 fputs(ln, stdout);
91                 return ln;
92             }
93         }
94     }
95
96     return NULL;
97 }
98
99 /*  Check if this loc is eligible for any hints.  If been here int
100  *  enough, display.  Ignore "HINTS" < 4 (special stuff, see database
101  *  notes). */
102 static void checkhints(void)
103 {
104     if (conditions[game.loc] >= game.conds) {
105         for (int hint = 0; hint < NHINTS; hint++) {
106             if (game.hints[hint].used)
107                 continue;
108             if (!CNDBIT(game.loc, hint + 1 + COND_HBASE))
109                 game.hints[hint].lc = -1;
110             ++game.hints[hint].lc;
111             /*  Come here if he's been int enough at required loc(s) for some
112              *  unused hint. */
113             if (game.hints[hint].lc >= hints[hint].turns) {
114                 int i;
115
116                 switch (hint) {
117                 case 0:
118                     /* cave */
119                     if (game.objects[GRATE].prop == GRATE_CLOSED && !HERE(KEYS))
120                         break;
121                     game.hints[hint].lc = 0;
122                     return;
123                 case 1: /* bird */
124                     if (game.objects[BIRD].place == game.loc && TOTING(ROD) && game.oldobj == BIRD)
125                         break;
126                     return;
127                 case 2: /* snake */
128                     if (HERE(SNAKE) && !HERE(BIRD))
129                         break;
130                     game.hints[hint].lc = 0;
131                     return;
132                 case 3: /* maze */
133                     if (game.locs[game.loc].atloc == NO_OBJECT &&
134                         game.locs[game.oldloc].atloc == NO_OBJECT &&
135                         game.locs[game.oldlc2].atloc == NO_OBJECT &&
136                         game.holdng > 1)
137                         break;
138                     game.hints[hint].lc = 0;
139                     return;
140                 case 4: /* dark */
141                     if (!PROP_IS_NOTFOUND(EMERALD) && PROP_IS_NOTFOUND(PYRAMID))
142                         break;
143                     game.hints[hint].lc = 0;
144                     return;
145                 case 5: /* witt */
146                     break;
147                 case 6: /* urn */
148                     if (game.dflag == 0)
149                         break;
150                     game.hints[hint].lc = 0;
151                     return;
152                 case 7: /* woods */
153                     if (game.locs[game.loc].atloc == NO_OBJECT &&
154                         game.locs[game.oldloc].atloc == NO_OBJECT &&
155                         game.locs[game.oldlc2].atloc == NO_OBJECT)
156                         break;
157                     return;
158                 case 8: /* ogre */
159                     i = atdwrf(game.loc);
160                     if (i < 0) {
161                         game.hints[hint].lc = 0;
162                         return;
163                     }
164                     if (HERE(OGRE) && i == 0)
165                         break;
166                     return;
167                 case 9: /* jade */
168                     if (game.tally == 1 && PROP_IS_STASHED_OR_UNSEEN(JADE))
169                         break;
170                     game.hints[hint].lc = 0;
171                     return;
172                 default: // LCOV_EXCL_LINE
173                     // Should never happen
174                     BUG(HINT_NUMBER_EXCEEDS_GOTO_LIST); // LCOV_EXCL_LINE
175                 }
176
177                 /* Fall through to hint display */
178                 game.hints[hint].lc = 0;
179                 if (!yes_or_no(hints[hint].question, arbitrary_messages[NO_MESSAGE], arbitrary_messages[OK_MAN]))
180                     return;
181                 rspeak(HINT_COST, hints[hint].penalty, hints[hint].penalty);
182                 game.hints[hint].used = yes_or_no(arbitrary_messages[WANT_HINT], hints[hint].hint, arbitrary_messages[OK_MAN]);
183                 if (game.hints[hint].used && game.limit > WARNTIME)
184                     game.limit += WARNTIME * hints[hint].penalty;
185             }
186         }
187     }
188 }
189
190 static bool spotted_by_pirate(int i)
191 {
192     if (i != PIRATE)
193         return false;
194
195     /*  The pirate's spotted him.  Pirate leaves him alone once we've
196      *  found chest.  K counts if a treasure is here.  If not, and
197      *  tally=1 for an unseen chest, let the pirate be spotted.  Note
198      *  that game.objexts,place[CHEST] = LOC_NOWHERE might mean that he's thrown
199      *  it to the troll, but in that case he's seen the chest
200      *  PROP_IS_FOUND(CHEST) == true. */
201     if (game.loc == game.chloc || !PROP_IS_NOTFOUND(CHEST))
202         return true;
203     int snarfed = 0;
204     bool movechest = false, robplayer = false;
205     for (int treasure = 1; treasure <= NOBJECTS; treasure++) {
206         if (!objects[treasure].is_treasure)
207             continue;
208         /*  Pirate won't take pyramid from plover room or dark
209          *  room (too easy!). */
210         if (treasure == PYRAMID && (game.loc == objects[PYRAMID].plac ||
211                                     game.loc == objects[EMERALD].plac)) {
212             continue;
213         }
214         if (TOTING(treasure) || HERE(treasure))
215             ++snarfed;
216         if (TOTING(treasure)) {
217             movechest = true;
218             robplayer = true;
219         }
220     }
221     /* Force chest placement before player finds last treasure */
222     if (game.tally == 1 && snarfed == 0 && game.objects[CHEST].place == LOC_NOWHERE && HERE(LAMP) && game.objects[LAMP].prop == LAMP_BRIGHT) {
223         rspeak(PIRATE_SPOTTED);
224         movechest = true;
225     }
226     /* Do things in this order (chest move before robbery) so chest is listed
227      * last at the maze location. */
228     if (movechest) {
229         move(CHEST, game.chloc);
230         move(MESSAG, game.chloc2);
231         game.dwarves[PIRATE].loc = game.chloc;
232         game.dwarves[PIRATE].oldloc = game.chloc;
233         game.dwarves[PIRATE].seen = false;
234     } else {
235         /* You might get a hint of the pirate's presence even if the
236          * chest doesn't move... */
237         if (game.dwarves[PIRATE].oldloc != game.dwarves[PIRATE].loc && PCT(20))
238             rspeak(PIRATE_RUSTLES);
239     }
240     if (robplayer) {
241         rspeak(PIRATE_POUNCES);
242         for (int treasure = 1; treasure <= NOBJECTS; treasure++) {
243             if (!objects[treasure].is_treasure)
244                 continue;
245             if (!(treasure == PYRAMID && (game.loc == objects[PYRAMID].plac ||
246                                           game.loc == objects[EMERALD].plac))) {
247                 if (AT(treasure) && game.objects[treasure].fixed == IS_FREE)
248                     carry(treasure, game.loc);
249                 if (TOTING(treasure))
250                     drop(treasure, game.chloc);
251             }
252         }
253     }
254
255     return true;
256 }
257
258 static bool dwarfmove(void)
259 /* Dwarves move.  Return true if player survives, false if he dies. */
260 {
261     int kk, stick, attack;
262     loc_t tk[21];
263
264     /*  Dwarf stuff.  See earlier comments for description of
265      *  variables.  Remember sixth dwarf is pirate and is thus
266      *  very different except for motion rules. */
267
268     /*  First off, don't let the dwarves follow him into a pit or a
269      *  wall.  Activate the whole mess the first time he gets as far
270      *  as the Hall of Mists (what INDEEP() tests).  If game.newloc
271      *  is forbidden to pirate (in particular, if it's beyond the
272      *  troll bridge), bypass dwarf stuff.  That way pirate can't
273      *  steal return toll, and dwarves can't meet the bear.  Also
274      *  means dwarves won't follow him into dead end in maze, but
275      *  c'est la vie.  They'll wait for him outside the dead end. */
276     if (game.loc == LOC_NOWHERE || FORCED(game.loc) || CNDBIT(game.newloc, COND_NOARRR))
277         return true;
278
279     /* Dwarf activity level ratchets up */
280     if (game.dflag == 0) {
281         if (INDEEP(game.loc))
282             game.dflag = 1;
283         return true;
284     }
285
286     /*  When we encounter the first dwarf, we kill 0, 1, or 2 of
287      *  the 5 dwarves.  If any of the survivors is at game.loc,
288      *  replace him with the alternate. */
289     if (game.dflag == 1) {
290         if (!INDEEP(game.loc) ||
291             (PCT(95) && (!CNDBIT(game.loc, COND_NOBACK) || PCT(85))))
292             return true;
293         game.dflag = 2;
294         for (int i = 1; i <= 2; i++) {
295             int j = 1 + randrange(NDWARVES - 1);
296             if (PCT(50))
297                 game.dwarves[j].loc = 0;
298         }
299
300         /* Alternate initial loc for dwarf, in case one of them
301         *  starts out on top of the adventurer. */
302         for (int i = 1; i <= NDWARVES - 1; i++) {
303             if (game.dwarves[i].loc == game.loc)
304                 game.dwarves[i].loc = DALTLC; //
305             game.dwarves[i].oldloc = game.dwarves[i].loc;
306         }
307         rspeak(DWARF_RAN);
308         drop(AXE, game.loc);
309         return true;
310     }
311
312     /*  Things are in full swing.  Move each dwarf at random,
313      *  except if he's seen us he sticks with us.  Dwarves stay
314      *  deep inside.  If wandering at random, they don't back up
315      *  unless there's no alternative.  If they don't have to
316      *  move, they attack.  And, of course, dead dwarves don't do
317      *  much of anything. */
318     game.dtotal = 0;
319     attack = 0;
320     stick = 0;
321     for (int i = 1; i <= NDWARVES; i++) {
322         if (game.dwarves[i].loc == 0)
323             continue;
324         /*  Fill tk array with all the places this dwarf might go. */
325         unsigned int j = 1;
326         kk = tkey[game.dwarves[i].loc];
327         if (kk != 0)
328             do {
329                 enum desttype_t desttype = travel[kk].desttype;
330                 game.newloc = travel[kk].destval;
331                 /* Have we avoided a dwarf encounter? */
332                 if (desttype != dest_goto)
333                     continue;
334                 else if (!INDEEP(game.newloc))
335                     continue;
336                 else if (game.newloc == game.dwarves[i].oldloc)
337                     continue;
338                 else if (j > 1 && game.newloc == tk[j - 1])
339                     continue;
340                 else if (j >= DIM(tk) - 1)
341                     /* This can't actually happen. */
342                     continue; // LCOV_EXCL_LINE
343                 else if (game.newloc == game.dwarves[i].loc)
344                     continue;
345                 else if (FORCED(game.newloc))
346                     continue;
347                 else if (i == PIRATE && CNDBIT(game.newloc, COND_NOARRR))
348                     continue;
349                 else if (travel[kk].nodwarves)
350                     continue;
351                 tk[j++] = game.newloc;
352             } while
353             (!travel[kk++].stop);
354         tk[j] = game.dwarves[i].oldloc;
355         if (j >= 2)
356             --j;
357         j = 1 + randrange(j);
358         game.dwarves[i].oldloc = game.dwarves[i].loc;
359         game.dwarves[i].loc = tk[j];
360         game.dwarves[i].seen = (game.dwarves[i].seen && INDEEP(game.loc)) ||
361                         (game.dwarves[i].loc == game.loc ||
362                          game.dwarves[i].oldloc == game.loc);
363         if (!game.dwarves[i].seen)
364             continue;
365         game.dwarves[i].loc = game.loc;
366         if (spotted_by_pirate(i))
367             continue;
368         /* This threatening little dwarf is in the room with him! */
369         ++game.dtotal;
370         if (game.dwarves[i].oldloc == game.dwarves[i].loc) {
371             ++attack;
372             if (game.knfloc >= LOC_NOWHERE)
373                 game.knfloc = game.loc;
374             if (randrange(1000) < 95 * (game.dflag - 2))
375                 ++stick;
376         }
377     }
378
379     /*  Now we know what's happening.  Let's tell the poor sucker about it. */
380     if (game.dtotal == 0)
381         return true;
382     rspeak(game.dtotal == 1 ? DWARF_SINGLE : DWARF_PACK, game.dtotal);
383     if (attack == 0)
384         return true;
385     if (game.dflag == 2)
386         game.dflag = 3;
387     if (attack > 1) {
388         rspeak(THROWN_KNIVES, attack);
389         rspeak(stick > 1 ? MULTIPLE_HITS : (stick == 1 ? ONE_HIT : NONE_HIT), stick);
390     } else {
391         rspeak(KNIFE_THROWN);
392         rspeak(stick ? GETS_YOU : MISSES_YOU);
393     }
394     if (stick == 0)
395         return true;
396     game.oldlc2 = game.loc;
397     return false;
398 }
399
400 /*  "You're dead, Jim."
401  *
402  *  If the current loc is zero, it means the clown got himself killed.
403  *  We'll allow this maxdie times.  NDEATHS is automatically set based
404  *  on the number of snide messages available.  Each death results in
405  *  a message (obituaries[n]) which offers reincarnation; if accepted,
406  *  this results in message obituaries[0], obituaries[2], etc.  The
407  *  last time, if he wants another chance, he gets a snide remark as
408  *  we exit.  When reincarnated, all objects being carried get dropped
409  *  at game.oldlc2 (presumably the last place prior to being killed)
410  *  without change of props.  The loop runs backwards to assure that
411  *  the bird is dropped before the cage.  (This kluge could be changed
412  *  once we're sure all references to bird and cage are done by
413  *  keywords.)  The lamp is a special case (it wouldn't do to leave it
414  *  in the cave). It is turned off and left outside the building (only
415  *  if he was carrying it, of course).  He himself is left inside the
416  *  building (and heaven help him if he tries to xyzzy back into the
417  *  cave without the lamp!).  game.oldloc is zapped so he can't just
418  *  "retreat". */
419 static void croak(void)
420 /*  Okay, he's dead.  Let's get on with it. */
421 {
422     const char* query = obituaries[game.numdie].query;
423     const char* yes_response = obituaries[game.numdie].yes_response;
424
425     ++game.numdie;
426
427     if (game.closng) {
428         /*  He died during closing time.  No resurrection.  Tally up a
429          *  death and exit. */
430         rspeak(DEATH_CLOSING);
431         terminate(endgame);
432     } else if (!yes_or_no(query, yes_response, arbitrary_messages[OK_MAN])
433                || game.numdie == NDEATHS) {
434         /* Player is asked if he wants to try again. If not, or if
435          * he's already used all of his lives, we end the game */
436         terminate(endgame);
437     } else {
438         /* If player wishes to continue, we empty the liquids in the
439          * user's inventory, turn off the lamp, and drop all items
440          * where he died. */
441         game.objects[WATER].place = game.objects[OIL].place = LOC_NOWHERE;
442         if (TOTING(LAMP))
443             game.objects[LAMP].prop = LAMP_DARK;
444         for (int j = 1; j <= NOBJECTS; j++) {
445             int i = NOBJECTS + 1 - j;
446             if (TOTING(i)) {
447                 /* Always leave lamp where it's accessible aboveground */
448                 drop(i, (i == LAMP) ? LOC_START : game.oldlc2);
449             }
450         }
451         game.oldloc = game.loc = game.newloc = LOC_BUILDING;
452     }
453 }
454
455 static void describe_location(void)
456 /* Describe the location to the user */
457 {
458     const char* msg = locations[game.loc].description.small;
459
460     if (MOD(game.locs[game.loc].abbrev, game.abbnum) == 0 || msg == NO_MESSAGE)
461         msg = locations[game.loc].description.big;
462
463     if (!FORCED(game.loc) && DARK(game.loc)) {
464         msg = arbitrary_messages[PITCH_DARK];
465     }
466
467     if (TOTING(BEAR))
468         rspeak(TAME_BEAR);
469
470     speak(msg);
471
472     if (game.loc == LOC_Y2 && PCT(25) && !game.closng)
473         rspeak(SAYS_PLUGH);
474 }
475
476
477 static bool traveleq(int a, int b)
478 /* Are two travel entries equal for purposes of skip after failed condition? */
479 {
480     return (travel[a].condtype == travel[b].condtype)
481            && (travel[a].condarg1 == travel[b].condarg1)
482            && (travel[a].condarg2 == travel[b].condarg2)
483            && (travel[a].desttype == travel[b].desttype)
484            && (travel[a].destval == travel[b].destval);
485 }
486
487 /*  Given the current location in "game.loc", and a motion verb number in
488  *  "motion", put the new location in "game.newloc".  The current loc is saved
489  *  in "game.oldloc" in case he wants to retreat.  The current
490  *  game.oldloc is saved in game.oldlc2, in case he dies.  (if he
491  *  does, game.newloc will be limbo, and game.oldloc will be what killed
492  *  him, so we need game.oldlc2, which is the last place he was
493  *  safe.) */
494 static void playermove(int motion)
495 {
496     int scratchloc, travel_entry = tkey[game.loc];
497     game.newloc = game.loc;
498     if (travel_entry == 0)
499         BUG(LOCATION_HAS_NO_TRAVEL_ENTRIES); // LCOV_EXCL_LINE
500     if (motion == NUL)
501         return;
502     else if (motion == BACK) {
503         /*  Handle "go back".  Look for verb which goes from game.loc to
504          *  game.oldloc, or to game.oldlc2 If game.oldloc has forced-motion.
505          *  te_tmp saves entry -> forced loc -> previous loc. */
506         motion = game.oldloc;
507         if (FORCED(motion))
508             motion = game.oldlc2;
509         game.oldlc2 = game.oldloc;
510         game.oldloc = game.loc;
511         if (CNDBIT(game.loc, COND_NOBACK)) {
512             rspeak(TWIST_TURN);
513             return;
514         }
515         if (motion == game.loc) {
516             rspeak(FORGOT_PATH);
517             return;
518         }
519
520         int te_tmp = 0;
521         for (;;) {
522             enum desttype_t desttype = travel[travel_entry].desttype;
523             scratchloc = travel[travel_entry].destval;
524             if (desttype != dest_goto || scratchloc != motion) {
525                 if (desttype == dest_goto) {
526                     if (FORCED(scratchloc) && travel[tkey[scratchloc]].destval == motion)
527                         te_tmp = travel_entry;
528                 }
529                 if (!travel[travel_entry].stop) {
530                     ++travel_entry;     /* go to next travel entry for this location */
531                     continue;
532                 }
533                 /* we've reached the end of travel entries for game.loc */
534                 travel_entry = te_tmp;
535                 if (travel_entry == 0) {
536                     rspeak(NOT_CONNECTED);
537                     return;
538                 }
539             }
540
541             motion = travel[travel_entry].motion;
542             travel_entry = tkey[game.loc];
543             break; /* fall through to ordinary travel */
544         }
545     } else if (motion == LOOK) {
546         /*  Look.  Can't give more detail.  Pretend it wasn't dark
547          *  (though it may now be dark) so he won't fall into a
548          *  pit while staring into the gloom. */
549         if (game.detail < 3)
550             rspeak(NO_MORE_DETAIL);
551         ++game.detail;
552         game.wzdark = false;
553         game.locs[game.loc].abbrev = 0;
554         return;
555     } else if (motion == CAVE) {
556         /*  Cave.  Different messages depending on whether above ground. */
557         rspeak((OUTSID(game.loc) && game.loc != LOC_GRATE) ? FOLLOW_STREAM : NEED_DETAIL);
558         return;
559     } else {
560         /* none of the specials */
561         game.oldlc2 = game.oldloc;
562         game.oldloc = game.loc;
563     }
564
565     /* Look for a way to fulfil the motion verb passed in - travel_entry indexes
566      * the beginning of the motion entries for here (game.loc). */
567     for (;;) {
568         if ((travel[travel_entry].motion == HERE) || travel[travel_entry].motion == motion)
569             break;
570         if (travel[travel_entry].stop) {
571             /*  Couldn't find an entry matching the motion word passed
572              *  in.  Various messages depending on word given. */
573             switch (motion) {
574             case EAST:
575             case WEST:
576             case SOUTH:
577             case NORTH:
578             case NE:
579             case NW:
580             case SW:
581             case SE:
582             case UP:
583             case DOWN:
584                 rspeak(BAD_DIRECTION);
585                 break;
586             case FORWARD:
587             case LEFT:
588             case RIGHT:
589                 rspeak(UNSURE_FACING);
590                 break;
591             case OUTSIDE:
592             case INSIDE:
593                 rspeak(NO_INOUT_HERE);
594                 break;
595             case XYZZY:
596             case PLUGH:
597                 rspeak(NOTHING_HAPPENS);
598                 break;
599             case CRAWL:
600                 rspeak(WHICH_WAY);
601                 break;
602             default:
603                 rspeak(CANT_APPLY);
604             }
605             return;
606         }
607         ++travel_entry;
608     }
609
610     /* (ESR) We've found a destination that goes with the motion verb.
611      * Next we need to check any conditional(s) on this destination, and
612      * possibly on following entries. */
613     do {
614         for (;;) { /* L12 loop */
615             for (;;) {
616                 enum condtype_t condtype = travel[travel_entry].condtype;
617                 int condarg1 = travel[travel_entry].condarg1;
618                 int condarg2 = travel[travel_entry].condarg2;
619                 if (condtype < cond_not) {
620                     /* YAML N and [pct N] conditionals */
621                     if (condtype == cond_goto || condtype == cond_pct) {
622                         if (condarg1 == 0 || PCT(condarg1))
623                             break;
624                         /* else fall through */
625                     }
626                     /* YAML [with OBJ] clause */
627                     else if (TOTING(condarg1) || (condtype == cond_with && AT(condarg1)))
628                         break;
629                     /* else fall through to check [not OBJ STATE] */
630                 } else if (game.objects[condarg1].prop != condarg2)
631                     break;
632
633                 /* We arrive here on conditional failure.
634                  * Skip to next non-matching destination */
635                 int te_tmp = travel_entry;
636                 do {
637                     if (travel[te_tmp].stop)
638                         BUG(CONDITIONAL_TRAVEL_ENTRY_WITH_NO_ALTERATION); // LCOV_EXCL_LINE
639                     ++te_tmp;
640                 } while
641                 (traveleq(travel_entry, te_tmp));
642                 travel_entry = te_tmp;
643             }
644
645             /* Found an eligible rule, now execute it */
646             enum desttype_t desttype = travel[travel_entry].desttype;
647             game.newloc = travel[travel_entry].destval;
648             if (desttype == dest_goto)
649                 return;
650
651             if (desttype == dest_speak) {
652                 /* Execute a speak rule */
653                 rspeak(game.newloc);
654                 game.newloc = game.loc;
655                 return;
656             } else {
657                 switch (game.newloc) {
658                 case 1:
659                     /* Special travel 1.  Plover-alcove passage.  Can carry only
660                      * emerald.  Note: travel table must include "useless"
661                      * entries going through passage, which can never be used
662                      * for actual motion, but can be spotted by "go back". */
663                     game.newloc = (game.loc == LOC_PLOVER)
664                                   ? LOC_ALCOVE
665                                   : LOC_PLOVER;
666                     if (game.holdng > 1 || (game.holdng == 1 && !TOTING(EMERALD))) {
667                         game.newloc = game.loc;
668                         rspeak(MUST_DROP);
669                     }
670                     return;
671                 case 2:
672                     /* Special travel 2.  Plover transport.  Drop the
673                      * emerald (only use special travel if toting
674                      * it), so he's forced to use the plover-passage
675                      * to get it out.  Having dropped it, go back and
676                      * pretend he wasn't carrying it after all. */
677                     drop(EMERALD, game.loc);
678                     {
679                         int te_tmp = travel_entry;
680                         do {
681                             if (travel[te_tmp].stop)
682                                 BUG(CONDITIONAL_TRAVEL_ENTRY_WITH_NO_ALTERATION); // LCOV_EXCL_LINE
683                             ++te_tmp;
684                         } while
685                         (traveleq(travel_entry, te_tmp));
686                         travel_entry = te_tmp;
687                     }
688                     continue; /* goto L12 */
689                 case 3:
690                     /* Special travel 3.  Troll bridge.  Must be done
691                      * only as special motion so that dwarves won't
692                      * wander across and encounter the bear.  (They
693                      * won't follow the player there because that
694                      * region is forbidden to the pirate.)  If
695                      * game.prop[TROLL]=TROLL_PAIDONCE, he's crossed
696                      * since paying, so step out and block him.
697                      * (standard travel entries check for
698                      * game.prop[TROLL]=TROLL_UNPAID.)  Special stuff
699                      * for bear. */
700                     if (game.objects[TROLL].prop == TROLL_PAIDONCE) {
701                         pspeak(TROLL, look, true, TROLL_PAIDONCE);
702                         game.objects[TROLL].prop = TROLL_UNPAID;
703                         DESTROY(TROLL2);
704                         move(TROLL2 + NOBJECTS, IS_FREE);
705                         move(TROLL, objects[TROLL].plac);
706                         move(TROLL + NOBJECTS, objects[TROLL].fixd);
707                         juggle(CHASM);
708                         game.newloc = game.loc;
709                         return;
710                     } else {
711                         game.newloc = objects[TROLL].plac + objects[TROLL].fixd - game.loc;
712                         if (game.objects[TROLL].prop == TROLL_UNPAID)
713                             game.objects[TROLL].prop = TROLL_PAIDONCE;
714                         if (!TOTING(BEAR))
715                             return;
716                         state_change(CHASM, BRIDGE_WRECKED);
717                         game.objects[TROLL].prop = TROLL_GONE;
718                         drop(BEAR, game.newloc);
719                         game.objects[BEAR].fixed = IS_FIXED;
720                         game.objects[BEAR].prop = BEAR_DEAD;
721                         game.oldlc2 = game.newloc;
722                         croak();
723                         return;
724                     }
725                 default: // LCOV_EXCL_LINE
726                     BUG(SPECIAL_TRAVEL_500_GT_L_GT_300_EXCEEDS_GOTO_LIST); // LCOV_EXCL_LINE
727                 }
728             }
729             break; /* Leave L12 loop */
730         }
731     } while
732     (false);
733 }
734
735 static void lampcheck(void)
736 /* Check game limit and lamp timers */
737 {
738     if (game.objects[LAMP].prop == LAMP_BRIGHT)
739         --game.limit;
740
741     /*  Another way we can force an end to things is by having the
742      *  lamp give out.  When it gets close, we come here to warn him.
743      *  First following arm checks if the lamp and fresh batteries are
744      *  here, in which case we replace the batteries and continue.
745      *  Second is for other cases of lamp dying.  Even after it goes
746      *  out, he can explore outside for a while if desired. */
747     if (game.limit <= WARNTIME) {
748         if (HERE(BATTERY) && game.objects[BATTERY].prop == FRESH_BATTERIES && HERE(LAMP)) {
749             rspeak(REPLACE_BATTERIES);
750             game.objects[BATTERY].prop = DEAD_BATTERIES;
751 #ifdef __unused__
752             /* This code from the original game seems to have been faulty.
753              * No tests ever passed the guard, and with the guard removed
754              * the game hangs when the lamp limit is reached.
755              */
756             if (TOTING(BATTERY))
757                 drop(BATTERY, game.loc);
758 #endif
759             game.limit += BATTERYLIFE;
760             game.lmwarn = false;
761         } else if (!game.lmwarn && HERE(LAMP)) {
762             game.lmwarn = true;
763             if (game.objects[BATTERY].prop == DEAD_BATTERIES)
764                 rspeak(MISSING_BATTERIES);
765             else if (game.objects[BATTERY].place == LOC_NOWHERE)
766                 rspeak(LAMP_DIM);
767             else
768                 rspeak(GET_BATTERIES);
769         }
770     }
771     if (game.limit == 0) {
772         game.limit = -1;
773         game.objects[LAMP].prop = LAMP_DARK;
774         if (HERE(LAMP))
775             rspeak(LAMP_OUT);
776     }
777 }
778
779 static bool closecheck(void)
780 /*  Handle the closing of the cave.  The cave closes "clock1" turns
781  *  after the last treasure has been located (including the pirate's
782  *  chest, which may of course never show up).  Note that the
783  *  treasures need not have been taken yet, just located.  Hence
784  *  clock1 must be large enough to get out of the cave (it only ticks
785  *  while inside the cave).  When it hits zero, we start closing the
786  *  cave, and then sit back and wait for him to try to get out.  If he
787  *  doesn't within clock2 turns, we close the cave; if he does try, we
788  *  assume he panics, and give him a few additional turns to get
789  *  frantic before we close.  When clock2 hits zero, we transport him
790  *  into the final puzzle.  Note that the puzzle depends upon all
791  *  sorts of random things.  For instance, there must be no water or
792  *  oil, since there are beanstalks which we don't want to be able to
793  *  water, since the code can't handle it.  Also, we can have no keys,
794  *  since there is a grate (having moved the fixed object!)  there
795  *  separating him from all the treasures.  Most of these problems
796  *  arise from the use of negative prop numbers to suppress the object
797  *  descriptions until he's actually moved the objects. */
798 {
799     /* If a turn threshold has been met, apply penalties and tell
800      * the player about it. */
801     for (int i = 0; i < NTHRESHOLDS; ++i) {
802         if (game.turns == turn_thresholds[i].threshold + 1) {
803             game.trnluz += turn_thresholds[i].point_loss;
804             speak(turn_thresholds[i].message);
805         }
806     }
807
808     /*  Don't tick game.clock1 unless well into cave (and not at Y2). */
809     if (game.tally == 0 && INDEEP(game.loc) && game.loc != LOC_Y2)
810         --game.clock1;
811
812     /*  When the first warning comes, we lock the grate, destroy
813      *  the bridge, kill all the dwarves (and the pirate), remove
814      *  the troll and bear (unless dead), and set "closng" to
815      *  true.  Leave the dragon; too much trouble to move it.
816      *  from now until clock2 runs out, he cannot unlock the
817      *  grate, move to any location outside the cave, or create
818      *  the bridge.  Nor can he be resurrected if he dies.  Note
819      *  that the snake is already gone, since he got to the
820      *  treasure accessible only via the hall of the mountain
821      *  king. Also, he's been in giant room (to get eggs), so we
822      *  can refer to it.  Also also, he's gotten the pearl, so we
823      *  know the bivalve is an oyster.  *And*, the dwarves must
824      *  have been activated, since we've found chest. */
825     if (game.clock1 == 0) {
826         game.objects[GRATE].prop = GRATE_CLOSED;
827         game.objects[FISSURE].prop = UNBRIDGED;
828         for (int i = 1; i <= NDWARVES; i++) {
829             game.dwarves[i].seen = false;
830             game.dwarves[i].loc = LOC_NOWHERE;
831         }
832         DESTROY(TROLL);
833         move(TROLL + NOBJECTS, IS_FREE);
834         move(TROLL2, objects[TROLL].plac);
835         move(TROLL2 + NOBJECTS, objects[TROLL].fixd);
836         juggle(CHASM);
837         if (game.objects[BEAR].prop != BEAR_DEAD)
838             DESTROY(BEAR);
839         game.objects[CHAIN].prop = CHAIN_HEAP;
840         game.objects[CHAIN].fixed = IS_FREE;
841         game.objects[AXE].prop = AXE_HERE;
842         game.objects[AXE].fixed = IS_FREE;
843         rspeak(CAVE_CLOSING);
844         game.clock1 = -1;
845         game.closng = true;
846         return game.closed;
847     } else if (game.clock1 < 0)
848         --game.clock2;
849     if (game.clock2 == 0) {
850         /*  Once he's panicked, and clock2 has run out, we come here
851          *  to set up the storage room.  The room has two locs,
852          *  hardwired as LOC_NE and LOC_SW.  At the ne end, we
853          *  place empty bottles, a nursery of plants, a bed of
854          *  oysters, a pile of lamps, rods with stars, sleeping
855          *  dwarves, and him.  At the sw end we place grate over
856          *  treasures, snake pit, covey of caged birds, more rods, and
857          *  pillows.  A mirror stretches across one wall.  Many of the
858          *  objects come from known locations and/or states (e.g. the
859          *  snake is known to have been destroyed and needn't be
860          *  carried away from its old "place"), making the various
861          *  objects be handled differently.  We also drop all other 
862          *  objects he might be carrying (lest he has some which
863          *  could cause trouble, such as the keys).  We describe the
864          *  flash of light and trundle back. */
865         put(BOTTLE, LOC_NE, EMPTY_BOTTLE);
866         put(PLANT, LOC_NE, PLANT_THIRSTY);
867         put(OYSTER, LOC_NE, STATE_FOUND);
868         put(LAMP, LOC_NE, LAMP_DARK);
869         put(ROD, LOC_NE, STATE_FOUND);
870         put(DWARF, LOC_NE, STATE_FOUND);
871         game.loc = LOC_NE;
872         game.oldloc = LOC_NE;
873         game.newloc = LOC_NE;
874         /*  Leave the grate with normal (non-negative) property.
875          *  Reuse sign. */
876         move(GRATE, LOC_SW);
877         move(SIGN, LOC_SW);
878         game.objects[SIGN].prop = ENDGAME_SIGN;
879         put(SNAKE, LOC_SW, SNAKE_CHASED);
880         put(BIRD, LOC_SW, BIRD_CAGED);
881         put(CAGE, LOC_SW, STATE_FOUND);
882         put(ROD2, LOC_SW, STATE_FOUND);
883         put(PILLOW, LOC_SW, STATE_FOUND);
884
885         put(MIRROR, LOC_NE, STATE_FOUND);
886         game.objects[MIRROR].fixed = LOC_SW;
887
888         for (int i = 1; i <= NOBJECTS; i++) {
889             if (TOTING(i))
890                 DESTROY(i);
891         }
892
893         rspeak(CAVE_CLOSED);
894         game.closed = true;
895         return game.closed;
896     }
897
898     lampcheck();
899     return false;
900 }
901
902 static void listobjects(void)
903 /*  Print out descriptions of objects at this location.  If
904  *  not closing and property value is negative, tally off
905  *  another treasure.  Rug is special case; once seen, its
906  *  game.prop is RUG_DRAGON (dragon on it) till dragon is killed.
907  *  Similarly for chain; game.prop is initially CHAINING_BEAR (locked to
908  *  bear).  These hacks are because game.prop=0 is needed to
909  *  get full score. */
910 {
911     if (!DARK(game.loc)) {
912         ++game.locs[game.loc].abbrev;
913         for (int i = game.locs[game.loc].atloc; i != 0; i = game.link[i]) {
914             obj_t obj = i;
915             if (obj > NOBJECTS)
916                 obj = obj - NOBJECTS;
917             if (obj == STEPS && TOTING(NUGGET))
918                 continue;
919             /* (ESR) Warning: it looks like you could get away with
920              * running this code only on objects with the treasure
921              * property set. Nope.  There is mystery here.
922              */
923             if (PROP_IS_STASHED_OR_UNSEEN(obj)) {
924                 if (game.closed)
925                     continue;
926                 PROP_SET_FOUND(obj);
927                 if (obj == RUG)
928                     game.objects[RUG].prop = RUG_DRAGON;
929                 if (obj == CHAIN)
930                     game.objects[CHAIN].prop = CHAINING_BEAR;
931                 if (obj == EGGS)
932                     game.seenbigwords = true;
933                 --game.tally;
934                 /*  Note: There used to be a test here to see whether the
935                  *  player had blown it so badly that he could never ever see
936                  *  the remaining treasures, and if so the lamp was zapped to
937                  *  35 turns.  But the tests were too simple-minded; things
938                  *  like killing the bird before the snake was gone (can never
939                  *  see jewelry), and doing it "right" was hopeless.  E.G.,
940                  *  could cross troll bridge several times, using up all
941                  *  available treasures, breaking vase, using coins to buy
942                  *  batteries, etc., and eventually never be able to get
943                  *  across again.  If bottle were left on far side, could then
944                  *  never get eggs or trident, and the effects propagate.  So
945                  *  the whole thing was flushed.  anyone who makes such a
946                  *  gross blunder isn't likely to find everything else anyway
947                  *  (so goes the rationalisation). */
948             }
949             int kk = game.objects[obj].prop;
950             if (obj == STEPS)
951                 kk = (game.loc == game.objects[STEPS].fixed)
952                      ? STEPS_UP
953                      : STEPS_DOWN;
954             pspeak(obj, look, true, kk);
955         }
956     }
957 }
958
959 static bool preprocess_command(command_t *command)
960 /* Pre-processes a command input to see if we need to tease out a few specific cases:
961  * - "enter water" or "enter stream":
962  *   weird specific case that gets the user wet, and then kicks us back to get another command
963  * - <object> <verb>:
964  *   Irregular form of input, but should be allowed. We switch back to <verb> <object> form for
965  *   further processing.
966  * - "grate":
967  *   If in location with grate, we move to that grate. If we're in a number of other places,
968  *   we move to the entrance.
969  * - "water plant", "oil plant", "water door", "oil door":
970  *   Change to "pour water" or "pour oil" based on context
971  * - "cage bird":
972  *   If bird is present, we change to "carry bird"
973  *
974  * Returns true if pre-processing is complete, and we're ready to move to the primary command
975  * processing, false otherwise. */
976 {
977     if (command->word[0].type == MOTION && command->word[0].id == ENTER
978         && (command->word[1].id == STREAM || command->word[1].id == WATER)) {
979         if (LIQLOC(game.loc) == WATER)
980             rspeak(FEET_WET);
981         else
982             rspeak(WHERE_QUERY);
983     } else {
984         if (command->word[0].type == OBJECT) {
985             /* From OV to VO form */
986             if (command->word[1].type == ACTION) {
987                 command_word_t stage = command->word[0];
988                 command->word[0] = command->word[1];
989                 command->word[1] = stage;
990             }
991
992             if (command->word[0].id == GRATE) {
993                 command->word[0].type = MOTION;
994                 if (game.loc == LOC_START ||
995                     game.loc == LOC_VALLEY ||
996                     game.loc == LOC_SLIT) {
997                     command->word[0].id = DEPRESSION;
998                 }
999                 if (game.loc == LOC_COBBLE ||
1000                     game.loc == LOC_DEBRIS ||
1001                     game.loc == LOC_AWKWARD ||
1002                     game.loc == LOC_BIRDCHAMBER ||
1003                     game.loc == LOC_PITTOP) {
1004                     command->word[0].id = ENTRANCE;
1005                 }
1006             }
1007             if ((command->word[0].id == WATER || command->word[0].id == OIL) &&
1008                 (command->word[1].id == PLANT || command->word[1].id == DOOR)) {
1009                 if (AT(command->word[1].id)) {
1010                     command->word[1] = command->word[0];
1011                     command->word[0].id = POUR;
1012                     command->word[0].type = ACTION;
1013                     strncpy(command->word[0].raw, "pour", LINESIZE - 1);
1014                 }
1015             }
1016             if (command->word[0].id == CAGE && command->word[1].id == BIRD && HERE(CAGE) && HERE(BIRD)) {
1017                 command->word[0].id = CARRY;
1018                 command->word[0].type = ACTION;
1019             }
1020         }
1021
1022         /* If no word type is given for the first word, we assume it's a motion. */
1023         if (command->word[0].type == NO_WORD_TYPE)
1024             command->word[0].type = MOTION;
1025
1026         command->state = PREPROCESSED;
1027         return true;
1028     }
1029     return false;
1030 }
1031
1032 static bool do_move(void)
1033 /* Actually execute the move to the new location and dwarf movement */
1034 {
1035     /*  Can't leave cave once it's closing (except by main office). */
1036     if (OUTSID(game.newloc) && game.newloc != 0 && game.closng) {
1037         rspeak(EXIT_CLOSED);
1038         game.newloc = game.loc;
1039         if (!game.panic)
1040             game.clock2 = PANICTIME;
1041         game.panic = true;
1042     }
1043
1044     /*  See if a dwarf has seen him and has come from where he
1045      *  wants to go.  If so, the dwarf's blocking his way.  If
1046      *  coming from place forbidden to pirate (dwarves rooted in
1047      *  place) let him get out (and attacked). */
1048     if (game.newloc != game.loc && !FORCED(game.loc) && !CNDBIT(game.loc, COND_NOARRR)) {
1049         for (size_t i = 1; i <= NDWARVES - 1; i++) {
1050             if (game.dwarves[i].oldloc == game.newloc && game.dwarves[i].seen) {
1051                 game.newloc = game.loc;
1052                 rspeak(DWARF_BLOCK);
1053                 break;
1054             }
1055         }
1056     }
1057     game.loc = game.newloc;
1058
1059     if (!dwarfmove())
1060         croak();
1061
1062     if (game.loc == LOC_NOWHERE)
1063         croak();
1064
1065     /* The easiest way to get killed is to fall into a pit in
1066      * pitch darkness. */
1067     if (!FORCED(game.loc) && DARK(game.loc) && game.wzdark && PCT(PIT_KILL_PROB)) {
1068         rspeak(PIT_FALL);
1069         game.oldlc2 = game.loc;
1070         croak();
1071         return false;
1072     }
1073
1074     return true;
1075 }
1076
1077 static bool do_command(void)
1078 /* Get and execute a command */
1079 {
1080     static command_t command;
1081     clear_command(&command);
1082
1083     /* Describe the current location and (maybe) get next command. */
1084     while (command.state != EXECUTED) {
1085         describe_location();
1086
1087         if (FORCED(game.loc)) {
1088             playermove(HERE);
1089             return true;
1090         }
1091
1092         listobjects();
1093
1094         /* Command not yet given; keep getting commands from user
1095          * until valid command is both given and executed. */
1096         clear_command(&command);
1097         while (command.state <= GIVEN) {
1098
1099             if (game.closed) {
1100                 /*  If closing time, check for any stashed objects
1101                  *  being toted and unstash them.  This way objects
1102                  *  won't be described until they've been picked up
1103                  *  and put down separate from their respective
1104                  *  piles. */
1105                 if ((PROP_IS_NOTFOUND(OYSTER) || PROP_IS_STASHED(OYSTER)) && TOTING(OYSTER))
1106                     pspeak(OYSTER, look, true, 1);
1107                 for (size_t i = 1; i <= NOBJECTS; i++) {
1108                     if (TOTING(i) && (PROP_IS_NOTFOUND(i) || PROP_IS_STASHED(i)))
1109                         game.objects[i].prop = PROP_STASHED(i);
1110                 }
1111             }
1112
1113             /* Check to see if the room is dark. If the knife is here,
1114              * and it's dark, the knife permanently disappears */
1115             game.wzdark = DARK(game.loc);
1116             if (game.knfloc != LOC_NOWHERE && game.knfloc != game.loc)
1117                 game.knfloc = LOC_NOWHERE;
1118
1119             /* Check some for hints, get input from user, increment
1120              * turn, and pre-process commands. Keep going until
1121              * pre-processing is done. */
1122             while ( command.state < PREPROCESSED ) {
1123                 checkhints();
1124
1125                 /* Get command input from user */
1126                 if (!get_command_input(&command))
1127                     return false;
1128
1129                 /* Every input, check "foobar" flag. If zero, nothing's going
1130                  * on. If pos, make neg. If neg, he skipped a word, so make it
1131                  * zero.
1132                  */
1133                 game.foobar = (game.foobar > WORD_EMPTY) ? -game.foobar : WORD_EMPTY;
1134
1135                 ++game.turns;
1136                 preprocess_command(&command);
1137             }
1138
1139             /* check if game is closed, and exit if it is */
1140             if (closecheck() )
1141                 return true;
1142
1143             /* loop until all words in command are processed */
1144             while (command.state == PREPROCESSED ) {
1145                 command.state = PROCESSING;
1146
1147                 if (command.word[0].id == WORD_NOT_FOUND) {
1148                     /* Gee, I don't understand. */
1149                     sspeak(DONT_KNOW, command.word[0].raw);
1150                     clear_command(&command);
1151                     continue;
1152                 }
1153
1154                 /* Give user hints of shortcuts */
1155                 if (strncasecmp(command.word[0].raw, "west", sizeof("west")) == 0) {
1156                     if (++game.iwest == 10)
1157                         rspeak(W_IS_WEST);
1158                 }
1159                 if (strncasecmp(command.word[0].raw, "go", sizeof("go")) == 0 && command.word[1].id != WORD_EMPTY) {
1160                     if (++game.igo == 10)
1161                         rspeak(GO_UNNEEDED);
1162                 }
1163
1164                 switch (command.word[0].type) {
1165                 case MOTION:
1166                     playermove(command.word[0].id);
1167                     command.state = EXECUTED;
1168                     continue;
1169                 case OBJECT:
1170                     command.part = unknown;
1171                     command.obj = command.word[0].id;
1172                     break;
1173                 case ACTION:
1174                     if (command.word[1].type == NUMERIC)
1175                         command.part = transitive;
1176                     else
1177                         command.part = intransitive;
1178                     command.verb = command.word[0].id;
1179                     break;
1180                 case NUMERIC:
1181                     if (!settings.oldstyle) {
1182                         sspeak(DONT_KNOW, command.word[0].raw);
1183                         clear_command(&command);
1184                         continue;
1185                     }
1186                     break;// LCOV_EXCL_LINE
1187                 default: // LCOV_EXCL_LINE
1188                 case NO_WORD_TYPE: // LCOV_EXCL_LINE
1189                     BUG(VOCABULARY_TYPE_N_OVER_1000_NOT_BETWEEN_0_AND_3); // LCOV_EXCL_LINE
1190                 }
1191
1192                 switch (action(command)) {
1193                 case GO_TERMINATE:
1194                     command.state = EXECUTED;
1195                     break;
1196                 case GO_MOVE:
1197                     playermove(NUL);
1198                     command.state = EXECUTED;
1199                     break;
1200                 case GO_WORD2:
1201 #ifdef GDEBUG
1202                     printf("Word shift\n");
1203 #endif /* GDEBUG */
1204                     /* Get second word for analysis. */
1205                     command.word[0] = command.word[1];
1206                     command.word[1] = empty_command_word;
1207                     command.state = PREPROCESSED;
1208                     break;
1209                 case GO_UNKNOWN:
1210                     /*  Random intransitive verbs come here.  Clear obj just in case
1211                      *  (see attack()). */
1212                     command.word[0].raw[0] = toupper(command.word[0].raw[0]);
1213                     sspeak(DO_WHAT, command.word[0].raw);
1214                     command.obj = NO_OBJECT;
1215
1216                     /* object cleared; we need to go back to the preprocessing step */
1217                     command.state = GIVEN;
1218                     break;
1219                 case GO_CHECKHINT: // FIXME: re-name to be more contextual; this was previously a label
1220                     command.state = GIVEN;
1221                     break;
1222                 case GO_DWARFWAKE:
1223                     /*  Oh dear, he's disturbed the dwarves. */
1224                     rspeak(DWARVES_AWAKEN);
1225                     terminate(endgame);
1226                 case GO_CLEAROBJ: // FIXME: re-name to be more contextual; this was previously a label
1227                     clear_command(&command);
1228                     break;
1229                 case GO_TOP: // FIXME: re-name to be more contextual; this was previously a label
1230                     break;
1231                 default: // LCOV_EXCL_LINE
1232                     BUG(ACTION_RETURNED_PHASE_CODE_BEYOND_END_OF_SWITCH); // LCOV_EXCL_LINE
1233                 }
1234             } /* while command has not been fully processed */
1235         } /* while command is not yet given */
1236     } /* while command is not executed */
1237
1238     /* command completely executed; we return true. */
1239     return true;
1240 }
1241
1242 /*
1243  * MAIN PROGRAM
1244  *
1245  *  Adventure (rev 2: 20 treasures)
1246  *  History: Original idea & 5-treasure version (adventures) by Willie Crowther
1247  *           15-treasure version (adventure) by Don Woods, April-June 1977
1248  *           20-treasure version (rev 2) by Don Woods, August 1978
1249  *              Errata fixed: 78/12/25
1250  *           Revived 2017 as Open Adventure.
1251  */
1252
1253 int main(int argc, char *argv[])
1254 {
1255     int ch;
1256
1257     /*  Options. */
1258
1259 #if defined ADVENT_AUTOSAVE
1260     const char* opts = "dl:oa:";
1261     const char* usage = "Usage: %s [-l logfilename] [-o] [-a filename] [script...]\n";
1262     FILE *rfp = NULL;
1263     const char* autosave_filename = NULL;
1264 #elif !defined ADVENT_NOSAVE
1265     const char* opts = "dl:or:";
1266     const char* usage = "Usage: %s [-l logfilename] [-o] [-r restorefilename] [script...]\n";
1267     FILE *rfp = NULL;
1268 #else
1269     const char* opts = "dl:o";
1270     const char* usage = "Usage: %s [-l logfilename] [-o] [script...]\n";
1271 #endif
1272     while ((ch = getopt(argc, argv, opts)) != EOF) {
1273         switch (ch) {
1274         case 'd': // LCOV_EXCL_LINE
1275             settings.debug +=1; // LCOV_EXCL_LINE
1276             break; // LCOV_EXCL_LINE
1277         case 'l':
1278             settings.logfp = fopen(optarg, "w");
1279             if (settings.logfp == NULL)
1280                 fprintf(stderr,
1281                         "advent: can't open logfile %s for write\n",
1282                         optarg);
1283             signal(SIGINT, sig_handler);
1284             break;
1285         case 'o':
1286             settings.oldstyle = true;
1287             settings.prompt = false;
1288             break;
1289 #ifdef ADVENT_AUTOSAVE
1290         case 'a':
1291             rfp = fopen(optarg, READ_MODE);
1292             autosave_filename = optarg;
1293             signal(SIGHUP, sig_handler);
1294             signal(SIGTERM, sig_handler);
1295             break;
1296 #elif !defined ADVENT_NOSAVE
1297         case 'r':
1298             rfp = fopen(optarg, "r");
1299             if (rfp == NULL)
1300                 fprintf(stderr,
1301                         "advent: can't open save file %s for read\n",
1302                         optarg);
1303             break;
1304 #endif
1305         default:
1306             fprintf(stderr,
1307                     usage, argv[0]);
1308             fprintf(stderr,
1309                     "        -l create a log file of your game named as specified'\n");
1310             fprintf(stderr,
1311                     "        -o 'oldstyle' (no prompt, no command editing, displays 'Initialising...')\n");
1312 #if defined ADVENT_AUTOSAVE
1313             fprintf(stderr,
1314                     "        -a automatic save/restore from specified saved game file\n");
1315 #elif !defined ADVENT_NOSAVE
1316             fprintf(stderr,
1317                     "        -r restore from specified saved game file\n");
1318 #endif
1319             exit(EXIT_FAILURE);
1320             break;
1321         }
1322     }
1323
1324     /* copy invocation line part after switches */
1325     settings.argc = argc - optind;
1326     settings.argv = argv + optind;
1327     settings.optind = 0;
1328
1329     /*  Initialize game variables */
1330     int seedval = initialise();
1331
1332 #if !defined ADVENT_NOSAVE
1333     if (!rfp) {
1334         game.novice = yes_or_no(arbitrary_messages[WELCOME_YOU], arbitrary_messages[CAVE_NEARBY], arbitrary_messages[NO_MESSAGE]);
1335         if (game.novice)
1336             game.limit = NOVICELIMIT;
1337     } else {
1338         restore(rfp);
1339 #if defined ADVENT_AUTOSAVE
1340         score(scoregame);
1341 #endif
1342     }
1343 #if defined ADVENT_AUTOSAVE
1344     if (autosave_filename != NULL) {
1345         if ((autosave_fp = fopen(autosave_filename, WRITE_MODE)) == NULL) {
1346             perror(autosave_filename);
1347             return EXIT_FAILURE;
1348         }
1349         autosave();
1350     }
1351 #endif
1352 #else
1353     game.novice = yes_or_no(arbitrary_messages[WELCOME_YOU], arbitrary_messages[CAVE_NEARBY], arbitrary_messages[NO_MESSAGE]);
1354     if (game.novice)
1355         game.limit = NOVICELIMIT;
1356 #endif
1357
1358     if (settings.logfp)
1359         fprintf(settings.logfp, "seed %d\n", seedval);
1360
1361     /* interpret commands until EOF or interrupt */
1362     for (;;) {
1363         // if we're supposed to move, move
1364         if (!do_move())
1365             continue;
1366
1367         // get command
1368         if (!do_command())
1369             break;
1370     }
1371     /* show score and exit */
1372     terminate(quitgame);
1373 }
1374
1375 /* end */