a0d732d31fe5674c79b6e52063016ae028822b1e
[super-star-trek.git] / src / sst.c
1 #include <ctype.h>
2 #include <getopt.h>
3 #include <time.h>
4 #include "sstlinux.h"
5 #include "sst.h"
6
7 #define DOC_NAME "sst.doc"
8         
9 /*
10
11 Dave Matuszek says:
12
13    SRSCAN, MOVE, PHASERS, CALL, STATUS, IMPULSE, PHOTONS, ABANDON,
14    LRSCAN, WARP, SHIELDS, DESTRUCT, CHART, REST, DOCK, QUIT, and DAMAGE
15    were in the original non-"super" version of UT FORTRAN Star Trek.
16
17    Tholians weren't in the original. Dave is dubious about their merits.
18    (They are now controlled by OPTION_THOLIAN and turned off if the game
19    type is "plain".)
20
21    Planets and dilithium crystals weren't in the original.  Dave is OK
22    with this idea. (It's now controlled by OPTION_PLANETS and turned 
23    off if the game type is "plain".)
24
25    Dave says the bit about the Galileo getting turned into a
26    McDonald's is "consistant with our original vision".  (This has been
27    left permanently enabled, as it can only happen if OPTION_PLANETS
28    is on.)
29
30    Dave also says the Space Thingy should not be preserved across saved
31    games, so you can't prove to others that you've seen it.  He says it
32    shouldn't fire back, either.  It should do nothing except scream and
33    disappear when hit by photon torpedos.  It's OK that it may move
34    when attacked, but it didn't in the original.  (Whether the Thingy
35    can fire back is now controlled by OPTION_THINGY and turned off if the
36    game type is "plain" or "almy".  The no-save behavior has been restored.)
37
38    The Faerie Queen, black holes, and time warping were in the original.
39
40 Here are Tom Almy's changes:
41
42    In early 1997, I got the bright idea to look for references to
43    "Super Star Trek" on the World Wide Web. There weren't many hits,
44    but there was one that came up with 1979 Fortran sources! This
45    version had a few additional features that mine didn't have,
46    however mine had some feature it didn't have. So I merged its
47    features that I liked. I also took a peek at the DECUS version (a
48    port, less sources, to the PDP-10), and some other variations.
49
50    1, Compared to the original UT version, I've changed the "help" command to
51       "call" and the "terminate" command to "quit" to better match
52       user expectations. The DECUS version apparently made those changes
53       as well as changing "freeze" to "save". However I like "freeze".
54       (Both "freeze" and "save" work in SST2K.)
55
56    2. The experimental deathray originally had only a 5% chance of
57       success, but could be used repeatedly. I guess after a couple
58       years of use, it was less "experimental" because the 1979
59       version had a 70% success rate. However it was prone to breaking
60       after use. I upgraded the deathray, but kept the original set of
61       failure modes (great humor!).  (Now controlled by OPTION_DEATHRAY
62       and turned off if game type is "plain".)
63
64    3. The 1979 version also mentions srscan and lrscan working when
65       docked (using the starbase's scanners), so I made some changes here
66       to do this (and indicating that fact to the player), and then realized
67       the base would have a subspace radio as well -- doing a Chart when docked
68       updates the star chart, and all radio reports will be heard. The Dock
69       command will also give a report if a base is under attack.
70
71    4. Tholian Web from the 1979 version.  (Now controlled by
72       OPTION_THOLIAN and turned off if game type is "plain".)
73
74    5. Enemies can ram the Enterprise. (Now controlled by OPTION_RAMMING
75       and turned off if game type is "plain".)
76
77    6. Regular Klingons and Romulans can move in Expert and Emeritus games. 
78       This code could use improvement. (Now controlled by OPTION_MVBADDY
79       and turned off if game type is "plain".)
80
81    7. The deep-space probe feature from the DECUS version.  (Now controlled
82       by OPTION_PROBE and turned off if game type is "plain").
83
84    8. 'emexit' command from the 1979 version.
85
86    9. Bugfix: Klingon commander movements are no longer reported if long-range 
87       sensors are damaged.
88
89    10. Bugfix: Better base positioning at startup (more spread out).
90       That made sense to add because most people abort games with 
91       bad base placement.
92
93    In June 2002, I fixed two known bugs and a documentation typo.
94    In June 2004 I fixed a number of bugs involving: 1) parsing invalid
95    numbers, 2) manual phasers when SR scan is damaged and commander is
96    present, 3) time warping into the future, 4) hang when moving
97    klingons in crowded quadrants.  (These fixes are in SST2K.)
98
99 Here are Stas Sergeev's changes:
100
101    1. The Space Thingy can be shoved, if you ram it, and can fire back if 
102       fired upon. (Now controlled by OPTION_THINGY and turned off if game 
103       type is "plain" or "almy".)
104
105    2. When you are docked, base covers you with an almost invincible shield. 
106       (A commander can still ram you, or a Romulan can destroy the base,
107       or a SCom can even succeed with direct attack IIRC, but this rarely 
108       happens.)  (Now controlled by OPTION_BASE and turned off if game 
109       type is "plain" or "almy".)
110
111    3. Ramming a black hole is no longer instant death.  There is a
112       chance you might get timewarped instead. (Now controlled by 
113       OPTION_BLKHOLE and turned off if game type is "plain" or "almy".)
114
115    4. The Tholian can be hit with phasers.
116
117    5. SCom can't escape from you if no more enemies remain 
118       (without this, chasing SCom can take an eternity).
119
120    6. Probe target you enter is now the destination quadrant. Before I don't 
121       remember what it was, but it was something I had difficulty using.
122
123    7. Secret password is now autogenerated.
124
125    8. "Plaque" is adjusted for A4 paper :-)
126
127    9. Phasers now tells you how much energy needed, but only if the computer 
128       is alive.
129
130    10. Planets are auto-scanned when you enter the quadrant.
131
132    11. Mining or using crystals in presense of enemy now yields an attack.
133        There are other minor adjustments to what yields an attack
134        and what does not.
135
136    12. "freeze" command reverts to "save", most people will understand this
137        better anyway. (SST2K recognizes both.)
138
139    13. Screen-oriented interface, with sensor scans always up.  (SST2K
140        supports both screen-oriented and TTY modes.)
141
142 Eric Raymond's changes:
143
144 Mainly, I translated this C code out of FORTRAN into C -- created #defines
145 for a lot of magic numbers and refactored the heck out of it.
146
147    1. "sos" and "call" becomes "mayday", "freeze" and "save" are both good.
148
149    2. Status report now indicates when dilithium crystals are on board.
150
151    3. Per Dave Matuszek's remarks, Thingy state is never saved across games.
152
153    4. Added game option selection so you can play a close (but not bug-for-
154       bug identical) approximation of older versions.
155
156    5. Half the quadrants now have inhabited planets, from which one 
157       cannot mine dilithium (there will still be the same additional number
158       of dilithium-bearing planets).  Torpedoing an inhabited world is *bad*.
159       There is BSD-Trek-like logic for Klingons to attack and enslave 
160       inhabited worlds, producing more ships (only is skill is 'good' or 
161       better). (Controlled by OPTION_WORLDS and turned off if game 
162       type is "plain" or "almy".)
163
164    6. User input is now logged so we can do regression testing.
165
166    7. More BSD-Trek features: You can now lose if your entire crew
167       dies in battle.  When abandoning ship in a game with inhabited
168       worlds enabled, they must have one in the quadrant to beam down
169       to; otherwise they die in space and this counts heavily against
170       your score.  Docking at a starbase replenishes your crew.
171
172    8. Still more BSD-Trek: we now have a weighted damage table.
173       Also, the nav subsystem (enabling automatic course
174       setting) can be damaged separately from the main computer (which
175       handles weapons targeting, ETA calculation, and self-destruct).
176 */
177
178 /* the input queue */
179 static char line[128], *linep = line;
180
181 struct game game;
182 coord thing;
183 bool iqhere, iqengry;
184 int iscore, iskill;     // Common PLAQ
185 double aaitem;
186 double perdate;
187 char citem[10];
188 int seed;               // the random-number seed
189 bool idebug;            // debug mode
190 FILE *logfp, *replayfp;
191
192 char *systnames[NINHAB + 1];
193 char *device[NDEVICES];
194
195 static struct 
196 {
197     char *name;
198     int value;
199     unsigned long option;
200 }
201
202 commands[] = {
203 #define SRSCAN  0
204         {"SRSCAN",      SRSCAN,         OPTION_TTY},
205 #define STATUS  1
206         {"STATUS",      STATUS,         OPTION_TTY},
207 #define REQUEST 2
208         {"REQUEST",     REQUEST,        OPTION_TTY},
209 #define LRSCAN  3
210         {"LRSCAN",      LRSCAN,         OPTION_TTY},
211 #define PHASERS 4
212         {"PHASERS",     PHASERS,        0},
213 #define TORPEDO 5
214         {"TORPEDO",     TORPEDO,        0},
215         {"PHOTONS",     TORPEDO,        0},
216 #define MOVE    7
217         {"MOVE",        MOVE,           0},
218 #define SHIELDS 8
219         {"SHIELDS",     SHIELDS,        0},
220 #define DOCK    9
221         {"DOCK",        DOCK,           0},
222 #define DAMAGES 10
223         {"DAMAGES",     DAMAGES,        0},
224 #define CHART   11
225         {"CHART",       CHART,          0},
226 #define IMPULSE 12
227         {"IMPULSE",     IMPULSE,        0},
228 #define REST    13
229         {"REST",        REST,           0},
230 #define WARP    14
231         {"WARP",        WARP,           0},
232 #define SCORE   15
233         {"SCORE",       SCORE,          0},
234 #define SENSORS 16
235         {"SENSORS",     SENSORS,        OPTION_PLANETS},
236 #define ORBIT   17
237         {"ORBIT",       ORBIT,          OPTION_PLANETS},
238 #define TRANSPORT       18
239         {"TRANSPORT",   TRANSPORT,      OPTION_PLANETS},
240 #define MINE    19
241         {"MINE",        MINE,           OPTION_PLANETS},
242 #define CRYSTALS        20
243         {"CRYSTALS",    CRYSTALS,       OPTION_PLANETS},
244 #define SHUTTLE 21
245         {"SHUTTLE",     SHUTTLE,        OPTION_PLANETS},
246 #define PLANETS 22
247         {"PLANETS",     PLANETS,        OPTION_PLANETS},
248 #define REPORT  23
249         {"REPORT",      REPORT,         0},
250 #define COMPUTER        24
251         {"COMPUTER",    COMPUTER,       0},
252 #define COMMANDS        25
253         {"COMMANDS",    COMMANDS,       0},
254 #define EMEXIT  26
255         {"EMEXIT",      EMEXIT,         0},
256 #define PROBE   27
257         {"PROBE",       PROBE,          OPTION_PROBE},
258 #define SAVE    28
259         {"SAVE",        SAVE,           0},
260         {"FREEZE",      SAVE,           0},
261 #define ABANDON 30
262         {"ABANDON",     ABANDON,        0},
263 #define DESTRUCT        31
264         {"DESTRUCT",    DESTRUCT,       0},
265 #define DEATHRAY        32
266         {"DEATHRAY",    DEATHRAY,       0},
267 #define DEBUGCMD        33
268         {"DEBUG",       DEBUGCMD,       0},
269 #define MAYDAY  34
270         {"MAYDAY",      MAYDAY,         0},
271         //{"SOS",               MAYDAY,         0},
272         //{"CALL",      MAYDAY,         0},
273 #define QUIT    35
274         {"QUIT",        QUIT,           0},
275 #define HELP    36
276         {"HELP",        HELP,           0},
277 #define SEED    37
278         {"SEED",        SEED,           0},
279 #if BSD_BUG_FOR_BUG
280 #define VISUAL  38
281         {"VISUAL",      VISUAL,         0},
282 #endif
283 };
284
285 #define NUMCOMMANDS     ARRAY_SIZE(commands)
286 #define ACCEPT(i)       (!commands[i].option || (commands[i].option & game.options))
287
288 static void listCommands(void) 
289 /* generate a list of legal commands */
290 {
291     int i, k = 0;
292     proutn(_("LEGAL COMMANDS ARE:"));
293     for (i = 0; i < NUMCOMMANDS; i++) {
294         if (!ACCEPT(i))
295             continue;
296         if (k % 5 == 0)
297             skip(1);
298         proutn("%-12s ", commands[i].name); 
299         k++;
300     }
301     skip(1);
302 }
303
304 static void helpme(void)
305 /* browse on-line help */
306 {
307     int i, j;
308     char cmdbuf[32], *cp;
309     char linebuf[132];
310     FILE *fp;
311     /* Give help on commands */
312     int key;
313     key = scan();
314     for(;;) {
315         if (key == IHEOL) {
316             setwnd(prompt_window);
317             proutn(_("Help on what command? "));
318             key = scan();
319         }
320         setwnd(message_window);
321         if (key == IHEOL)
322             return;
323         for (i = 0; i < NUMCOMMANDS; i++) {
324             if (ACCEPT(i) && strcasecmp(commands[i].name, citem)==0) {
325                 i = commands[i].value;
326                 break;
327             }
328         }
329         if (i != NUMCOMMANDS)
330             break;
331         skip(1);
332         listCommands();
333         key = IHEOL;
334         chew();
335         skip(1);
336     }
337     if (i == COMMANDS) {
338         strcpy(cmdbuf, " ABBREV");
339     }
340     else {
341         for (j = 0; commands[i].name[j]; j++)
342             cmdbuf[j] = toupper(commands[i].name[j]);
343         cmdbuf[j] = '\0';
344     }
345     fp = fopen(SSTDOC, "r");
346     if (fp == NULL)
347         fp = fopen(DOC_NAME, "r");
348     if (fp == NULL) {
349         prout(_("Spock-  \"Captain, that information is missing from the"));
350         proutn(_("   computer. You need to find "));
351         proutn(DOC_NAME);
352         prout(_(" and put it in the"));
353         proutn(_("   current directory or to "));
354         proutn(SSTDOC);
355         prout(".\"");
356         /*
357          * This used to continue: "You need to find SST.DOC and put 
358          * it in the current directory."
359          */
360         return;
361     }
362     for (;;) {
363         if (fgets(linebuf, sizeof(linebuf), fp) == NULL) {
364             prout(_("Spock- \"Captain, there is no information on that command.\""));
365             fclose(fp);
366             return;
367         }
368         if (linebuf[0] == '%' && linebuf[1] == '%'&& linebuf[2] == ' ') {
369             for (cp = linebuf+3; isspace(*cp); cp++)
370                 continue;
371             linebuf[strlen(linebuf)-1] = '\0';
372             if (strcasecmp(cp, cmdbuf) == 0)
373                 break;
374         }
375     }
376
377     skip(1);
378     prout(_("Spock- \"Captain, I've found the following information:\""));
379     skip(1);
380
381     while (fgets(linebuf, sizeof(linebuf), fp)) {
382         char *eol;
383         if (strstr(linebuf, "******"))
384             break;
385         if ((eol = strpbrk(linebuf, "\r\n")))
386             *eol = 0;
387         prout(linebuf);
388     }
389     fclose(fp);
390 }
391
392 static void makemoves(void)
393 /* command-interpretation loop */
394 {
395     int key, i, v = 0;
396     bool hitme;
397     clrscr();
398     setwnd(message_window);
399     for(;;) { /* command loop */
400         drawmaps(1);
401         for(;;)  { /* get a command */
402             hitme = false;
403             game.justin = false;
404             game.optime = 0.0;
405             i = -1;
406             chew();
407             setwnd(prompt_window);
408             clrscr();
409             proutn("COMMAND> ");
410             if (scan() == IHEOL) {
411                 if (game.options & OPTION_CURSES)
412                     makechart();
413                 continue;
414             }
415             game.ididit = false;
416             clrscr();
417             setwnd(message_window);
418             clrscr();
419             for (i=0; i < ABANDON; i++)
420                 if (ACCEPT(i) && isit(commands[i].name)) {
421                     v = commands[i].value;
422                     break;
423                 }
424             if (i < ABANDON && (!commands[i].option || (commands[i].option & game.options))) 
425                 break;
426             for (; i < NUMCOMMANDS; i++)
427                 if (ACCEPT(i) && strcasecmp(commands[i].name, citem) == 0) {
428                     v = commands[i].value;
429                     break;
430                 }
431             if (i < NUMCOMMANDS && (!commands[i].option || (commands[i].option & game.options))) 
432                 break;
433             listCommands();
434         }
435         commandhook(commands[i].name, true);
436         switch (v) { /* command switch */
437         case SRSCAN:                 // srscan
438             srscan();
439             break;
440         case STATUS:                 // status
441             status(0);
442             break;
443         case REQUEST:                   // status request 
444             request();
445             break;
446         case LRSCAN:                    // lrscan
447             lrscan();
448             break;
449         case PHASERS:                   // phasers
450             phasers();
451             if (game.ididit)
452                 hitme = true;
453             break;
454         case TORPEDO:                   // photons
455             photon();
456             if (game.ididit)
457                 hitme = true;
458             break;
459         case MOVE:                      // move
460             warp(false);
461             break;
462         case SHIELDS:                   // shields
463             doshield(false);
464             if (game.ididit) {
465                 hitme = true;
466                 game.shldchg = false;
467             }
468             break;
469         case DOCK:                      // dock
470             dock(true);
471             if (game.ididit)
472                 attack(false);          
473             break;
474         case DAMAGES:                   // damages
475             dreprt();
476             break;
477         case CHART:                     // chart
478             makechart();
479             break;
480         case IMPULSE:                   // impulse
481             impuls();
482             break;
483         case REST:                      // rest
484             wait();
485             if (game.ididit)
486                 hitme = true;
487             break;
488         case WARP:                      // warp
489             setwrp();
490             break;
491         case SCORE:                     // score
492             score();
493             break;
494         case SENSORS:                   // sensors
495             sensor();
496             break;
497         case ORBIT:                     // orbit
498             orbit();
499             if (game.ididit)
500                 hitme = true;
501             break;
502         case TRANSPORT:                 // transport "beam"
503             beam();
504             break;
505         case MINE:                      // mine
506             mine();
507             if (game.ididit)
508                 hitme = true;
509             break;
510         case CRYSTALS:                  // crystals
511             usecrystals();
512             if (game.ididit)
513                 hitme = true;
514             break;
515         case SHUTTLE:                   // shuttle
516             shuttle();
517             if (game.ididit)
518                 hitme = true;
519             break;
520         case PLANETS:                   // Planet list
521             preport();
522             break;
523         case REPORT:                    // Game Report 
524             report();
525             break;
526         case COMPUTER:                  // use COMPUTER!
527             eta();
528             break;
529         case COMMANDS:
530             listCommands();
531             break;
532         case EMEXIT:                    // Emergency exit
533             clrscr();                   // Hide screen
534             freeze(true);               // forced save
535             exit(1);                    // And quick exit
536             break;
537         case PROBE:
538             probe();                    // Launch probe
539             if (game.ididit)
540                 hitme = true;
541             break;
542         case ABANDON:                   // Abandon Ship
543             abandn();
544             break;
545         case DESTRUCT:                  // Self Destruct
546             selfdestruct();
547             break;
548         case SAVE:                      // Save Game
549             freeze(false);
550             clrscr();
551             if (game.skill > SKILL_GOOD)
552                 prout(_("WARNING--Saved games produce no plaques!"));
553             break;
554         case DEATHRAY:                  // Try a desparation measure
555             deathray();
556             if (game.ididit)
557                 hitme = true;
558             break;
559         case DEBUGCMD:                  // What do we want for debug???
560             debugme();
561             break;
562         case MAYDAY:                    // Call for help
563             mayday();
564             if (game.ididit)
565                 hitme = true;
566             break;
567         case QUIT:
568             game.alldone = true;                // quit the game
569             break;
570         case HELP:
571             helpme();                   // get help
572             break;
573         case SEED:                      // set random-number seed
574             key = scan();
575             if (key == IHREAL)
576                 seed = (int)aaitem;
577             break;
578 #if BSD_BUG_FOR_BUG
579         case VISUAL:
580             visual();                   // perform visual scan
581             break;
582 #endif
583         }
584         commandhook(commands[i].name, false);
585         for (;;) {
586             if (game.alldone) break;            // Game has ended
587             if (game.optime != 0.0) {
588                 events();
589                 if (game.alldone) break;        // Events did us in
590             }
591             if (game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova) { // Galaxy went Nova!
592                 atover(false);
593                 continue;
594             }
595             if (hitme && !game.justin) {
596                 attack(true);
597                 if (game.alldone)
598                     break;
599                 if (game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova) {    // went NOVA! 
600                     atover(false);
601                     hitme = true;
602                     continue;
603                 }
604             }
605             break;
606         }
607         if (game.alldone)
608             break;
609     }
610     if (idebug)
611         prout("=== Ending");
612 }
613
614
615 int main(int argc, char **argv) 
616 {
617     int i, option;
618
619     game.options = OPTION_ALL &~ (OPTION_IOMODES | OPTION_SHOWME | OPTION_PLAIN | OPTION_ALMY);
620     if (getenv("TERM"))
621         game.options |= OPTION_CURSES | OPTION_SHOWME;
622     else
623         game.options |= OPTION_TTY;
624
625     seed = (int)time(NULL);
626     while ((option = getopt(argc, argv, "r:tx")) != -1) {
627         switch (option) {
628         case 'r':
629             replayfp = fopen(optarg, "r");
630             if (replayfp == NULL) {
631                 fprintf(stderr, "sst: can't open replay file %s\n", optarg);
632                 exit(1);
633             }
634             if (fscanf(replayfp, "seed %d\n", &seed) != 1) {
635                 fprintf(stderr, "sst: replay file %s is ill-formed\n", optarg);
636                 exit(1);
637             }
638             /* FALL THROUGH */
639         case 't':
640             game.options |= OPTION_TTY;
641             game.options &=~ OPTION_CURSES;
642             break;
643         case 'x':
644             idebug = true;
645             break;
646         default:
647             fprintf(stderr, "usage: sst [-t] [-x] [startcommand...].\n");
648             exit(0);
649         }
650     }
651     /* where to save the input in case of bugs */
652     logfp = fopen("/usr/tmp/sst-input.log", "w");
653     if (logfp) {
654         setlinebuf(logfp);
655         fprintf(logfp, "seed %d\n", seed);
656     }
657     srand(seed);
658
659     iostart();
660
661     line[0] = '\0';
662     for (i = optind; i < argc;  i++) {
663         strcat(line, argv[i]);
664         strcat(line, " ");
665     }
666     for(;;) { /* Play a game */
667         setwnd(fullscreen_window);
668         clrscr();
669         prelim();
670         setup(line[0] == '\0');
671         if (game.alldone) {
672             score();
673             game.alldone = false;
674         }
675         else
676             makemoves();
677         skip(1);
678         stars();
679         skip(1);
680
681         if (game.tourn && game.alldone) {
682             proutn(_("Do you want your score recorded?"));
683             if (ja() == true) {
684                 chew2();
685                 freeze(false);
686             }
687         }
688         proutn(_("Do you want to play again? "));
689         if (!ja())
690             break;
691     }
692     skip(1);
693     prout(_("May the Great Bird of the Galaxy roost upon your home planet."));
694     return 0;
695 }
696
697
698 void cramen(feature i) 
699 /* print the name of an enemy */
700 {
701     /* return an enemy */
702     char *s;
703         
704     switch (i) {
705     case IHR: s = _("Romulan"); break;
706     case IHK: s = _("Klingon"); break;
707     case IHC: s = _("Commander"); break;
708     case IHS: s = _("Super-commander"); break;
709     case IHSTAR: s = _("Star"); break;
710     case IHP: s = _("Planet"); break;
711     case IHB: s = _("Starbase"); break;
712     case IHBLANK: s = _("Black hole"); break;
713     case IHT: s = _("Tholian"); break;
714     case IHWEB: s = _("Tholian web"); break;
715     case IHQUEST: s = _("Stranger"); break;
716     case IHW: s = _("Inhabited World"); break;
717     default: s = "Unknown??"; break;
718     }
719     proutn(s);
720 }
721
722 char *cramlc(enum loctype key, coord w)
723 /* name a location */
724 {
725     static char buf[32];
726     buf[0] = '\0';
727     if (key == quadrant)
728         strcpy(buf, _("Quadrant "));
729     else if (key == sector)
730         strcpy(buf, _("Sector "));
731     sprintf(buf+strlen(buf), "%d - %d", w.x, w.y);
732     return buf;
733 }
734
735 void crmena(bool stars, feature enemy, enum loctype key, coord w) 
736 /* print an enemy and his location */
737 {
738     if (stars)
739         proutn("***");
740     cramen(enemy);
741     proutn(_(" at "));
742     proutn(cramlc(key, w));
743 }
744
745 void crmshp(void)
746 /* print our ship name */
747 {
748     char *s;
749     switch (game.ship) {
750     case IHE: s = _("Enterprise"); break;
751     case IHF: s = _("Faerie Queene"); break;
752     default:  s = "Ship???"; break;
753     }
754     proutn(s);
755 }
756
757 void stars(void)
758 /* print a line of stars */
759 {
760     prouts("******************************************************");
761     skip(1);
762 }
763
764 double expran(double avrage) 
765 {
766     return -avrage*log(1e-7 + Rand());
767 }
768
769 double Rand(void) 
770 {
771     return rand()/(1.0 + (double)RAND_MAX);
772 }
773
774 coord randplace(int size)
775 /* choose a random location */ 
776 {
777     coord w;
778     w.x = Rand()*(size*1.0) + 1.0;
779     w.y = Rand()*(size*1.0) + 1.0;
780     return w;
781 }
782
783 void chew(void)
784 {
785     linep = line;
786     *linep = 0;
787 }
788
789 void chew2(void) 
790 {
791     /* return IHEOL next time */
792     linep = line+1;
793     *linep = 0;
794 }
795
796 int scan(void) 
797 {
798     int i;
799     char *cp;
800
801     // Init result
802     aaitem = 0.0;
803     *citem = 0;
804
805     // Read a line if nothing here
806     if (*linep == 0) {
807         if (linep != line) {
808             chew();
809             return IHEOL;
810         }
811         cgetline(line, sizeof(line));
812         fflush(stdin);
813         if (curwnd==prompt_window){
814             clrscr();
815             setwnd(message_window);
816             clrscr();
817         }
818         linep = line;
819     }
820     // Skip leading white space
821     while (*linep == ' ') linep++;
822     // Nothing left
823     if (*linep == 0) {
824         chew();
825         return IHEOL;
826     }
827     if (isdigit(*linep) || *linep=='+' || *linep=='-' || *linep=='.') {
828         // treat as a number
829         i = 0;
830         if (sscanf(linep, "%lf%n", &aaitem, &i) < 1) {
831             linep = line; // Invalid numbers are ignored
832             *linep = 0;
833             return IHEOL;
834         }
835         else {
836             // skip to end
837             linep += i;
838             return IHREAL;
839         }
840     }
841     // Treat as alpha
842     cp = citem;
843     while (*linep && *linep!=' ') {
844         if ((cp - citem) < 9)
845             *cp++ = tolower(*linep);
846         linep++;
847     }
848     *cp = 0;
849     return IHALPHA;
850 }
851
852 bool ja(void)
853 /* yes-or-no confirmation */
854 {
855     chew();
856     for(;;) {
857         scan();
858         chew();
859         if (*citem == 'y')
860             return true;
861         if (*citem == 'n')
862             return false;
863         proutn(_("Please answer with \"y\" or \"n\": "));
864     }
865 }
866
867 void huh(void)
868 /* complain about unparseable input */
869 {
870     chew();
871     skip(1);
872     prout(_("Beg your pardon, Captain?"));
873 }
874
875 bool isit(char *s) 
876 /* compares s to citem and returns true if it matches to the length of s */
877 {
878     return strncasecmp(s, citem, max(1, strlen(citem))) == 0;
879 }
880
881 void debugme(void)
882 /* access to the internals for debugging */
883 {
884     proutn("Reset levels? ");
885     if (ja() == true) {
886         if (game.energy < game.inenrg)
887             game.energy = game.inenrg;
888         game.shield = game.inshld;
889         game.torps = game.intorps;
890         game.lsupres = game.inlsr;
891     }
892     proutn("Reset damage? ");
893     if (ja() == true) {
894         int i;
895         for (i=0; i < NDEVICES; i++) 
896             if (game.damage[i] > 0.0) 
897                 game.damage[i] = 0.0;
898     }
899     proutn("Toggle debug flag? ");
900     if (ja() == true) {
901         idebug = !idebug;
902         if (idebug)
903             prout("Debug output ON");       
904         else
905             prout("Debug output OFF");
906     }
907     proutn("Cause selective damage? ");
908     if (ja() == true) {
909         int i, key;
910         for (i=0; i < NDEVICES; i++) {
911             proutn("Kill ");
912             proutn(device[i]);
913             proutn("? ");
914             chew();
915             key = scan();
916             if (key == IHALPHA &&  isit("y")) {
917                 game.damage[i] = 10.0;
918             }
919         }
920     }
921     proutn("Examine/change events? ");
922     if (ja() == true) {
923         event *ev;
924         coord w;
925         int i;
926         for (i = 1; i < NEVENTS; i++) {
927             int key;
928             switch (i) {
929             case FSNOVA:  proutn("Supernova       "); break;
930             case FTBEAM:  proutn("T Beam          "); break;
931             case FSNAP:   proutn("Snapshot        "); break;
932             case FBATTAK: proutn("Base Attack     "); break;
933             case FCDBAS:  proutn("Base Destroy    "); break;
934             case FSCMOVE: proutn("SC Move         "); break;
935             case FSCDBAS: proutn("SC Base Destroy "); break;
936             case FDSPROB: proutn("Probe Move      "); break;
937             case FDISTR:  proutn("Distress Call   "); break;
938             case FENSLV:  proutn("Enlavement      "); break;
939             case FREPRO:  proutn("Klingon Build   "); break;
940             }
941             if (is_scheduled(i)) {
942                 proutn("%.2f", scheduled(i)-game.state.date);
943                 if (i == FENSLV || i == FREPRO) {
944                     ev = findevent(i);
945                     proutn(" in %d-%d", ev->quadrant.x,ev->quadrant.y);
946                 }
947             } else
948                 proutn("never");
949             proutn("? ");
950             chew();
951             key = scan();
952             if (key == 'n') {
953                 unschedule(i);
954                 chew();
955             } else if (key == IHREAL) {
956                 ev = schedule(i, aaitem);
957                 if (i == FENSLV || i == FREPRO) {
958                     chew();
959                     proutn("In quadrant- ");
960                     key = scan();
961                     /* IHEOL says to leave coordinates as they are */
962                     if (key != IHEOL) {
963                         if (key != IHREAL) {
964                             prout("Event %d canceled, no x coordinate.", i);
965                             unschedule(i);
966                             continue;
967                         }
968                         w.x = (int)aaitem;
969                         key = scan();
970                         if (key != IHREAL) {
971                             prout("Event %d canceled, no y coordinate.", i);
972                             unschedule(i);
973                             continue;
974                         }
975                         w.y = (int)aaitem;
976                         ev->quadrant = w;
977                     }
978                 }
979             }
980         }
981         chew();
982     }
983     proutn("Induce supernova here? ");
984     if (ja() == true) {
985         game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova = true;
986         atover(true);
987     }
988 }