Save/resume fail test coverage -- test works in Docker now
[open-adventure.git] / main.c
diff --git a/main.c b/main.c
index 0dca0d845e23df7c07a2982b183b0db864eadc20..7a179212619ba34b306182894dde4205a0032cb9 100644 (file)
--- a/main.c
+++ b/main.c
@@ -30,17 +30,17 @@ struct game_t game;
 long LNLENG, LNPOSN, PARMS[MAXPARMS + 1];
 char rawbuf[LINESIZE], INLINE[LINESIZE + 1];
 
 long LNLENG, LNPOSN, PARMS[MAXPARMS + 1];
 char rawbuf[LINESIZE], INLINE[LINESIZE + 1];
 
-long AMBER, AXE, BACK, BATTER, BEAR, BIRD, BLOOD,
+long AMBER, AXE, BACK, BATTERY, BEAR, BIRD, BLOOD,
      BOTTLE, CAGE, CAVE, CAVITY, CHAIN, CHASM, CHEST,
      CLAM, COINS, DOOR, DPRSSN, DRAGON, DWARF, EGGS,
      BOTTLE, CAGE, CAVE, CAVITY, CHAIN, CHASM, CHEST,
      CLAM, COINS, DOOR, DPRSSN, DRAGON, DWARF, EGGS,
-     EMRALD, ENTER, ENTRNC, FIND, FISSUR, FOOD,
+     EMERALD, ENTER, ENTRNC, FIND, FISSURE, FOOD,
      GRATE, HINT, INVENT, JADE, KEYS,
      GRATE, HINT, INVENT, JADE, KEYS,
-     KNIFE, LAMP, LOCK, LOOK, MAGZIN,
+     KNIFE, LAMP, LOCK, LOOK, MAGAZINE,
      MESSAG, MIRROR, NUGGET, NUL, OGRE, OIL, OYSTER,
      MESSAG, MIRROR, NUGGET, NUL, OGRE, OIL, OYSTER,
-     PEARL, PILLOW, PLANT, PLANT2, PYRAM, RESER, ROD, ROD2,
+     PEARL, PILLOW, PLANT, PLANT2, PYRAMID, RESER, ROD, ROD2,
      RUBY, RUG, SAPPH, SAY, SIGN, SNAKE,
      RUBY, RUG, SAPPH, SAY, SIGN, SNAKE,
-     STEPS, STREAM, THROW, TRIDNT, TROLL, TROLL2,
-     URN, VASE, VEND, VOLCAN, WATER;
+     STEPS, STREAM, THROW, TRIDENT, TROLL, TROLL2,
+     URN, VASE, VEND, VOLCANO, WATER;
 long WD1, WD1X, WD2, WD2X;
 
 FILE  *logfp = NULL, *rfp = NULL;
 long WD1, WD1X, WD2, WD2X;
 
 FILE  *logfp = NULL, *rfp = NULL;
@@ -77,7 +77,14 @@ int main(int argc, char *argv[])
 
     /*  Options. */
 
 
     /*  Options. */
 
-    while ((ch = getopt(argc, argv, "l:or:s")) != EOF) {
+#ifndef ADVENT_NOSAVE
+    char* opts = "l:or:s";
+    char* usage = "Usage: %s [-l logfilename] [-o] [-r restorefilename] [-s] \n";
+#else
+    char* opts = "l:os";
+    char* usage = "Usage: %s [-l logfilename] [-o] [-s] \n";
+#endif
+    while ((ch = getopt(argc, argv, opts)) != EOF) {
         switch (ch) {
         case 'l':
             logfp = fopen(optarg, "w");
         switch (ch) {
         case 'l':
             logfp = fopen(optarg, "w");
@@ -91,6 +98,7 @@ int main(int argc, char *argv[])
             oldstyle = true;
             editline = prompt = false;
             break;
             oldstyle = true;
             editline = prompt = false;
             break;
+#ifndef ADVENT_NOSAVE
         case 'r':
             rfp = fopen(optarg, "r");
             if (rfp == NULL)
         case 'r':
             rfp = fopen(optarg, "r");
             if (rfp == NULL)
@@ -99,18 +107,21 @@ int main(int argc, char *argv[])
                         optarg);
             signal(SIGINT, sig_handler);
             break;
                         optarg);
             signal(SIGINT, sig_handler);
             break;
+#endif
         case 's':
             editline = false;
             break;
         default:
             fprintf(stderr,
         case 's':
             editline = false;
             break;
         default:
             fprintf(stderr,
-                    "Usage: %s [-l logfilename] [-o] [-r restorefilename] [-s] \n", argv[0]);
+                    usage, argv[0]);
             fprintf(stderr,
                     "  where -l creates a log file of your game named as specified'\n");
             fprintf(stderr,
                     "        -o 'oldstyle' (no prompt, no command editing, displays 'Initialising...')\n");
             fprintf(stderr,
                     "  where -l creates a log file of your game named as specified'\n");
             fprintf(stderr,
                     "        -o 'oldstyle' (no prompt, no command editing, displays 'Initialising...')\n");
+#ifndef ADVENT_NOSAVE
             fprintf(stderr,
                     "        -r indicates restoring from specified saved game file\n");
             fprintf(stderr,
                     "        -r indicates restoring from specified saved game file\n");
+#endif
             fprintf(stderr,
                     "        -s indicates playing with command editing suppressed\n");
             exit(-1);
             fprintf(stderr,
                     "        -s indicates playing with command editing suppressed\n");
             exit(-1);
@@ -148,7 +159,7 @@ int main(int argc, char *argv[])
     game.loc = LOC_START;
     game.limit = 330;
     if (!rfp) {
     game.loc = LOC_START;
     game.limit = 330;
     if (!rfp) {
-        game.novice = YES(WELCOME_YOU, CAVE_NEARBY, NO_MESSAGE);
+        game.novice = YES(arbitrary_messages[WELCOME_YOU], arbitrary_messages[CAVE_NEARBY], arbitrary_messages[NO_MESSAGE]);
         if (game.novice)game.limit = 1000;
     } else {
         restore(rfp);
         if (game.novice)game.limit = 1000;
     } else {
         restore(rfp);
@@ -226,7 +237,7 @@ static void checkhints(void)
                     game.hintlc[hint] = 0;
                     return;
                 case 4:        /* dark */
                     game.hintlc[hint] = 0;
                     return;
                 case 4:        /* dark */
-                    if (game.prop[EMRALD] != -1 && game.prop[PYRAM] == -1)
+                    if (game.prop[EMERALD] != -1 && game.prop[PYRAMID] == -1)
                         break;
                     game.hintlc[hint] = 0;
                     return;
                         break;
                     game.hintlc[hint] = 0;
                     return;
@@ -264,11 +275,11 @@ static void checkhints(void)
 
                 /* Fall through to hint display */
                 game.hintlc[hint] = 0;
 
                 /* Fall through to hint display */
                 game.hintlc[hint] = 0;
-                if (!YES(HINTS[hint][3], NO_MESSAGE, OK_MAN))
+                if (!YES(arbitrary_messages[HINTS[hint][3]], arbitrary_messages[NO_MESSAGE], arbitrary_messages[OK_MAN]))
                     return;
                 SETPRM(1, HINTS[hint][2], HINTS[hint][2]);
                 RSPEAK(HINT_COST);
                     return;
                 SETPRM(1, HINTS[hint][2], HINTS[hint][2]);
                 RSPEAK(HINT_COST);
-                game.hinted[hint] = YES(WANT_HINT, HINTS[hint][4], OK_MAN);
+                game.hinted[hint] = YES(arbitrary_messages[WANT_HINT], arbitrary_messages[HINTS[hint][4]], arbitrary_messages[OK_MAN]);
                 if (game.hinted[hint] && game.limit > WARNTIME)
                     game.limit += WARNTIME * HINTS[hint][2];
             }
                 if (game.hinted[hint] && game.limit > WARNTIME)
                     game.limit += WARNTIME * HINTS[hint][2];
             }
@@ -294,7 +305,7 @@ static bool spotted_by_pirate(int i)
     for (int treasure = MINTRS; treasure <= MAXTRS; treasure++) {
         /*  Pirate won't take pyramid from plover room or dark
          *  room (too easy!). */
     for (int treasure = MINTRS; treasure <= MAXTRS; treasure++) {
         /*  Pirate won't take pyramid from plover room or dark
          *  room (too easy!). */
-        if (treasure == PYRAM && (game.loc == PLAC[PYRAM] || game.loc == PLAC[EMRALD])) {
+        if (treasure == PYRAMID && (game.loc == PLAC[PYRAMID] || game.loc == PLAC[EMERALD])) {
             continue;
         }
         if (TOTING(treasure) || HERE(treasure))
             continue;
         }
         if (TOTING(treasure) || HERE(treasure))
@@ -326,7 +337,7 @@ static bool spotted_by_pirate(int i)
     if (robplayer) {
         RSPEAK(PIRATE_POUNCES);
         for (int treasure = MINTRS; treasure <= MAXTRS; treasure++) {
     if (robplayer) {
         RSPEAK(PIRATE_POUNCES);
         for (int treasure = MINTRS; treasure <= MAXTRS; treasure++) {
-            if (!(treasure == PYRAM && (game.loc == PLAC[PYRAM] || game.loc == PLAC[EMRALD]))) {
+            if (!(treasure == PYRAMID && (game.loc == PLAC[PYRAMID] || game.loc == PLAC[EMERALD]))) {
                 if (AT(treasure) && game.fixed[treasure] == 0)
                     CARRY(treasure, game.loc);
                 if (TOTING(treasure))
                 if (AT(treasure) && game.fixed[treasure] == 0)
                     CARRY(treasure, game.loc);
                 if (TOTING(treasure))
@@ -451,7 +462,7 @@ static bool dwarfmove(void)
     if (game.dtotal == 0)
         return true;
     SETPRM(1, game.dtotal, 0);
     if (game.dtotal == 0)
         return true;
     SETPRM(1, game.dtotal, 0);
-    RSPEAK(DWARF_PACK + 1 / game.dtotal);      /* FIXME: Arithmetic on message number */
+    RSPEAK(game.dtotal == 1 ? DWARF_SINGLE : DWARF_PACK);
     if (attack == 0)
         return true;
     if (game.dflag == 2)game.dflag = 3;
     if (attack == 0)
         return true;
     if (game.dflag == 2)game.dflag = 3;
@@ -470,7 +481,7 @@ static bool dwarfmove(void)
 /*  "You're dead, Jim."
  *
  *  If the current loc is zero, it means the clown got himself killed.
 /*  "You're dead, Jim."
  *
  *  If the current loc is zero, it means the clown got himself killed.
- *  We'll allow this maxdie times.  MAXDIE is automatically set based
+ *  We'll allow this maxdie times.  maximum_deaths is automatically set based
  *  on the number of snide messages available.  Each death results in
  *  a message (81, 83, etc.)  which offers reincarnation; if accepted,
  *  this results in message 82, 84, etc.  The last time, if he wants
  *  on the number of snide messages available.  Each death results in
  *  a message (81, 83, etc.)  which offers reincarnation; if accepted,
  *  this results in message 82, 84, etc.  The last time, if he wants
@@ -490,15 +501,15 @@ static bool dwarfmove(void)
 static void croak(void)
 /*  Okay, he's dead.  Let's get on with it. */
 {
 static void croak(void)
 /*  Okay, he's dead.  Let's get on with it. */
 {
+    const char* query = obituaries[game.numdie].query;
+    const char* yes_response = obituaries[game.numdie].yes_response;
     ++game.numdie;
     if (game.closng) {
         /*  He died during closing time.  No resurrection.  Tally up a
          *  death and exit. */
         RSPEAK(DEATH_CLOSING);
         terminate(endgame);
     ++game.numdie;
     if (game.closng) {
         /*  He died during closing time.  No resurrection.  Tally up a
          *  death and exit. */
         RSPEAK(DEATH_CLOSING);
         terminate(endgame);
-    }
-    /* FIXME: Arithmetic on message numbers */
-    else if (game.numdie == MAXDIE || !YES(WATCH_IT + game.numdie * 2, WHICH_WAY + game.numdie * 2, OK_MAN))
+    } else if (game.numdie == maximum_deaths || !YES(query, yes_response, arbitrary_messages[OK_MAN]))
         terminate(endgame);
     else {
         game.place[WATER] = game.place[OIL] = NOWHERE;
         terminate(endgame);
     else {
         game.place[WATER] = game.place[OIL] = NOWHERE;
@@ -605,7 +616,7 @@ static bool playermove(token_t verb, int motion)
             if (motion == 29 || motion == 30)spk = BAD_DIRECTION;
             if (motion == 7 || motion == 36 || motion == 37)spk = UNSURE_FACING;
             if (motion == 11 || motion == 19)spk = NO_INOUT_HERE;
             if (motion == 29 || motion == 30)spk = BAD_DIRECTION;
             if (motion == 7 || motion == 36 || motion == 37)spk = UNSURE_FACING;
             if (motion == 11 || motion == 19)spk = NO_INOUT_HERE;
-            if (verb == FIND || verb == INVENT)spk = NEreplace;
+            if (verb == FIND || verb == INVENT)spk = NEARBY;
             if (motion == 62 || motion == 65)spk = NOTHING_HAPPENS;
             if (motion == 17)spk = WHICH_WAY;
             RSPEAK(spk);
             if (motion == 62 || motion == 65)spk = NOTHING_HAPPENS;
             if (motion == 17)spk = WHICH_WAY;
             RSPEAK(spk);
@@ -618,93 +629,104 @@ static bool playermove(token_t verb, int motion)
     do {
         /*
          * (ESR) This special-travel loop may have to be repeated if it includes
     do {
         /*
          * (ESR) This special-travel loop may have to be repeated if it includes
-         * the plover passage.  Same deal for any future cases wgerw we beed to
+         * the plover passage.  Same deal for any future cases where we need to
          * block travel and then redo it once the blocking condition has been
          * removed.
          */
          * block travel and then redo it once the blocking condition has been
          * removed.
          */
-        for (;;) {
-            game.newloc = scratchloc / 1000;
-            motion = MOD(game.newloc, 100);
-            if (!SPECIAL(game.newloc)) {
-                if (game.newloc <= 100) {
-                    if (game.newloc == 0 || PCT(game.newloc))
+        for (;;) { /* L12 loop */
+            for (;;) {
+                game.newloc = scratchloc / 1000;
+                motion = MOD(game.newloc, 100);
+                if (!SPECIAL(game.newloc)) {
+                    if (game.newloc <= 100) {
+                        if (game.newloc == 0 || PCT(game.newloc))
+                            break;
+                        /* else fall through */
+                    }
+                    if (TOTING(motion) || (game.newloc > 200 && AT(motion)))
                         break;
                     /* else fall through */
                         break;
                     /* else fall through */
-                }
-                if (TOTING(motion) || (game.newloc > 200 && AT(motion)))
+                } else if (game.prop[motion] != game.newloc / 100 - 3)
                     break;
                     break;
-                /* else fall through */
-            } else if (game.prop[motion] != game.newloc / 100 - 3)
-                break;
-L12:
-            do {
-                if (TRAVEL[kk] < 0)
-                    BUG(CONDITIONAL_TRAVEL_ENTRY_WITH_NO_ALTERATION);
-                ++kk;
-                game.newloc = labs(TRAVEL[kk]) / 1000;
-            } while
-            (game.newloc == scratchloc);
-            scratchloc = game.newloc;
-        }
+                do {
+                    if (TRAVEL[kk] < 0)
+                        BUG(CONDITIONAL_TRAVEL_ENTRY_WITH_NO_ALTERATION);
+                    ++kk;
+                    game.newloc = labs(TRAVEL[kk]) / 1000;
+                } while
+                (game.newloc == scratchloc);
+                scratchloc = game.newloc;
+            }
 
 
-        game.newloc = MOD(scratchloc, 1000);
-        if (!SPECIAL(game.newloc))
-            return true;
-        if (game.newloc <= 500) {
-            game.newloc = game.newloc - SPECIALBASE;
-            switch (game.newloc) {
-            case 1:
-                /*  Travel 301.  Plover-alcove passage.  Can carry only
-                 *  emerald.  Note: travel table must include "useless"
-                 *  entries going through passage, which can never be used for
-                 *  actual motion, but can be spotted by "go back". */
-                /* FIXME: Arithmetic on location numbers */
-                game.newloc = 99 + 100 - game.loc;
-                if (game.holdng > 1 || (game.holdng == 1 && !TOTING(EMRALD))) {
-                    game.newloc = game.loc;
-                    RSPEAK(MUST_DROP);
-                }
+            game.newloc = MOD(scratchloc, 1000);
+            if (!SPECIAL(game.newloc))
                 return true;
                 return true;
-            case 2:
-                /*  Travel 302.  Plover transport.  Drop the emerald (only use
-                 *  special travel if toting it), so he's forced to use the
-                 *  plover-passage to get it out.  Having dropped it, go back and
-                 *  pretend he wasn't carrying it after all. */
-                DROP(EMRALD, game.loc);
-                goto L12;
-            case 3:
-                /*  Travel 303.  Troll bridge.  Must be done only as special
-                 *  motion so that dwarves won't wander across and encounter
-                 *  the bear.  (They won't follow the player there because
-                 *  that region is forbidden to the pirate.)  If
-                 *  game.prop(TROLL)=1, he's crossed since paying, so step out
-                 *  and block him.  (standard travel entries check for
-                 *  game.prop(TROLL)=0.)  Special stuff for bear. */
-                if (game.prop[TROLL] == 1) {
-                    PSPEAK(TROLL, 1);
-                    game.prop[TROLL] = 0;
-                    MOVE(TROLL2, 0);
-                    MOVE(TROLL2 + NOBJECTS, 0);
-                    MOVE(TROLL, PLAC[TROLL]);
-                    MOVE(TROLL + NOBJECTS, FIXD[TROLL]);
-                    JUGGLE(CHASM);
-                    game.newloc = game.loc;
+            if (game.newloc <= 500) {
+                game.newloc -= SPECIALBASE;
+                switch (game.newloc) {
+                case 1:
+                    /*  Travel 301.  Plover-alcove passage.  Can carry only
+                     *  emerald.  Note: travel table must include "useless"
+                     *  entries going through passage, which can never be used for
+                     *  actual motion, but can be spotted by "go back". */
+                    /* FIXME: Arithmetic on location numbers */
+                    game.newloc = 99 + 100 - game.loc;
+                    if (game.holdng > 1 || (game.holdng == 1 && !TOTING(EMERALD))) {
+                        game.newloc = game.loc;
+                        RSPEAK(MUST_DROP);
+                    }
                     return true;
                     return true;
-                } else {
-                    game.newloc = PLAC[TROLL] + FIXD[TROLL] - game.loc;
-                    if (game.prop[TROLL] == 0)game.prop[TROLL] = 1;
-                    if (!TOTING(BEAR)) return true;
-                    RSPEAK(BRIDGE_COLLAPSE);
-                    game.prop[CHASM] = 1;
-                    game.prop[TROLL] = 2;
-                    DROP(BEAR, game.newloc);
-                    game.fixed[BEAR] = -1;
-                    game.prop[BEAR] = 3;
-                    game.oldlc2 = game.newloc;
-                    croak();
+                case 2:
+                    /*  Travel 302.  Plover transport.  Drop the emerald (only use
+                     *  special travel if toting it), so he's forced to use the
+                     *  plover-passage to get it out.  Having dropped it, go back and
+                     *  pretend he wasn't carrying it after all. */
+                    DROP(EMERALD, game.loc);
+                    do {
+                        if (TRAVEL[kk] < 0)
+                            BUG(CONDITIONAL_TRAVEL_ENTRY_WITH_NO_ALTERATION);
+                        ++kk;
+                        game.newloc = labs(TRAVEL[kk]) / 1000;
+                    } while
+                    (game.newloc == scratchloc);
+                    scratchloc = game.newloc;
+                    continue; /* goto L12 */
+                case 3:
+                    /*  Travel 303.  Troll bridge.  Must be done only as special
+                     *  motion so that dwarves won't wander across and encounter
+                     *  the bear.  (They won't follow the player there because
+                     *  that region is forbidden to the pirate.)  If
+                     *  game.prop(TROLL)=1, he's crossed since paying, so step out
+                     *  and block him.  (standard travel entries check for
+                     *  game.prop(TROLL)=0.)  Special stuff for bear. */
+                    if (game.prop[TROLL] == 1) {
+                        PSPEAK(TROLL, 1);
+                        game.prop[TROLL] = 0;
+                        MOVE(TROLL2, 0);
+                        MOVE(TROLL2 + NOBJECTS, 0);
+                        MOVE(TROLL, PLAC[TROLL]);
+                        MOVE(TROLL + NOBJECTS, FIXD[TROLL]);
+                        JUGGLE(CHASM);
+                        game.newloc = game.loc;
+                        return true;
+                    } else {
+                        game.newloc = PLAC[TROLL] + FIXD[TROLL] - game.loc;
+                        if (game.prop[TROLL] == 0)game.prop[TROLL] = 1;
+                        if (!TOTING(BEAR)) return true;
+                        RSPEAK(BRIDGE_COLLAPSE);
+                        game.prop[CHASM] = 1;
+                        game.prop[TROLL] = 2;
+                        DROP(BEAR, game.newloc);
+                        game.fixed[BEAR] = -1;
+                        game.prop[BEAR] = 3;
+                        game.oldlc2 = game.newloc;
+                        croak();
+                        return true;
+                    }
                 }
                 }
+                BUG(SPECIAL_TRAVEL_500_GT_L_GT_300_EXCEEDS_GOTO_LIST);
             }
             }
-            BUG(SPECIAL_TRAVEL_500_GT_L_GT_300_EXCEEDS_GOTO_LIST);
+            break; /* Leave L12 loop */
         }
     } while
     (false);
         }
     } while
     (false);
@@ -753,7 +775,7 @@ static bool closecheck(void)
      *  have been activated, since we've found chest. */
     if (game.clock1 == 0) {
         game.prop[GRATE] = 0;
      *  have been activated, since we've found chest. */
     if (game.clock1 == 0) {
         game.prop[GRATE] = 0;
-        game.prop[FISSUR] = 0;
+        game.prop[FISSURE] = 0;
         for (int i = 1; i <= NDWARVES; i++) {
             game.dseen[i] = false;
             game.dloc[i] = 0;
         for (int i = 1; i <= NDWARVES; i++) {
             game.dseen[i] = false;
             game.dloc[i] = 0;
@@ -840,12 +862,12 @@ static void lampcheck(void)
      *  Second is for other cases of lamp dying.  12400 is when it
      *  goes out.  Even then, he can explore outside for a while
      *  if desired. */
      *  Second is for other cases of lamp dying.  12400 is when it
      *  goes out.  Even then, he can explore outside for a while
      *  if desired. */
-    if (game.limit <= WARNTIME && HERE(BATTER) && game.prop[BATTER] == 0 && HERE(LAMP)) {
+    if (game.limit <= WARNTIME && HERE(BATTERY) && game.prop[BATTERY] == 0 && HERE(LAMP)) {
         RSPEAK(REPLACE_BATTERIES);
         RSPEAK(REPLACE_BATTERIES);
-        game.prop[BATTER] = 1;
-        if (TOTING(BATTER))
-            DROP(BATTER, game.loc);
-        game.limit = game.limit + 2500;
+        game.prop[BATTERY] = 1;
+        if (TOTING(BATTERY))
+            DROP(BATTERY, game.loc);
+        game.limit += BATTERYLIFE;
         game.lmwarn = false;
     } else if (game.limit == 0) {
         game.limit = -1;
         game.lmwarn = false;
     } else if (game.limit == 0) {
         game.limit = -1;
@@ -856,8 +878,8 @@ static void lampcheck(void)
         if (!game.lmwarn && HERE(LAMP)) {
             game.lmwarn = true;
             int spk = GET_BATTERIES;
         if (!game.lmwarn && HERE(LAMP)) {
             game.lmwarn = true;
             int spk = GET_BATTERIES;
-            if (game.place[BATTER] == NOWHERE)spk = LAMP_DIM;
-            if (game.prop[BATTER] == 1)spk = MISSING_BATTERIES;
+            if (game.place[BATTERY] == NOWHERE)spk = LAMP_DIM;
+            if (game.prop[BATTERY] == 1)spk = MISSING_BATTERYIES;
             RSPEAK(spk);
         }
     }
             RSPEAK(spk);
         }
     }
@@ -971,7 +993,8 @@ static bool do_command(FILE *cmdin)
             else
                 continue;      /* back to top of main interpreter loop */
         }
             else
                 continue;      /* back to top of main interpreter loop */
         }
-        if (game.loc == 33 && PCT(25) && !game.closng)RSPEAK(SAYS_PLUGH);
+        if (game.loc == LOC_Y2 && PCT(25) && !game.closng)
+            RSPEAK(SAYS_PLUGH);
 
         listobjects();
 
 
         listobjects();