Jettison MAKEWD(), GETTXT(), vocab(), GETIN(), and the old db compiler.
[open-adventure.git] / main.c
diff --git a/main.c b/main.c
index a8fbfc35c1239b6e4f4e563957189ce47e46e8ec..c5dc311d0e7cf7a6b4e32107ec482a3d57afdc4e 100644 (file)
--- a/main.c
+++ b/main.c
 #include <getopt.h>
 #include <signal.h>
 #include <time.h>
+#include <string.h>
 #include "advent.h"
-#include "database.h"
 #include "linenoise/linenoise.h"
 #include "newdb.h"
 
 #define DIM(a) (sizeof(a)/sizeof(a[0]))
 
-/* Abstract out the encoding of words in the travel array.  Gives us
- * some hope of getting to a less cryptic representation than we
- * inherited from FORTRAN, someday. To understand these, read the
- * encoding description for TRAVEL.
- */
-#define T_DESTINATION(entry)   MOD(labs(entry) / 1000, 1000)
-#define T_NODWARVES(entry)     labs(entry) / 1000000 == 100
-#define T_MOTION(entry)                MOD(labs(entry), 1000)
-#define L_SPEAK(loc)           ((loc) - 500)
-#define T_TERMINATE(entry)     (T_MOTION(entry) == 1)
-
 struct game_t game;
 
 long LNLENG, LNPOSN;
 char rawbuf[LINESIZE], INLINE[LINESIZE + 1];
 
-long AMBER, AXE, BACK, BATTERY, BEAR, BIRD, BLOOD,
-     BOTTLE, CAGE, CAVE, CAVITY, CHAIN, CHASM, CHEST,
-     CLAM, COINS, DOOR, DPRSSN, DRAGON, DWARF, EGGS,
-     EMERALD, ENTER, ENTRNC, FIND, FISSURE, FOOD,
-     GRATE, HINT, INVENT, JADE, KEYS,
-     KNIFE, LAMP, LOCK, LOOK, MAGAZINE,
-     MESSAG, MIRROR, NUGGET, NUL, OGRE, OIL, OYSTER,
-     PEARL, PILLOW, PLANT, PLANT2, PYRAMID, RESER, ROD, ROD2,
-     RUBY, RUG, SAPPH, SAY, SIGN, SNAKE,
-     STEPS, STREAM, THROW, TRIDENT, TROLL, TROLL2,
-     URN, VASE, VEND, VOLCANO, WATER;
-
 FILE  *logfp = NULL, *rfp = NULL;
 bool oldstyle = false;
 bool editline = true;
 bool prompt = true;
 
+// LCOV_EXCL_START
+// exclude from coverage analysis because it requires interactivity to test
 static void sig_handler(int signo)
 {
     if (signo == SIGINT) {
@@ -68,6 +47,7 @@ static void sig_handler(int signo)
     }
     exit(0);
 }
+// LCOV_EXCL_STOP
 
 /*
  * MAIN PROGRAM
@@ -81,7 +61,7 @@ static void sig_handler(int signo)
  *          Revived 2017 as Open Adventure.
  */
 
-static bool do_command(FILE *);
+static bool do_command(void);
 
 int main(int argc, char *argv[])
 {
@@ -156,7 +136,7 @@ int main(int argc, char *argv[])
     initialise();
 
     /*  Start-up, dwarf stuff */
-    game.zzword = rndvoc(3, 0);
+    make_zzword(game.zzword);
     game.newloc = LOC_START;
     game.loc = LOC_START;
     game.limit = GAMELIMIT;
@@ -173,7 +153,7 @@ int main(int argc, char *argv[])
 
     /* interpret commands until EOF or interrupt */
     for (;;) {
-        if (!do_command(stdin))
+        if (!do_command())
             break;
     }
     /* show score and exit */
@@ -190,7 +170,7 @@ static bool fallback_handler(char *buf)
         // autogenerated, so don't charge user time for it.
         --game.turns;
         // here we reconfigure any global game state that uses random numbers
-        game.zzword = rndvoc(3, 0);
+       make_zzword(game.zzword);
         return true;
     }
     return false;
@@ -270,7 +250,7 @@ static void checkhints(void)
                     game.hintlc[hint] = 0;
                     return;
                 default:
-                    BUG(HINT_NUMBER_EXCEEDS_GOTO_LIST);
+                    BUG(HINT_NUMBER_EXCEEDS_GOTO_LIST); // LCOV_EXCL_LINE
                     break;
                 }
 
@@ -303,11 +283,11 @@ static bool spotted_by_pirate(int i)
     int snarfed = 0;
     bool movechest = false, robplayer = false;
     for (int treasure = 1; treasure <= NOBJECTS; treasure++) {
-       if (!object_descriptions[treasure].is_treasure)
+       if (!objects[treasure].is_treasure)
            continue;
         /*  Pirate won't take pyramid from plover room or dark
          *  room (too easy!). */
-        if (treasure == PYRAMID && (game.loc == object_descriptions[PYRAMID].plac || game.loc == object_descriptions[EMERALD].plac)) {
+        if (treasure == PYRAMID && (game.loc == objects[PYRAMID].plac || game.loc == objects[EMERALD].plac)) {
             continue;
         }
         if (TOTING(treasure) || HERE(treasure))
@@ -339,9 +319,9 @@ static bool spotted_by_pirate(int i)
     if (robplayer) {
         rspeak(PIRATE_POUNCES);
        for (int treasure = 1; treasure <= NOBJECTS; treasure++) {
-           if (!object_descriptions[treasure].is_treasure)
+           if (!objects[treasure].is_treasure)
                continue;
-            if (!(treasure == PYRAMID && (game.loc == object_descriptions[PYRAMID].plac || game.loc == object_descriptions[EMERALD].plac))) {
+            if (!(treasure == PYRAMID && (game.loc == objects[PYRAMID].plac || game.loc == objects[EMERALD].plac))) {
                 if (AT(treasure) && game.fixed[treasure] == 0)
                     carry(treasure, game.loc);
                 if (TOTING(treasure))
@@ -417,11 +397,11 @@ static bool dwarfmove(void)
         if (game.dloc[i] == 0)
             continue;
         /*  Fill tk array with all the places this dwarf might go. */
-        int j = 1;
-        kk = TKEY[game.dloc[i]];
+        unsigned int j = 1;
+        kk = tkey[game.dloc[i]];
         if (kk != 0)
             do {
-               game.newloc = T_DESTINATION(TRAVEL[kk]);
+               game.newloc = T_DESTINATION(travel[kk]);
                 /* Have we avoided a dwarf encounter? */
                 bool avoided = (SPECIAL(game.newloc) ||
                                 !INDEEP(game.newloc) ||
@@ -431,13 +411,13 @@ static bool dwarfmove(void)
                                 game.newloc == game.dloc[i] ||
                                 FORCED(game.newloc) ||
                                 (i == PIRATE && CNDBIT(game.newloc, COND_NOARRR)) ||
-                                T_NODWARVES(TRAVEL[kk]));
+                                T_NODWARVES(travel[kk]));
                 if (!avoided) {
                     tk[j++] = game.newloc;
                 }
                 ++kk;
             } while
-            (TRAVEL[kk - 1] >= 0);
+               (!travel[kk - 1].stop);
         tk[j] = game.odloc[i];
         if (j >= 2)
             --j;
@@ -541,10 +521,10 @@ static void croak(void)
 
 static bool playermove(token_t verb, int motion)
 {
-    int scratchloc, k2, kk = TKEY[game.loc];
+    int scratchloc, k2, kk = tkey[game.loc];
     game.newloc = game.loc;
     if (kk == 0)
-        BUG(LOCATION_HAS_NO_TRAVEL_ENTRIES);
+        BUG(LOCATION_HAS_NO_TRAVEL_ENTRIES); // LCOV_EXCL_LINE
     if (motion == NUL)
         return true;
     else if (motion == BACK) {
@@ -561,13 +541,13 @@ static bool playermove(token_t verb, int motion)
         if (CNDBIT(game.loc, COND_NOBACK))k2 = TWIST_TURN;
         if (k2 == 0) {
             for (;;) {
-                scratchloc = T_DESTINATION(TRAVEL[kk]);
+                scratchloc = T_DESTINATION(travel[kk]);
                 if (scratchloc != motion) {
                     if (!SPECIAL(scratchloc)) {
-                        if (FORCED(scratchloc) && T_DESTINATION(TRAVEL[TKEY[scratchloc]]) == motion)
+                        if (FORCED(scratchloc) && T_DESTINATION(travel[tkey[scratchloc]]) == motion)
                             k2 = kk;
                     }
-                    if (TRAVEL[kk] >= 0) {
+                    if (!travel[kk].stop) {
                         ++kk;  /* go to next travel entry for this location */
                         continue;
                     }
@@ -579,8 +559,8 @@ static bool playermove(token_t verb, int motion)
                     }
                 }
 
-                motion = T_MOTION(TRAVEL[kk]);
-                kk = TKEY[game.loc];
+                motion = travel[kk].motion;
+                kk = tkey[game.loc];
                 break; /* fall through to ordinary travel */
             }
         } else {
@@ -607,12 +587,12 @@ static bool playermove(token_t verb, int motion)
         game.oldloc = game.loc;
     }
 
-    /* Look for a way to fulfil the motion - kk indexes the beginning
-     * of the motion entries for here (game.loc). */
+    /* Look for a way to fulfil the motion verb passed in - kk indexes
+     * the beginning of the motion entries for here (game.loc). */
     for (;;) {
-        if (T_TERMINATE(TRAVEL[kk]) || T_MOTION(TRAVEL[kk]) == motion)
+        if (T_TERMINATE(travel[kk]) || travel[kk].motion == motion)
             break;
-        if (TRAVEL[kk] < 0) {
+        if (travel[kk].stop) {
             /* FIXME: Magic numbers! */
             /*  Couldn't find an entry matching the motion word passed
              *  in.  Various messages depending on word given. */
@@ -621,7 +601,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 (verb == FIND || verb == INVENT)spk = NEARBY;
+            if (verb == FIND || verb == INVENTORY)spk = NEARBY;
             if (motion == 62 || motion == 65)spk = NOTHING_HAPPENS;
             if (motion == 17)spk = WHICH_WAY;
             rspeak(spk);
@@ -629,51 +609,59 @@ static bool playermove(token_t verb, int motion)
         }
         ++kk;
     }
-    scratchloc = labs(TRAVEL[kk]) / 1000;
 
+    /* (ESR) We've found a destination that goes with the motion verb.
+     * Next we need to check any conditional(s) on this destination, and
+     * possibly on following entries. */
     do {
-        /*
-         * (ESR) This special-travel loop may have to be repeated if it includes
-         * 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.
-         */
         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))
+                long cond = T_CONDITION(travel[kk]);
+                long arg = MOD(cond, 100);
+                if (!SPECIAL(cond)) {
+                   /* YAML N and [pct N] conditionals */
+                    if (cond <= 100) {
+                        if (cond == 0 || PCT(cond))
                             break;
                         /* else fall through */
                     }
-                    if (TOTING(motion) || (game.newloc > 200 && AT(motion)))
+                   /* YAML [with OBJ] clause */
+                    if (TOTING(arg) || (cond > 200 && AT(arg)))
                         break;
-                    /* else fall through */
-                } else if (game.prop[motion] != game.newloc / 100 - 3)
+                    /* else fall through to check [not OBJ STATE] */
+                } else if (game.prop[arg] != cond / 100 - 3)
                     break;
-                do {
-                    if (TRAVEL[kk] < 0)
-                        BUG(CONDITIONAL_TRAVEL_ENTRY_WITH_NO_ALTERATION);
-                    ++kk;
-                    game.newloc = labs(TRAVEL[kk]) / 1000;
+
+               /* We arrive here on conditional failure.
+                * Skip to next non-matching destination */
+               long k2 = kk;
+               do {
+                    if (travel[k2].stop)
+                        BUG(CONDITIONAL_TRAVEL_ENTRY_WITH_NO_ALTERATION); // LCOV_EXCL_LINE
+                    ++k2;
                 } while
-                (game.newloc == scratchloc);
-                scratchloc = game.newloc;
+                   (T_HIGH(travel[kk]) == T_HIGH(travel[k2]));
+               kk = k2;
             }
 
-            game.newloc = MOD(scratchloc, 1000);
+           /* Found an eligible rule, now execute it */
+            game.newloc = T_DESTINATION(travel[kk]);
             if (!SPECIAL(game.newloc))
                 return true;
-            if (game.newloc <= 500) {
-                game.newloc -= SPECIALBASE;
+
+           if (game.newloc > 500) {
+               /* Execute a speak rule */
+               rspeak(L_SPEAK(game.newloc));
+               game.newloc = game.loc;
+               return true;
+           } else {
+               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". */
+                    /* 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))) {
@@ -682,40 +670,43 @@ static bool playermove(token_t verb, int motion)
                     }
                     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. */
+                    /* 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);
+                   k2 = kk;
                     do {
-                        if (TRAVEL[kk] < 0)
-                            BUG(CONDITIONAL_TRAVEL_ENTRY_WITH_NO_ALTERATION);
-                        ++kk;
-                        game.newloc = labs(TRAVEL[kk]) / 1000;
+                        if (travel[k2].stop)
+                            BUG(CONDITIONAL_TRAVEL_ENTRY_WITH_NO_ALTERATION); // LCOV_EXCL_LINE
+                        ++k2;
                     } while
-                    (game.newloc == scratchloc);
-                    scratchloc = game.newloc;
+                       (T_HIGH(travel[kk]) == T_HIGH(travel[k2]));
+                    kk = k2;
                     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. */
+                    /* 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,look, 1);
                         game.prop[TROLL] = 0;
                         move(TROLL2, 0);
                         move(TROLL2 + NOBJECTS, 0);
-                        move(TROLL, object_descriptions[TROLL].plac);
-                        move(TROLL + NOBJECTS, object_descriptions[TROLL].fixd);
+                        move(TROLL, objects[TROLL].plac);
+                        move(TROLL + NOBJECTS, objects[TROLL].fixd);
                         juggle(CHASM);
                         game.newloc = game.loc;
                         return true;
                     } else {
-                        game.newloc = object_descriptions[TROLL].plac + object_descriptions[TROLL].fixd - game.loc;
+                        game.newloc = objects[TROLL].plac + objects[TROLL].fixd - game.loc;
                         if (game.prop[TROLL] == 0)game.prop[TROLL] = 1;
                         if (!TOTING(BEAR)) return true;
                         rspeak(BRIDGE_COLLAPSE);
@@ -723,23 +714,19 @@ static bool playermove(token_t verb, int motion)
                         game.prop[TROLL] = 2;
                         drop(BEAR, game.newloc);
                         game.fixed[BEAR] = -1;
-                        game.prop[BEAR] = 3;
+                        game.prop[BEAR] = BEAR_DEAD;
                         game.oldlc2 = game.newloc;
                         croak();
                         return true;
                     }
+               default:
+                   BUG(SPECIAL_TRAVEL_500_GT_L_GT_300_EXCEEDS_GOTO_LIST); // LCOV_EXCL_LINE
                 }
-                BUG(SPECIAL_TRAVEL_500_GT_L_GT_300_EXCEEDS_GOTO_LIST);
             }
             break; /* Leave L12 loop */
         }
     } while
     (false);
-    
-    /* Execute a speak rule */
-    rspeak(L_SPEAK(game.newloc));
-    game.newloc = game.loc;
-    return true;
 }
 
 static bool closecheck(void)
@@ -753,15 +740,14 @@ static bool closecheck(void)
  *  to get out.  If he doesn't within clock2 turns, we close the cave;
  *  if he does try, we assume he panics, and give him a few additional
  *  turns to get frantic before we close.  When clock2 hits zero, we
- *  branch to 11000 to transport him into the final puzzle.  Note that
- *  the puzzle depends upon all sorts of random things.  For instance,
- *  there must be no water or oil, since there are beanstalks which we
- *  don't want to be able to water, since the code can't handle it.
- *  Also, we can have no keys, since there is a grate (having moved
- *  the fixed object!) there separating him from all the treasures.
- *  Most of these problems arise from the use of negative prop numbers
- *  to suppress the object descriptions until he's actually moved the
- *  objects. */
+ *  transport him into the final puzzle.  Note that the puzzle depends
+ *  upon all sorts of random things.  For instance, there must be no
+ *  water or oil, since there are beanstalks which we don't want to be
+ *  able to water, since the code can't handle it.  Also, we can have
+ *  no keys, since there is a grate (having moved the fixed object!)
+ *  there separating him from all the treasures.  Most of these
+ *  problems arise from the use of negative prop numbers to suppress
+ *  the object descriptions until he's actually moved the objects. */
 {
     if (game.tally == 0 && INDEEP(game.loc) && game.loc != LOC_Y2)
         --game.clock1;
@@ -788,10 +774,11 @@ static bool closecheck(void)
         }
         move(TROLL, 0);
         move(TROLL + NOBJECTS, 0);
-        move(TROLL2, object_descriptions[TROLL].plac);
-        move(TROLL2 + NOBJECTS, object_descriptions[TROLL].fixd);
+        move(TROLL2, objects[TROLL].plac);
+        move(TROLL2 + NOBJECTS, objects[TROLL].fixd);
         juggle(CHASM);
-        if (game.prop[BEAR] != 3)DESTROY(BEAR);
+        if (game.prop[BEAR] != BEAR_DEAD)
+           DESTROY(BEAR);
         game.prop[CHAIN] = 0;
         game.fixed[CHAIN] = 0;
         game.prop[AXE] = 0;
@@ -936,7 +923,7 @@ static void listobjects(void)
     }
 }
 
-static bool do_command(FILE *cmdin)
+static bool do_command()
 /* Get and execute a command */
 {
     long V1, V2;
@@ -1029,8 +1016,25 @@ L2600:
             game.knfloc = 0;
 
         /* This is where we get a new command from the user */
-        if (!GETIN(cmdin, &command.wd1, &command.wd1x, &command.wd2, &command.wd2x))
-            return false;
+       char* input;
+       for (;;) {
+         input = get_input();
+         if (input == NULL)
+           return(false);
+         if (word_count(input) > 2)
+         {
+           rspeak(TWO_WORDS);
+           continue;
+         }
+         if (strcmp(input, "") != 0)
+           break;
+       }
+       long tokens[4];
+       tokenize(input, tokens);
+       command.wd1 = tokens[0];
+        command.wd1x = tokens[1];
+        command.wd2 = tokens[2];
+        command.wd2x = tokens[3];
 
         /*  Every input, check "game.foobar" flag.  If zero, nothing's
          *  going on.  If pos, make neg.  If neg, he skipped a word,
@@ -1062,8 +1066,12 @@ L2607:
         } else
             lampcheck();
 
-        V1 = vocab(command.wd1, -1);
-        V2 = vocab(command.wd2, -1);
+       char word1[6];
+       char word2[6];
+       packed_to_token(command.wd1, word1);
+       packed_to_token(command.wd2, word2);
+       V1 = get_vocab_id(word1);
+       V2 = get_vocab_id(word2);
         if (V1 == ENTER && (V2 == STREAM || V2 == 1000 + WATER)) {
             if (LIQLOC(game.loc) == WATER) {
                 rspeak(FEET_WET);
@@ -1081,26 +1089,27 @@ L2607:
             if (!((V1 != 1000 + WATER && V1 != 1000 + OIL) ||
                   (V2 != 1000 + PLANT && V2 != 1000 + DOOR))) {
                 if (AT(V2 - 1000))
-                    command.wd2 = MAKEWD(WORD_POUR);
+                   command.wd2 = token_to_packed("POUR");
             }
             if (V1 == 1000 + CAGE && V2 == 1000 + BIRD && HERE(CAGE) && HERE(BIRD))
-                command.wd1 = MAKEWD(WORD_CATCH);
+               command.wd1 = token_to_packed("CATCH");
         }
 L2620:
-        if (wordeq(command.wd1, MAKEWD(WORD_WEST))) {
+        if (wordeq(command.wd1, token_to_packed("WEST"))) {
             ++game.iwest;
             if (game.iwest == 10)
                 rspeak(W_IS_WEST);
         }
-        if (wordeq(command.wd1, MAKEWD(WORD_GO)) && !wordempty(command.wd2)) {
+        if (wordeq(command.wd1, token_to_packed("GO")) && !wordempty(command.wd2)) {
             if (++igo == 10)
                 rspeak(GO_UNNEEDED);
         }
 Lookup:
-        defn = vocab(command.wd1, -1);
+       packed_to_token(command.wd1, word1);
+       defn = get_vocab_id(word1);
         if (defn == -1) {
             /* Gee, I don't understand. */
-            if (fallback_handler(rawbuf))
+            if (fallback_handler(input))
                 continue;
             rspeak(DONT_KNOW, command.wd1, command.wd1x);
             goto L2600;
@@ -1121,14 +1130,14 @@ Lookup:
             command.verb = kmod;
             break;
         case 3:
-            rspeak(kmod);
+            rspeak(specials[kmod].message);
             goto L2012;
         default:
-            BUG(VOCABULARY_TYPE_N_OVER_1000_NOT_BETWEEN_0_AND_3);
+            BUG(VOCABULARY_TYPE_N_OVER_1000_NOT_BETWEEN_0_AND_3); // LCOV_EXCL_LINE
         }
 
 Laction:
-        switch (action(cmdin, &command)) {
+        switch (action(&command)) {
         case GO_TERMINATE:
             return true;
         case GO_MOVE:
@@ -1161,8 +1170,9 @@ Laction:
             rspeak(DWARVES_AWAKEN);
             terminate(endgame);
         default:
-            BUG(ACTION_RETURNED_PHASE_CODE_BEYOND_END_OF_SWITCH);
+            BUG(ACTION_RETURNED_PHASE_CODE_BEYOND_END_OF_SWITCH); // LCOV_EXCL_LINE
         }
+       linenoiseFree(input);
     }
 }