LCOV - code coverage report
Current view: top level - open-adventure - main.c (source / functions) Hit Total Coverage
Test: advent.info Lines: 649 650 99.8 %
Date: 2018-03-12 03:52:32 Functions: 11 11 100.0 %

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

Generated by: LCOV version 1.13