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