ae8298407bece129a510c20c444e2c83675cdcc4
[super-star-trek.git] / 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)
587                 break;          // Game has ended
588             if (game.optime != 0.0) {
589                 events();
590                 if (game.alldone)
591                     break;      // Events did us in
592             }
593             if (game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova) { // Galaxy went Nova!
594                 atover(false);
595                 continue;
596             }
597             if (hitme && !game.justin) {
598                 attack(true);
599                 if (game.alldone)
600                     break;
601                 if (game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova) {    // went NOVA! 
602                     atover(false);
603                     hitme = true;
604                     continue;
605                 }
606             }
607             break;
608         }
609         if (game.alldone)
610             break;
611     }
612     if (idebug)
613         prout("=== Ending");
614 }
615
616
617 int main(int argc, char **argv) 
618 {
619     int i, option;
620
621     game.options = OPTION_ALL &~ (OPTION_IOMODES | OPTION_SHOWME | OPTION_PLAIN | OPTION_ALMY);
622     if (getenv("TERM"))
623         game.options |= OPTION_CURSES | OPTION_SHOWME;
624     else
625         game.options |= OPTION_TTY;
626
627     seed = (int)time(NULL);
628     while ((option = getopt(argc, argv, "r:tx")) != -1) {
629         switch (option) {
630         case 'r':
631             replayfp = fopen(optarg, "r");
632             if (replayfp == NULL) {
633                 fprintf(stderr, "sst: can't open replay file %s\n", optarg);
634                 exit(1);
635             }
636             if (fscanf(replayfp, "seed %d\n", &seed) != 1) {
637                 fprintf(stderr, "sst: replay file %s is ill-formed\n", optarg);
638                 exit(1);
639             }
640             /* FALL THROUGH */
641         case 't':
642             game.options |= OPTION_TTY;
643             game.options &=~ OPTION_CURSES;
644             break;
645         case 'x':
646             idebug = true;
647             break;
648         default:
649             fprintf(stderr, "usage: sst [-t] [-x] [startcommand...].\n");
650             exit(0);
651         }
652     }
653     /* where to save the input in case of bugs */
654     logfp = fopen("/usr/tmp/sst-input.log", "w");
655     if (logfp) {
656         setlinebuf(logfp);
657         fprintf(logfp, "seed %d\n", seed);
658     }
659     srand(seed);
660
661     iostart();
662
663     line[0] = '\0';
664     for (i = optind; i < argc;  i++) {
665         strcat(line, argv[i]);
666         strcat(line, " ");
667     }
668     for(;;) { /* Play a game */
669         setwnd(fullscreen_window);
670         clrscr();
671         prelim();
672         setup(line[0] == '\0');
673         if (game.alldone) {
674             score();
675             game.alldone = false;
676         }
677         else
678             makemoves();
679         skip(1);
680         stars();
681         skip(1);
682
683         if (game.tourn && game.alldone) {
684             proutn(_("Do you want your score recorded?"));
685             if (ja() == true) {
686                 chew2();
687                 freeze(false);
688             }
689         }
690         proutn(_("Do you want to play again? "));
691         if (!ja())
692             break;
693     }
694     skip(1);
695     prout(_("May the Great Bird of the Galaxy roost upon your home planet."));
696     return 0;
697 }
698
699
700 void cramen(feature i) 
701 /* print the name of an enemy */
702 {
703     /* return an enemy */
704     char *s;
705         
706     switch (i) {
707     case IHR: s = _("Romulan"); break;
708     case IHK: s = _("Klingon"); break;
709     case IHC: s = _("Commander"); break;
710     case IHS: s = _("Super-commander"); break;
711     case IHSTAR: s = _("Star"); break;
712     case IHP: s = _("Planet"); break;
713     case IHB: s = _("Starbase"); break;
714     case IHBLANK: s = _("Black hole"); break;
715     case IHT: s = _("Tholian"); break;
716     case IHWEB: s = _("Tholian web"); break;
717     case IHQUEST: s = _("Stranger"); break;
718     case IHW: s = _("Inhabited World"); break;
719     default: s = "Unknown??"; break;
720     }
721     proutn(s);
722 }
723
724 char *cramlc(enum loctype key, coord w)
725 /* name a location */
726 {
727     static char buf[32];
728     buf[0] = '\0';
729     if (key == quadrant)
730         strcpy(buf, _("Quadrant "));
731     else if (key == sector)
732         strcpy(buf, _("Sector "));
733     sprintf(buf+strlen(buf), "%d - %d", w.x, w.y);
734     return buf;
735 }
736
737 void crmena(bool stars, feature enemy, enum loctype key, coord w) 
738 /* print an enemy and his location */
739 {
740     if (stars)
741         proutn("***");
742     cramen(enemy);
743     proutn(_(" at "));
744     proutn(cramlc(key, w));
745 }
746
747 void crmshp(void)
748 /* print our ship name */
749 {
750     char *s;
751     switch (game.ship) {
752     case IHE: s = _("Enterprise"); break;
753     case IHF: s = _("Faerie Queene"); break;
754     default:  s = "Ship???"; break;
755     }
756     proutn(s);
757 }
758
759 void stars(void)
760 /* print a line of stars */
761 {
762     prouts("******************************************************");
763     skip(1);
764 }
765
766 double expran(double avrage) 
767 {
768     return -avrage*log(1e-7 + Rand());
769 }
770
771 double Rand(void) 
772 {
773     return rand()/(1.0 + (double)RAND_MAX);
774 }
775
776 coord randplace(int size)
777 /* choose a random location */ 
778 {
779     coord w;
780     w.x = Rand()*(size*1.0) + 1.0;
781     w.y = Rand()*(size*1.0) + 1.0;
782     return w;
783 }
784
785 void chew(void)
786 {
787     linep = line;
788     *linep = 0;
789 }
790
791 void chew2(void) 
792 {
793     /* return IHEOL next time */
794     linep = line+1;
795     *linep = 0;
796 }
797
798 int scan(void) 
799 {
800     int i;
801     char *cp;
802
803     // Init result
804     aaitem = 0.0;
805     *citem = 0;
806
807     // Read a line if nothing here
808     if (*linep == 0) {
809         if (linep != line) {
810             chew();
811             return IHEOL;
812         }
813         cgetline(line, sizeof(line));
814         fflush(stdin);
815         if (curwnd==prompt_window){
816             clrscr();
817             setwnd(message_window);
818             clrscr();
819         }
820         linep = line;
821     }
822     // Skip leading white space
823     while (*linep == ' ') linep++;
824     // Nothing left
825     if (*linep == 0) {
826         chew();
827         return IHEOL;
828     }
829     if (isdigit(*linep) || *linep=='+' || *linep=='-' || *linep=='.') {
830         // treat as a number
831         i = 0;
832         if (sscanf(linep, "%lf%n", &aaitem, &i) < 1) {
833             linep = line; // Invalid numbers are ignored
834             *linep = 0;
835             return IHEOL;
836         }
837         else {
838             // skip to end
839             linep += i;
840             return IHREAL;
841         }
842     }
843     // Treat as alpha
844     cp = citem;
845     while (*linep && *linep!=' ') {
846         if ((cp - citem) < 9)
847             *cp++ = tolower(*linep);
848         linep++;
849     }
850     *cp = 0;
851     return IHALPHA;
852 }
853
854 bool ja(void)
855 /* yes-or-no confirmation */
856 {
857     chew();
858     for(;;) {
859         scan();
860         chew();
861         if (*citem == 'y')
862             return true;
863         if (*citem == 'n')
864             return false;
865         proutn(_("Please answer with \"y\" or \"n\": "));
866     }
867 }
868
869 void huh(void)
870 /* complain about unparseable input */
871 {
872     chew();
873     skip(1);
874     prout(_("Beg your pardon, Captain?"));
875 }
876
877 bool isit(char *s) 
878 /* compares s to citem and returns true if it matches to the length of s */
879 {
880     return strncasecmp(s, citem, max(1, strlen(citem))) == 0;
881 }
882
883 void debugme(void)
884 /* access to the internals for debugging */
885 {
886     proutn("Reset levels? ");
887     if (ja() == true) {
888         if (game.energy < game.inenrg)
889             game.energy = game.inenrg;
890         game.shield = game.inshld;
891         game.torps = game.intorps;
892         game.lsupres = game.inlsr;
893     }
894     proutn("Reset damage? ");
895     if (ja() == true) {
896         int i;
897         for (i=0; i < NDEVICES; i++) 
898             if (game.damage[i] > 0.0) 
899                 game.damage[i] = 0.0;
900     }
901     proutn("Toggle debug flag? ");
902     if (ja() == true) {
903         idebug = !idebug;
904         if (idebug)
905             prout("Debug output ON");       
906         else
907             prout("Debug output OFF");
908     }
909     proutn("Cause selective damage? ");
910     if (ja() == true) {
911         int i, key;
912         for (i=0; i < NDEVICES; i++) {
913             proutn("Kill ");
914             proutn(device[i]);
915             proutn("? ");
916             chew();
917             key = scan();
918             if (key == IHALPHA &&  isit("y")) {
919                 game.damage[i] = 10.0;
920             }
921         }
922     }
923     proutn("Examine/change events? ");
924     if (ja() == true) {
925         event *ev;
926         coord w;
927         int i;
928         for (i = 1; i < NEVENTS; i++) {
929             int key;
930             switch (i) {
931             case FSNOVA:  proutn("Supernova       "); break;
932             case FTBEAM:  proutn("T Beam          "); break;
933             case FSNAP:   proutn("Snapshot        "); break;
934             case FBATTAK: proutn("Base Attack     "); break;
935             case FCDBAS:  proutn("Base Destroy    "); break;
936             case FSCMOVE: proutn("SC Move         "); break;
937             case FSCDBAS: proutn("SC Base Destroy "); break;
938             case FDSPROB: proutn("Probe Move      "); break;
939             case FDISTR:  proutn("Distress Call   "); break;
940             case FENSLV:  proutn("Enlavement      "); break;
941             case FREPRO:  proutn("Klingon Build   "); break;
942             }
943             if (is_scheduled(i)) {
944                 proutn("%.2f", scheduled(i)-game.state.date);
945                 if (i == FENSLV || i == FREPRO) {
946                     ev = findevent(i);
947                     proutn(" in %d-%d", ev->quadrant.x,ev->quadrant.y);
948                 }
949             } else
950                 proutn("never");
951             proutn("? ");
952             chew();
953             key = scan();
954             if (key == 'n') {
955                 unschedule(i);
956                 chew();
957             } else if (key == IHREAL) {
958                 ev = schedule(i, aaitem);
959                 if (i == FENSLV || i == FREPRO) {
960                     chew();
961                     proutn("In quadrant- ");
962                     key = scan();
963                     /* IHEOL says to leave coordinates as they are */
964                     if (key != IHEOL) {
965                         if (key != IHREAL) {
966                             prout("Event %d canceled, no x coordinate.", i);
967                             unschedule(i);
968                             continue;
969                         }
970                         w.x = (int)aaitem;
971                         key = scan();
972                         if (key != IHREAL) {
973                             prout("Event %d canceled, no y coordinate.", i);
974                             unschedule(i);
975                             continue;
976                         }
977                         w.y = (int)aaitem;
978                         ev->quadrant = w;
979                     }
980                 }
981             }
982         }
983         chew();
984     }
985     proutn("Induce supernova here? ");
986     if (ja() == true) {
987         game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova = true;
988         atover(true);
989     }
990 }