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