Chase more booleans and enums.
[super-star-trek.git] / src / sst.c
index 3ec832c719017269199db55ee48f624d4358ff83..d19698491d71a64611cf7b7a0fd6a99591a9d6cd 100644 (file)
--- a/src/sst.c
+++ b/src/sst.c
@@ -152,17 +152,42 @@ for a lot of magic numbers and refactored the heck out of it.
 
    4. Added game option selection so you can play a close (but not bug-for-
       bug identical) approximation of older versions.
+
+   5. Half the quadrants now have inhabited planets, from which one 
+      cannot mine dilithium (there will still be the same additional number
+      of dilithium-bearing planets).  Torpedoing an inhabited world is *bad*.
+      There is BSD-Trek-like logic for Klingons to attack and enslave 
+      inhabited worlds, producing more ships (only is skill is 'good' or 
+      better). (Controlled by OPTION_WORLDS and turned off if game 
+      type is "plain" or "almy".)
+
+   6. User input is now logged so we can do regression testing.
+
+   7. More BSD-Trek features: You can now lose if your entire crew
+      dies in battle.  When abandoning ship in a game with inhabited
+      worlds enabled, they must have one in the quadrant to beam down
+      to; otherwise they die in space and this counts heavily against
+      your score.  Docking at a starbase replenishes your crew.
+
+   8. Still more BSD-Trek: we now have a weighted damage table.
+      Also, the nav subsystem (enabling automatic course
+      setting) can be damaged separately from the main computer (which
+      handles weapons targeting, ETA calculation, and self-destruct).
 */
 
 /* the input queue */
 static char line[128], *linep = line;
 
 struct game game;
-int thingx, thingy, iqhere, iqengry;
-int iscore, iskill; // Common PLAQ
+coord thing;
+bool iqhere, iqengry;
+int iscore, iskill;    // Common PLAQ
 double aaitem;
 double perdate;
 char citem[10];
+int seed;              // the random-number seed
+bool idebug;           // debug mode
+FILE *logfp, *replayfp;
 
 char *device[NDEVICES] = {
        "S. R. Sensors",
@@ -176,6 +201,7 @@ char *device[NDEVICES] = {
        "Subspace Radio",
        "Shuttle Craft",
        "Computer",
+       "Navigation System",
        "Transporter",
        "Shield Control",
        "Death Ray",
@@ -202,73 +228,77 @@ commands[] = {
 #define TORPEDO        5
         {"TORPEDO",    TORPEDO,        0},
        {"PHOTONS",     TORPEDO,        0},
-#define MOVE   6
+#define MOVE   7
        {"MOVE",        MOVE,           0},
-#define SHIELDS        7
+#define SHIELDS        8
        {"SHIELDS",     SHIELDS,        0},
-#define DOCK   8
+#define DOCK   9
        {"DOCK",        DOCK,           0},
-#define DAMAGES        9
+#define DAMAGES        10
        {"DAMAGES",     DAMAGES,        0},
-#define CHART  10
+#define CHART  11
        {"CHART",       CHART,          0},
-#define IMPULSE        11
+#define IMPULSE        12
        {"IMPULSE",     IMPULSE,        0},
-#define REST   12
+#define REST   13
        {"REST",        REST,           0},
-#define WARP   13
+#define WARP   14
        {"WARP",        WARP,           0},
-#define SCORE  14
+#define SCORE  15
        {"SCORE",       SCORE,          0},
-#define SENSORS        15
+#define SENSORS        16
        {"SENSORS",     SENSORS,        OPTION_PLANETS},
-#define ORBIT  16
+#define ORBIT  17
        {"ORBIT",       ORBIT,          OPTION_PLANETS},
-#define TRANSPORT      17
+#define TRANSPORT      18
        {"TRANSPORT",   TRANSPORT,      OPTION_PLANETS},
-#define MINE   18
+#define MINE   19
        {"MINE",        MINE,           OPTION_PLANETS},
-#define CRYSTALS       19
+#define CRYSTALS       20
        {"CRYSTALS",    CRYSTALS,       OPTION_PLANETS},
-#define SHUTTLE        20
+#define SHUTTLE        21
        {"SHUTTLE",     SHUTTLE,        OPTION_PLANETS},
-#define PLANETS        21
+#define PLANETS        22
        {"PLANETS",     PLANETS,        OPTION_PLANETS},
-#define REPORT 22
+#define REPORT 23
        {"REPORT",      REPORT,         0},
-#define COMPUTER       23
+#define COMPUTER       24
        {"COMPUTER",    COMPUTER,       0},
-#define COMMANDS       24
+#define COMMANDS       25
        {"COMMANDS",    COMMANDS,       0},
-#define EMEXIT 25
+#define EMEXIT 26
        {"EMEXIT",      EMEXIT,         0},
-#define PROBE  26
+#define PROBE  27
        {"PROBE",       PROBE,          OPTION_PROBE},
-#define SAVE   27
+#define SAVE   28
        {"SAVE",        SAVE,           0},
        {"FREEZE",      SAVE,           0},
-#define ABANDON        28
+#define ABANDON        30
        {"ABANDON",     ABANDON,        0},
-#define DESTRUCT       29
+#define DESTRUCT       31
        {"DESTRUCT",    DESTRUCT,       0},
-#define DEATHRAY       30
+#define DEATHRAY       32
        {"DEATHRAY",    DEATHRAY,       0},
-#define DEBUGCMD       31
+#define DEBUGCMD       33
        {"DEBUG",       DEBUGCMD,       0},
-#define MAYDAY 32
+#define MAYDAY 34
        {"MAYDAY",      MAYDAY,         0},
        //{"SOS",               MAYDAY,         0},
        //{"CALL",      MAYDAY,         0},
-#define QUIT   33
+#define QUIT   35
        {"QUIT",        QUIT,           0},
-#define HELP   34
+#define HELP   36
        {"HELP",        HELP,           0},
+#define SEED   37
+       {"SEED",        SEED,           0},
 };
 
 #define NUMCOMMANDS    sizeof(commands)/sizeof(commands[0])
 #define ACCEPT(i)      (!commands[i].option || (commands[i].option & game.options))
 
-static void listCommands(void) {
+static void listCommands(void) 
+/* generate a list of legal commands */
+{
     int i, k = 0;
     proutn("LEGAL COMMANDS ARE:");
     for (i = 0; i < NUMCOMMANDS; i++) {
@@ -282,7 +312,8 @@ static void listCommands(void) {
     skip(1);
 }
 
-static void helpme(void) 
+static void helpme(void)
+/* browse on-line help */
 {
     int i, j;
     char cmdbuf[32], *cp;
@@ -291,7 +322,7 @@ static void helpme(void)
     /* Give help on commands */
     int key;
     key = scan();
-    while (TRUE) {
+    for(;;) {
        if (key == IHEOL) {
            setwnd(prompt_window);
            proutn("Help on what command? ");
@@ -361,21 +392,24 @@ static void helpme(void)
     fclose(fp);
 }
 
-void enqueue(char *s) 
+void enqueue(char *s)
+/* enqueue input for the command parser */
 {
     strcpy(line, s);
 }
 
-static void makemoves(void) 
+static void makemoves(void)
+/* command-interpretation loop */
 {
-    int i, v = 0, hitme;
+    int key, i, v = 0;
+    bool hitme;
     clrscr();
     setwnd(message_window);
-    while (TRUE) { /* command loop */
+    for(;;) { /* command loop */
        drawmaps(1);
-       while (TRUE)  { /* get a command */
-           hitme = FALSE;
-           game.justin = 0;
+       for(;;)  { /* get a command */
+           hitme = false;
+           game.justin = false;
            game.optime = 0.0;
            i = -1;
            chew();
@@ -386,7 +420,7 @@ static void makemoves(void)
                makechart();
                continue;
            }
-           game.ididit=0;
+           game.ididit = false;
            clrscr();
            setwnd(message_window);
            clrscr();
@@ -406,7 +440,7 @@ static void makemoves(void)
                break;
            listCommands();
        }
-       commandhook(commands[i].name, TRUE);
+       commandhook(commands[i].name, true);
        switch (v) { /* command switch */
        case SRSCAN:                 // srscan
            srscan(SCAN_FULL);
@@ -422,20 +456,20 @@ static void makemoves(void)
            break;
        case PHASERS:                   // phasers
            phasers();
-           if (game.ididit) hitme = TRUE;
+           if (game.ididit) hitme = true;
            break;
        case TORPEDO:                   // photons
            photon();
-           if (game.ididit) hitme = TRUE;
+           if (game.ididit) hitme = true;
            break;
        case MOVE:                      // move
-           warp(1);
+           warp(false);
            break;
        case SHIELDS:                   // shields
-           doshield(1);
+           doshield(false);
            if (game.ididit) {
-               hitme=TRUE;
-               game.shldchg = 0;
+               hitme = true;
+               game.shldchg = false;
            }
            break;
        case DOCK:                      // dock
@@ -446,14 +480,14 @@ static void makemoves(void)
            dreprt();
            break;
        case CHART:                     // chart
-           chart(0);
+           chart(false);
            break;
        case IMPULSE:                   // impulse
            impuls();
            break;
        case REST:                      // rest
            wait();
-           if (game.ididit) hitme = TRUE;
+           if (game.ididit) hitme = true;
            break;
        case WARP:                      // warp
            setwrp();
@@ -466,22 +500,22 @@ static void makemoves(void)
            break;
        case ORBIT:                     // orbit
            orbit();
-           if (game.ididit) hitme = TRUE;
+           if (game.ididit) hitme = true;
            break;
        case TRANSPORT:                 // transport "beam"
            beam();
            break;
        case MINE:                      // mine
            mine();
-           if (game.ididit) hitme = TRUE;
+           if (game.ididit) hitme = true;
            break;
        case CRYSTALS:                  // crystals
            usecrystals();
-           if (game.ididit) hitme = TRUE;
+           if (game.ididit) hitme = true;
            break;
        case SHUTTLE:                   // shuttle
            shuttle();
-           if (game.ididit) hitme = TRUE;
+           if (game.ididit) hitme = true;
            break;
        case PLANETS:                   // Planet list
            preport();
@@ -497,68 +531,65 @@ static void makemoves(void)
            break;
        case EMEXIT:                    // Emergency exit
            clrscr();                   // Hide screen
-           freeze(TRUE);               // forced save
+           freeze(true);               // forced save
            exit(1);                    // And quick exit
            break;
        case PROBE:
            probe();                    // Launch probe
-           if (game.ididit) hitme = TRUE;
+           if (game.ididit) hitme = true;
            break;
        case ABANDON:                   // Abandon Ship
            abandn();
            break;
        case DESTRUCT:                  // Self Destruct
-           dstrct();
+           selfdestruct();
            break;
        case SAVE:                      // Save Game
-           freeze(FALSE);
+           freeze(false);
            clrscr();
            if (game.skill > SKILL_GOOD)
                prout("WARNING--Saved games produce no plaques!");
            break;
        case DEATHRAY:                  // Try a desparation measure
            deathray();
-           if (game.ididit) hitme = TRUE;
+           if (game.ididit) hitme = true;
            break;
        case DEBUGCMD:                  // What do we want for debug???
-#ifdef DEBUG
            debugme();
-#endif
            break;
        case MAYDAY:                    // Call for help
-           help();
-           if (game.ididit) hitme = TRUE;
+           mayday();
+           if (game.ididit) hitme = true;
            break;
        case QUIT:
-           game.alldone = 1;           // quit the game
-#ifdef DEBUG
-           if (game.idebug) score();
-#endif
+           game.alldone = true;                // quit the game
            break;
        case HELP:
-           helpme();   // get help
+           helpme();                   // get help
+           break;
+       case SEED:                      // set random-number seed
+           key = scan();
+           if (key == IHREAL)
+               seed = (int)aaitem;
            break;
        }
-       commandhook(commands[i].name, FALSE);
+       commandhook(commands[i].name, false);
        for (;;) {
            if (game.alldone) break;            // Game has ended
-#ifdef DEBUG
-           if (game.idebug) prout("2500");
-#endif
            if (game.optime != 0.0) {
                events();
                if (game.alldone) break;        // Events did us in
            }
-           if (game.state.galaxy[game.quadx][game.quady].supernova) { // Galaxy went Nova!
-               atover(0);
+           if (game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova) { // Galaxy went Nova!
+               atover(false);
                continue;
            }
-           if (hitme && game.justin==0) {
+           if (hitme && !game.justin) {
                attack(2);
                if (game.alldone) break;
-               if (game.state.galaxy[game.quadx][game.quady].supernova) {      // went NOVA! 
-                   atover(0);
-                   hitme = TRUE;
+               if (game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova) {    // went NOVA! 
+                   atover(false);
+                   hitme = true;
                    continue;
                }
            }
@@ -566,6 +597,7 @@ static void makemoves(void)
        }
        if (game.alldone) break;
     }
+    if (idebug) prout("=== Ending");
 }
 
 
@@ -579,19 +611,39 @@ int main(int argc, char **argv)
     else
        game.options |= OPTION_TTY;
 
-    while ((option = getopt(argc, argv, "t")) != -1) {
+    seed = (int)time(NULL);
+    while ((option = getopt(argc, argv, "r:tx")) != -1) {
        switch (option) {
+       case 'r':
+           replayfp = fopen(optarg, "r");
+           if (replayfp == NULL) {
+               fprintf(stderr, "sst: can't open replay file %s\n", optarg);
+               exit(1);        
+           }
+           if (fscanf(replayfp, "seed %d\n", &seed) != 1) {
+               fprintf(stderr, "sst: replay file %s is ill-formed\n", optarg);
+               exit(1);        
+           }
+           /* FALL THROUGH */
        case 't':
            game.options |= OPTION_TTY;
            game.options &=~ OPTION_CURSES;
            break;
+       case 'x':
+           idebug = true;
+           break;
        default:
-           fprintf(stderr, "usage: sst [-t] [startcommand...].\n");
+           fprintf(stderr, "usage: sst [-t] [-x] [startcommand...].\n");
            exit(0);
        }
     }
+    /* where to save the input in case of bugs */
+    logfp = fopen("sst-input.log", "w");
+    setlinebuf(logfp);
+    fprintf(logfp, "seed %d\n", seed);
+    srand(seed);
 
-    randomize();
+    srand(seed);
     iostart();
 
     line[0] = '\0';
@@ -599,17 +651,14 @@ int main(int argc, char **argv)
        strcat(line, argv[i]);
        strcat(line, " ");
     }
-    while (TRUE) { /* Play a game */
+    for(;;) { /* Play a game */
        setwnd(fullscreen_window);
-#ifdef DEBUG
-       prout("INITIAL OPTIONS: %0lx", game.options);
-#endif /* DEBUG */
        clrscr();
        prelim();
        setup(line[0] == '\0');
        if (game.alldone) {
            score();
-           game.alldone = 0;
+           game.alldone = false;
        }
        else makemoves();
        skip(1);
@@ -618,9 +667,9 @@ int main(int argc, char **argv)
 
        if (game.tourn && game.alldone) {
            proutn("Do you want your score recorded?");
-           if (ja()) {
+           if (ja() == true) {
                chew2();
-               freeze(FALSE);
+               freeze(false);
            }
        }
        proutn("Do you want to play again? ");
@@ -632,7 +681,8 @@ int main(int argc, char **argv)
 }
 
 
-void cramen(int i) 
+void cramen(feature i) 
+/* print the name of an enemy */
 {
     /* return an enemy */
     char *s;
@@ -654,25 +704,28 @@ void cramen(int i)
     proutn(s);
 }
 
-char *cramlc(enum loctype key, int x, int y)
+char *cramlc(enum loctype key, coord w)
+/* name a location */
 {
     static char buf[32];
     buf[0] = '\0';
     if (key == quadrant) strcpy(buf, "Quadrant ");
     else if (key == sector) strcpy(buf, "Sector ");
-    sprintf(buf+strlen(buf), "%d - %d", x, y);
+    sprintf(buf+strlen(buf), "%d - %d", w.x, w.y);
     return buf;
 }
 
-void crmena(int i, int enemy, int key, int x, int y) 
+void crmena(bool stars, feature enemy, enum loctype key, coord w) 
+/* print an enemy and his location */
 {
-    if (i == 1) proutn("***");
+    if (stars) proutn("***");
     cramen(enemy);
     proutn(" at ");
-    proutn(cramlc(key, x, y));
+    proutn(cramlc(key, w));
 }
 
-void crmshp(void) 
+void crmshp(void)
+/* print our ship name */
 {
     char *s;
     switch (game.ship) {
@@ -683,7 +736,8 @@ void crmshp(void)
     proutn(s);
 }
 
-void stars(void) 
+void stars(void)
+/* print a line of stars */
 {
     prouts("******************************************************");
     skip(1);
@@ -694,14 +748,18 @@ double expran(double avrage)
     return -avrage*log(1e-7 + Rand());
 }
 
-double Rand(void) {
-       return rand()/(1.0 + (double)RAND_MAX);
+double Rand(void) 
+{
+    return rand()/(1.0 + (double)RAND_MAX);
 }
 
-void iran(int size, int *i, int *j) 
+coord randplace(int size)
+/* choose a random location */ 
 {
-    *i = Rand()*(size*1.0) + 1.0;
-    *j = Rand()*(size*1.0) + 1.0;
+    coord w;
+    w.x = Rand()*(size*1.0) + 1.0;
+    w.y = Rand()*(size*1.0) + 1.0;
+    return w;
 }
 
 void chew(void)
@@ -772,59 +830,58 @@ int scan(void)
     return IHALPHA;
 }
 
-int ja(void) 
+bool ja(void)
+/* yes-or-no confirmation */
 {
     chew();
-    while (TRUE) {
+    for(;;) {
        scan();
        chew();
-       if (*citem == 'y') return TRUE;
-       if (*citem == 'n') return FALSE;
+       if (*citem == 'y') return true;
+       if (*citem == 'n') return false;
        proutn("Please answer with \"Y\" or \"N\": ");
     }
 }
 
-void huh(void) 
+void huh(void)
+/* complain about unparseable input */
 {
     chew();
     skip(1);
     prout("Beg your pardon, Captain?");
 }
 
-int isit(char *s) 
+bool isit(char *s) 
+/* compares s to citem and returns true if it matches to the length of s */
 {
-    /* New function -- compares s to scanned citem and returns true if it
-       matches to the length of s */
-
     return strncasecmp(s, citem, max(1, strlen(citem))) == 0;
-
 }
 
-#ifdef DEBUG
-void debugme(void) 
+void debugme(void)
+/* access to the internals for debugging */
 {
     proutn("Reset levels? ");
-    if (ja() != 0) {
-       if (energy < game.inenrg) energy = game.inenrg;
-       shield = game.inshld;
-       torps = game.intorps;
+    if (ja() == true) {
+       if (game.energy < game.inenrg) game.energy = game.inenrg;
+       game.shield = game.inshld;
+       game.torps = game.intorps;
        game.lsupres = game.inlsr;
     }
     proutn("Reset damage? ");
-    if (ja() != 0) {
+    if (ja() == true) {
        int i;
        for (i=0; i < NDEVICES; i++) 
            if (game.damage[i] > 0.0) 
                game.damage[i] = 0.0;
     }
-    proutn("Toggle game.idebug? ");
-    if (ja() != 0) {
-       game.idebug = !game.idebug;
-       if (game.idebug) prout("Debug output ON");
+    proutn("Toggle debug flag? ");
+    if (ja() == true) {
+       idebug = !idebug;
+       if (idebug) prout("Debug output ON");
        else prout("Debug output OFF");
     }
     proutn("Cause selective damage? ");
-    if (ja() != 0) {
+    if (ja() == true) {
        int i, key;
        for (i=0; i < NDEVICES; i++) {
            proutn("Kill ");
@@ -838,11 +895,12 @@ void debugme(void)
        }
     }
     proutn("Examine/change events? ");
-    if (ja() != 0) {
+    if (ja() == true) {
+       event *ev;
+       coord w;
        int i;
        for (i = 1; i < NEVENTS; i++) {
            int key;
-           if (game.future[i] == FOREVER) continue;
            switch (i) {
            case FSNOVA:  proutn("Supernova       "); break;
            case FTBEAM:  proutn("T Beam          "); break;
@@ -851,21 +909,56 @@ void debugme(void)
            case FCDBAS:  proutn("Base Destroy    "); break;
            case FSCMOVE: proutn("SC Move         "); break;
            case FSCDBAS: proutn("SC Base Destroy "); break;
+           case FDSPROB: proutn("Probe Move      "); break;
+           case FDISTR:  proutn("Distress Call   "); break;
+           case FENSLV:  proutn("Enlavement      "); break;
+           case FREPRO:  proutn("Klingon Build   "); break;
            }
-           proutn("%.2f", game.future[i]-game.state.date);
+           if (is_scheduled(i)) {
+               proutn("%.2f", scheduled(i)-game.state.date);
+               if (i == FENSLV || i == FREPRO) {
+                   ev = findevent(i);
+                   proutn(" in %d-%d", ev->quadrant.x,ev->quadrant.y);
+               }
+           } else
+               proutn("never");
+           proutn("? ");
            chew();
-           proutn("  ?");
            key = scan();
-           if (key == IHREAL) {
-               game.future[i] = game.state.date + aaitem;
+           if (key == 'n') {
+               unschedule(i);
+               chew();
+           } else if (key == IHREAL) {
+               ev = schedule(i, aaitem);
+               if (i == FENSLV || i == FREPRO) {
+                   chew();
+                   proutn("In quadrant- ");
+                   key = scan();
+                   /* IHEOL says to leave coordinates as they are */
+                   if (key != IHEOL) {
+                       if (key != IHREAL) {
+                           prout("Event %d canceled, no x coordinate.", i);
+                           unschedule(i);
+                           continue;
+                       }
+                       w.x = (int)aaitem;
+                       key = scan();
+                       if (key != IHREAL) {
+                           prout("Event %d canceled, no y coordinate.", i);
+                           unschedule(i);
+                           continue;
+                       }
+                       w.y = (int)aaitem;
+                       ev->quadrant = w;
+                   }
+               }
            }
        }
        chew();
     }
     proutn("Induce supernova here? ");
-    if (ja() != 0) {
-       game.state.galaxy[game.quadx][game.quady].supernova = TRUE;
-       atover(1);
+    if (ja() == true) {
+       game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova = true;
+       atover(true);
     }
 }
-#endif