On Stas Sergeev's suggestion, change where the logfile is written.
[super-star-trek.git] / src / sst.c
index 0679cbb9c271a9b8bfe759fa5756eda213accec3..ccf7470b64ea04fc4f39152e0fa75ff6e272f5a5 100644 (file)
--- a/src/sst.c
+++ b/src/sst.c
@@ -155,9 +155,24 @@ for a lot of magic numbers and refactored the heck out of it.
 
    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).  Right now this is just color, but
-      eventually we'll fold in BSD-Trek-like logic for Klingons to attack
-      and enslave inhabited worlds.
+      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 */
@@ -165,32 +180,17 @@ static char line[128], *linep = line;
 
 struct game game;
 coord thing;
-int iqhere, iqengry;
+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
-bool randready;                // Has the random-number generator initialized?
-FILE *logfp;
-
-char *device[NDEVICES] = {
-       "S. R. Sensors",
-       "L. R. Sensors",
-       "Phasers",
-       "Photon Tubes",
-       "Life Support",
-       "Warp Engines",
-       "Impulse Engines",
-       "Shields",
-       "Subspace Radio",
-       "Shuttle Craft",
-       "Computer",
-       "Transporter",
-       "Shield Control",
-       "Death Ray",
-       "D. S. Probe"};                                                                 
+FILE *logfp, *replayfp;
+
+char *systnames[NINHAB + 1];
+char *device[NDEVICES];
 
 static struct 
 {
@@ -274,14 +274,22 @@ commands[] = {
        {"QUIT",        QUIT,           0},
 #define HELP   36
        {"HELP",        HELP,           0},
+#define SEED   37
+       {"SEED",        SEED,           0},
+#if BSD_BUG_FOR_BUG
+#define VISUAL 38
+       {"VISUAL",      VISUAL,         0},
+#endif
 };
 
 #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:");
+    proutn(_("LEGAL COMMANDS ARE:"));
     for (i = 0; i < NUMCOMMANDS; i++) {
        if (!ACCEPT(i))
            continue;
@@ -293,7 +301,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;
@@ -305,7 +314,7 @@ static void helpme(void)
     for(;;) {
        if (key == IHEOL) {
            setwnd(prompt_window);
-           proutn("Help on what command? ");
+           proutn(_("Help on what command? "));
            key = scan();
        }
        setwnd(message_window);
@@ -318,7 +327,6 @@ static void helpme(void)
        }
        if (i != NUMCOMMANDS) break;
        skip(1);
-       prout("Valid commands:");
        listCommands();
        key = IHEOL;
        chew();
@@ -336,9 +344,13 @@ static void helpme(void)
     if (fp == NULL)
         fp = fopen(DOC_NAME, "r");
     if (fp == NULL) {
-       prout("Spock-  \"Captain, that information is missing from the");
-        prout("   computer. You need to find "DOC_NAME" and put it in the");
-        prout("   current directory or to "SSTDOC".\"");
+       prout(_("Spock-  \"Captain, that information is missing from the"));
+        proutn(_("   computer. You need to find "));
+        proutn(DOC_NAME);
+        prout(_(" and put it in the"));
+        proutn(_("   current directory or to "));
+        proutn(SSTDOC);
+        prout(".\"");
        /*
         * This used to continue: "You need to find SST.DOC and put 
         * it in the current directory."
@@ -347,7 +359,7 @@ static void helpme(void)
     }
     for (;;) {
        if (fgets(linebuf, sizeof(linebuf), fp) == NULL) {
-           prout("Spock- \"Captain, there is no information on that command.\"");
+           prout(_("Spock- \"Captain, there is no information on that command.\""));
            fclose(fp);
            return;
        }
@@ -361,25 +373,31 @@ static void helpme(void)
     }
 
     skip(1);
-    prout("Spock- \"Captain, I've found the following information:\"");
+    prout(_("Spock- \"Captain, I've found the following information:\""));
     skip(1);
 
-    while (fgets(linebuf, sizeof(linebuf),fp)) {
+    while (fgets(linebuf, sizeof(linebuf), fp)) {
+       char *eol;
        if (strstr(linebuf, "******"))
            break;
-       proutn(linebuf);
+       if ((eol = strpbrk(linebuf, "\r\n")))
+           *eol = 0;
+       prout(linebuf);
     }
     fclose(fp);
 }
 
-void enqueue(char *s) 
+void enqueue(char *s)
+/* enqueue input for the command parser */
 {
+    chew();
     strcpy(line, s);
 }
 
-static void makemoves(void) 
+static void makemoves(void)
+/* command-interpretation loop */
 {
-    int i, v = 0;
+    int key, i, v = 0;
     bool hitme;
     clrscr();
     setwnd(message_window);
@@ -387,7 +405,7 @@ static void makemoves(void)
        drawmaps(1);
        for(;;)  { /* get a command */
            hitme = false;
-           game.justin = 0;
+           game.justin = false;
            game.optime = 0.0;
            i = -1;
            chew();
@@ -398,7 +416,7 @@ static void makemoves(void)
                makechart();
                continue;
            }
-           game.ididit=0;
+           game.ididit = false;
            clrscr();
            setwnd(message_window);
            clrscr();
@@ -441,24 +459,24 @@ static void makemoves(void)
            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
-           dock(1);
-           if (game.ididit) attack(0);
+           dock(true);
+           if (game.ididit) attack(false);
            break;
        case DAMAGES:                   // damages
            dreprt();
            break;
        case CHART:                     // chart
-           chart(0);
+           chart(false);
            break;
        case IMPULSE:                   // impulse
            impuls();
@@ -520,13 +538,13 @@ static void makemoves(void)
            abandn();
            break;
        case DESTRUCT:                  // Self Destruct
-           dstrct();
+           selfdestruct();
            break;
        case SAVE:                      // Save Game
            freeze(false);
            clrscr();
            if (game.skill > SKILL_GOOD)
-               prout("WARNING--Saved games produce no plaques!");
+               prout(_("WARNING--Saved games produce no plaques!"));
            break;
        case DEATHRAY:                  // Try a desparation measure
            deathray();
@@ -540,11 +558,21 @@ static void makemoves(void)
            if (game.ididit) hitme = true;
            break;
        case QUIT:
-           game.alldone = 1;           // quit the game
+           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;
+#if BSD_BUG_FOR_BUG
+       case VISUAL:
+           visual();                   // perform visual scan
            break;
+#endif
        }
        commandhook(commands[i].name, false);
        for (;;) {
@@ -554,14 +582,14 @@ static void makemoves(void)
                if (game.alldone) break;        // Events did us in
            }
            if (game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova) { // Galaxy went Nova!
-               atover(0);
+               atover(false);
                continue;
            }
-           if (hitme && game.justin==0) {
-               attack(2);
+           if (hitme && !game.justin) {
+               attack(true);
                if (game.alldone) break;
                if (game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova) {    // went NOVA! 
-                   atover(0);
+                   atover(false);
                    hitme = true;
                    continue;
                }
@@ -584,19 +612,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("/usr/tmp/sst-input.log", "w");
+    setlinebuf(logfp);
+    fprintf(logfp, "seed %d\n", seed);
+    srand(seed);
 
-    randomize();
+    srand(seed);
     iostart();
 
     line[0] = '\0';
@@ -611,7 +659,7 @@ int main(int argc, char **argv)
        setup(line[0] == '\0');
        if (game.alldone) {
            score();
-           game.alldone = 0;
+           game.alldone = false;
        }
        else makemoves();
        skip(1);
@@ -619,73 +667,79 @@ int main(int argc, char **argv)
        skip(1);
 
        if (game.tourn && game.alldone) {
-           proutn("Do you want your score recorded?");
-           if (ja()) {
+           proutn(_("Do you want your score recorded?"));
+           if (ja() == true) {
                chew2();
                freeze(false);
            }
        }
-       proutn("Do you want to play again? ");
+       proutn(_("Do you want to play again? "));
        if (!ja()) break;
     }
     skip(1);
-    prout("May the Great Bird of the Galaxy roost upon your home planet.");
+    prout(_("May the Great Bird of the Galaxy roost upon your home planet."));
     return 0;
 }
 
 
-void cramen(int i) 
+void cramen(feature i) 
+/* print the name of an enemy */
 {
     /* return an enemy */
     char *s;
        
     switch (i) {
-    case IHR: s = "Romulan"; break;
-    case IHK: s = "Klingon"; break;
-    case IHC: s = "Commander"; break;
-    case IHS: s = "Super-commander"; break;
-    case IHSTAR: s = "Star"; break;
-    case IHP: s = "Planet"; break;
-    case IHB: s = "Starbase"; break;
-    case IHBLANK: s = "Black hole"; break;
-    case IHT: s = "Tholian"; break;
-    case IHWEB: s = "Tholian web"; break;
-    case IHQUEST: s = "Stranger"; break;
+    case IHR: s = _("Romulan"); break;
+    case IHK: s = _("Klingon"); break;
+    case IHC: s = _("Commander"); break;
+    case IHS: s = _("Super-commander"); break;
+    case IHSTAR: s = _("Star"); break;
+    case IHP: s = _("Planet"); break;
+    case IHB: s = _("Starbase"); break;
+    case IHBLANK: s = _("Black hole"); break;
+    case IHT: s = _("Tholian"); break;
+    case IHWEB: s = _("Tholian web"); break;
+    case IHQUEST: s = _("Stranger"); break;
+    case IHW: s = _("Inhabited World"); break;
     default: s = "Unknown??"; break;
     }
     proutn(s);
 }
 
 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 ");
+    if (key == quadrant) strcpy(buf, _("Quadrant "));
+    else if (key == sector) strcpy(buf, _("Sector "));
     sprintf(buf+strlen(buf), "%d - %d", w.x, w.y);
     return buf;
 }
 
-void crmena(int i, int enemy, int key, coord w) 
+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(_(" at "));
     proutn(cramlc(key, w));
 }
 
-void crmshp(void) 
+void crmshp(void)
+/* print our ship name */
 {
     char *s;
     switch (game.ship) {
-    case IHE: s = "Enterprise"; break;
-    case IHF: s = "Faerie Queene"; break;
+    case IHE: s = _("Enterprise"); break;
+    case IHF: s = _("Faerie Queene"); break;
     default:  s = "Ship???"; break;
     }
     proutn(s);
 }
 
-void stars(void) 
+void stars(void)
+/* print a line of stars */
 {
     prouts("******************************************************");
     skip(1);
@@ -698,21 +752,16 @@ double expran(double avrage)
 
 double Rand(void) 
 {
-    if (!randready) {
-       if (seed == 0)
-           seed = (unsigned)time(NULL);
-       if (idebug)
-           fprintf(logfp, "seed %d\n", seed);
-       srand(seed);
-       randready = true;
-    }
     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)
@@ -783,7 +832,8 @@ int scan(void)
     return IHALPHA;
 }
 
-bool ja(void) 
+bool ja(void)
+/* yes-or-no confirmation */
 {
     chew();
     for(;;) {
@@ -791,50 +841,49 @@ bool ja(void)
        chew();
        if (*citem == 'y') return true;
        if (*citem == 'n') return false;
-       proutn("Please answer with \"Y\" or \"N\": ");
+       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?");
+    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;
-
 }
 
-void debugme(void) 
+void debugme(void)
+/* access to the internals for debugging */
 {
     proutn("Reset levels? ");
-    if (ja() != 0) {
+    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) {
+    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 ");
@@ -848,11 +897,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 (!is_scheduled(i)) continue;
            switch (i) {
            case FSNOVA:  proutn("Supernova       "); break;
            case FTBEAM:  proutn("T Beam          "); break;
@@ -861,20 +911,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", scheduled(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) {
-               schedule(i, 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) {
+    if (ja() == true) {
        game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova = true;
-       atover(1);
+       atover(true);
     }
 }