Parallel cleanups in the C and scratch Python code.
[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) return;
322         for (i = 0; i < NUMCOMMANDS; i++) {
323             if (ACCEPT(i) && strcasecmp(commands[i].name, citem)==0) {
324                 i = commands[i].value;
325                 break;
326             }
327         }
328         if (i != NUMCOMMANDS) break;
329         skip(1);
330         listCommands();
331         key = IHEOL;
332         chew();
333         skip(1);
334     }
335     if (i == COMMANDS) {
336         strcpy(cmdbuf, " ABBREV");
337     }
338     else {
339         for (j = 0; commands[i].name[j]; j++)
340             cmdbuf[j] = toupper(commands[i].name[j]);
341         cmdbuf[j] = '\0';
342     }
343     fp = fopen(SSTDOC, "r");
344     if (fp == NULL)
345         fp = fopen(DOC_NAME, "r");
346     if (fp == NULL) {
347         prout(_("Spock-  \"Captain, that information is missing from the"));
348         proutn(_("   computer. You need to find "));
349         proutn(DOC_NAME);
350         prout(_(" and put it in the"));
351         proutn(_("   current directory or to "));
352         proutn(SSTDOC);
353         prout(".\"");
354         /*
355          * This used to continue: "You need to find SST.DOC and put 
356          * it in the current directory."
357          */
358         return;
359     }
360     for (;;) {
361         if (fgets(linebuf, sizeof(linebuf), fp) == NULL) {
362             prout(_("Spock- \"Captain, there is no information on that command.\""));
363             fclose(fp);
364             return;
365         }
366         if (linebuf[0] == '%' && linebuf[1] == '%'&& linebuf[2] == ' ') {
367             for (cp = linebuf+3; isspace(*cp); cp++)
368                 continue;
369             linebuf[strlen(linebuf)-1] = '\0';
370             if (strcasecmp(cp, cmdbuf) == 0)
371                 break;
372         }
373     }
374
375     skip(1);
376     prout(_("Spock- \"Captain, I've found the following information:\""));
377     skip(1);
378
379     while (fgets(linebuf, sizeof(linebuf), fp)) {
380         char *eol;
381         if (strstr(linebuf, "******"))
382             break;
383         if ((eol = strpbrk(linebuf, "\r\n")))
384             *eol = 0;
385         prout(linebuf);
386     }
387     fclose(fp);
388 }
389
390 static void makemoves(void)
391 /* command-interpretation loop */
392 {
393     int key, i, v = 0;
394     bool hitme;
395     clrscr();
396     setwnd(message_window);
397     for(;;) { /* command loop */
398         drawmaps(1);
399         for(;;)  { /* get a command */
400             hitme = false;
401             game.justin = false;
402             game.optime = 0.0;
403             i = -1;
404             chew();
405             setwnd(prompt_window);
406             clrscr();
407             proutn("COMMAND> ");
408             if (scan() == IHEOL) {
409                 if (game.options & OPTION_CURSES)
410                     makechart();
411                 continue;
412             }
413             game.ididit = false;
414             clrscr();
415             setwnd(message_window);
416             clrscr();
417             for (i=0; i < ABANDON; i++)
418                 if (ACCEPT(i) && isit(commands[i].name)) {
419                     v = commands[i].value;
420                     break;
421                 }
422             if (i < ABANDON && (!commands[i].option || (commands[i].option & game.options))) 
423                 break;
424             for (; i < NUMCOMMANDS; i++)
425                 if (ACCEPT(i) && strcasecmp(commands[i].name, citem) == 0) {
426                     v = commands[i].value;
427                     break;
428                 }
429             if (i < NUMCOMMANDS && (!commands[i].option || (commands[i].option & game.options))) 
430                 break;
431             listCommands();
432         }
433         commandhook(commands[i].name, true);
434         switch (v) { /* command switch */
435         case SRSCAN:                 // srscan
436             srscan();
437             break;
438         case STATUS:                 // status
439             status(0);
440             break;
441         case REQUEST:                   // status request 
442             request();
443             break;
444         case LRSCAN:                    // lrscan
445             lrscan();
446             break;
447         case PHASERS:                   // phasers
448             phasers();
449             if (game.ididit) hitme = true;
450             break;
451         case TORPEDO:                   // photons
452             photon();
453             if (game.ididit) hitme = true;
454             break;
455         case MOVE:                      // move
456             warp(false);
457             break;
458         case SHIELDS:                   // shields
459             doshield(false);
460             if (game.ididit) {
461                 hitme = true;
462                 game.shldchg = false;
463             }
464             break;
465         case DOCK:                      // dock
466             dock(true);
467             if (game.ididit) attack(false);
468             break;
469         case DAMAGES:                   // damages
470             dreprt();
471             break;
472         case CHART:                     // chart
473             makechart();
474             break;
475         case IMPULSE:                   // impulse
476             impuls();
477             break;
478         case REST:                      // rest
479             wait();
480             if (game.ididit) hitme = true;
481             break;
482         case WARP:                      // warp
483             setwrp();
484             break;
485         case SCORE:                     // score
486             score();
487             break;
488         case SENSORS:                   // sensors
489             sensor();
490             break;
491         case ORBIT:                     // orbit
492             orbit();
493             if (game.ididit) hitme = true;
494             break;
495         case TRANSPORT:                 // transport "beam"
496             beam();
497             break;
498         case MINE:                      // mine
499             mine();
500             if (game.ididit) hitme = true;
501             break;
502         case CRYSTALS:                  // crystals
503             usecrystals();
504             if (game.ididit) hitme = true;
505             break;
506         case SHUTTLE:                   // shuttle
507             shuttle();
508             if (game.ididit) hitme = true;
509             break;
510         case PLANETS:                   // Planet list
511             preport();
512             break;
513         case REPORT:                    // Game Report 
514             report();
515             break;
516         case COMPUTER:                  // use COMPUTER!
517             eta();
518             break;
519         case COMMANDS:
520             listCommands();
521             break;
522         case EMEXIT:                    // Emergency exit
523             clrscr();                   // Hide screen
524             freeze(true);               // forced save
525             exit(1);                    // And quick exit
526             break;
527         case PROBE:
528             probe();                    // Launch probe
529             if (game.ididit) hitme = true;
530             break;
531         case ABANDON:                   // Abandon Ship
532             abandn();
533             break;
534         case DESTRUCT:                  // Self Destruct
535             selfdestruct();
536             break;
537         case SAVE:                      // Save Game
538             freeze(false);
539             clrscr();
540             if (game.skill > SKILL_GOOD)
541                 prout(_("WARNING--Saved games produce no plaques!"));
542             break;
543         case DEATHRAY:                  // Try a desparation measure
544             deathray();
545             if (game.ididit) hitme = true;
546             break;
547         case DEBUGCMD:                  // What do we want for debug???
548             debugme();
549             break;
550         case MAYDAY:                    // Call for help
551             mayday();
552             if (game.ididit) hitme = true;
553             break;
554         case QUIT:
555             game.alldone = true;                // quit the game
556             break;
557         case HELP:
558             helpme();                   // get help
559             break;
560         case SEED:                      // set random-number seed
561             key = scan();
562             if (key == IHREAL)
563                 seed = (int)aaitem;
564             break;
565 #if BSD_BUG_FOR_BUG
566         case VISUAL:
567             visual();                   // perform visual scan
568             break;
569 #endif
570         }
571         commandhook(commands[i].name, false);
572         for (;;) {
573             if (game.alldone) break;            // Game has ended
574             if (game.optime != 0.0) {
575                 events();
576                 if (game.alldone) break;        // Events did us in
577             }
578             if (game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova) { // Galaxy went Nova!
579                 atover(false);
580                 continue;
581             }
582             if (hitme && !game.justin) {
583                 attack(true);
584                 if (game.alldone) break;
585                 if (game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova) {    // went NOVA! 
586                     atover(false);
587                     hitme = true;
588                     continue;
589                 }
590             }
591             break;
592         }
593         if (game.alldone) break;
594     }
595     if (idebug) prout("=== Ending");
596 }
597
598
599 int main(int argc, char **argv) 
600 {
601     int i, option;
602
603     game.options = OPTION_ALL &~ (OPTION_IOMODES | OPTION_SHOWME | OPTION_PLAIN | OPTION_ALMY);
604     if (getenv("TERM"))
605         game.options |= OPTION_CURSES | OPTION_SHOWME;
606     else
607         game.options |= OPTION_TTY;
608
609     seed = (int)time(NULL);
610     while ((option = getopt(argc, argv, "r:tx")) != -1) {
611         switch (option) {
612         case 'r':
613             replayfp = fopen(optarg, "r");
614             if (replayfp == NULL) {
615                 fprintf(stderr, "sst: can't open replay file %s\n", optarg);
616                 exit(1);        
617             }
618             if (fscanf(replayfp, "seed %d\n", &seed) != 1) {
619                 fprintf(stderr, "sst: replay file %s is ill-formed\n", optarg);
620                 exit(1);        
621             }
622             /* FALL THROUGH */
623         case 't':
624             game.options |= OPTION_TTY;
625             game.options &=~ OPTION_CURSES;
626             break;
627         case 'x':
628             idebug = true;
629             break;
630         default:
631             fprintf(stderr, "usage: sst [-t] [-x] [startcommand...].\n");
632             exit(0);
633         }
634     }
635     /* where to save the input in case of bugs */
636     logfp = fopen("/usr/tmp/sst-input.log", "w");
637     if (logfp) {
638         setlinebuf(logfp);
639         fprintf(logfp, "seed %d\n", seed);
640     }
641     srand(seed);
642
643     iostart();
644
645     line[0] = '\0';
646     for (i = optind; i < argc;  i++) {
647         strcat(line, argv[i]);
648         strcat(line, " ");
649     }
650     for(;;) { /* Play a game */
651         setwnd(fullscreen_window);
652         clrscr();
653         prelim();
654         setup(line[0] == '\0');
655         if (game.alldone) {
656             score();
657             game.alldone = false;
658         }
659         else makemoves();
660         skip(1);
661         stars();
662         skip(1);
663
664         if (game.tourn && game.alldone) {
665             proutn(_("Do you want your score recorded?"));
666             if (ja() == true) {
667                 chew2();
668                 freeze(false);
669             }
670         }
671         proutn(_("Do you want to play again? "));
672         if (!ja()) break;
673     }
674     skip(1);
675     prout(_("May the Great Bird of the Galaxy roost upon your home planet."));
676     return 0;
677 }
678
679
680 void cramen(feature i) 
681 /* print the name of an enemy */
682 {
683     /* return an enemy */
684     char *s;
685         
686     switch (i) {
687     case IHR: s = _("Romulan"); break;
688     case IHK: s = _("Klingon"); break;
689     case IHC: s = _("Commander"); break;
690     case IHS: s = _("Super-commander"); break;
691     case IHSTAR: s = _("Star"); break;
692     case IHP: s = _("Planet"); break;
693     case IHB: s = _("Starbase"); break;
694     case IHBLANK: s = _("Black hole"); break;
695     case IHT: s = _("Tholian"); break;
696     case IHWEB: s = _("Tholian web"); break;
697     case IHQUEST: s = _("Stranger"); break;
698     case IHW: s = _("Inhabited World"); break;
699     default: s = "Unknown??"; break;
700     }
701     proutn(s);
702 }
703
704 char *cramlc(enum loctype key, coord w)
705 /* name a location */
706 {
707     static char buf[32];
708     buf[0] = '\0';
709     if (key == quadrant) strcpy(buf, _("Quadrant "));
710     else if (key == sector) strcpy(buf, _("Sector "));
711     sprintf(buf+strlen(buf), "%d - %d", w.x, w.y);
712     return buf;
713 }
714
715 void crmena(bool stars, feature enemy, enum loctype key, coord w) 
716 /* print an enemy and his location */
717 {
718     if (stars) proutn("***");
719     cramen(enemy);
720     proutn(_(" at "));
721     proutn(cramlc(key, w));
722 }
723
724 void crmshp(void)
725 /* print our ship name */
726 {
727     char *s;
728     switch (game.ship) {
729     case IHE: s = _("Enterprise"); break;
730     case IHF: s = _("Faerie Queene"); break;
731     default:  s = "Ship???"; break;
732     }
733     proutn(s);
734 }
735
736 void stars(void)
737 /* print a line of stars */
738 {
739     prouts("******************************************************");
740     skip(1);
741 }
742
743 double expran(double avrage) 
744 {
745     return -avrage*log(1e-7 + Rand());
746 }
747
748 double Rand(void) 
749 {
750     return rand()/(1.0 + (double)RAND_MAX);
751 }
752
753 coord randplace(int size)
754 /* choose a random location */ 
755 {
756     coord w;
757     w.x = Rand()*(size*1.0) + 1.0;
758     w.y = Rand()*(size*1.0) + 1.0;
759     return w;
760 }
761
762 void chew(void)
763 {
764     linep = line;
765     *linep = 0;
766 }
767
768 void chew2(void) 
769 {
770     /* return IHEOL next time */
771     linep = line+1;
772     *linep = 0;
773 }
774
775 int scan(void) 
776 {
777     int i;
778     char *cp;
779
780     // Init result
781     aaitem = 0.0;
782     *citem = 0;
783
784     // Read a line if nothing here
785     if (*linep == 0) {
786         if (linep != line) {
787             chew();
788             return IHEOL;
789         }
790         cgetline(line, sizeof(line));
791         fflush(stdin);
792         if (curwnd==prompt_window){
793             clrscr();
794             setwnd(message_window);
795             clrscr();
796         }
797         linep = line;
798     }
799     // Skip leading white space
800     while (*linep == ' ') linep++;
801     // Nothing left
802     if (*linep == 0) {
803         chew();
804         return IHEOL;
805     }
806     if (isdigit(*linep) || *linep=='+' || *linep=='-' || *linep=='.') {
807         // treat as a number
808         i = 0;
809         if (sscanf(linep, "%lf%n", &aaitem, &i) < 1) {
810             linep = line; // Invalid numbers are ignored
811             *linep = 0;
812             return IHEOL;
813         }
814         else {
815             // skip to end
816             linep += i;
817             return IHREAL;
818         }
819     }
820     // Treat as alpha
821     cp = citem;
822     while (*linep && *linep!=' ') {
823         if ((cp - citem) < 9) *cp++ = tolower(*linep);
824         linep++;
825     }
826     *cp = 0;
827     return IHALPHA;
828 }
829
830 bool ja(void)
831 /* yes-or-no confirmation */
832 {
833     chew();
834     for(;;) {
835         scan();
836         chew();
837         if (*citem == 'y') return true;
838         if (*citem == 'n') return false;
839         proutn(_("Please answer with \"y\" or \"n\": "));
840     }
841 }
842
843 void huh(void)
844 /* complain about unparseable input */
845 {
846     chew();
847     skip(1);
848     prout(_("Beg your pardon, Captain?"));
849 }
850
851 bool isit(char *s) 
852 /* compares s to citem and returns true if it matches to the length of s */
853 {
854     return strncasecmp(s, citem, max(1, strlen(citem))) == 0;
855 }
856
857 void debugme(void)
858 /* access to the internals for debugging */
859 {
860     proutn("Reset levels? ");
861     if (ja() == true) {
862         if (game.energy < game.inenrg) game.energy = game.inenrg;
863         game.shield = game.inshld;
864         game.torps = game.intorps;
865         game.lsupres = game.inlsr;
866     }
867     proutn("Reset damage? ");
868     if (ja() == true) {
869         int i;
870         for (i=0; i < NDEVICES; i++) 
871             if (game.damage[i] > 0.0) 
872                 game.damage[i] = 0.0;
873     }
874     proutn("Toggle debug flag? ");
875     if (ja() == true) {
876         idebug = !idebug;
877         if (idebug) prout("Debug output ON");
878         else prout("Debug output OFF");
879     }
880     proutn("Cause selective damage? ");
881     if (ja() == true) {
882         int i, key;
883         for (i=0; i < NDEVICES; i++) {
884             proutn("Kill ");
885             proutn(device[i]);
886             proutn("? ");
887             chew();
888             key = scan();
889             if (key == IHALPHA &&  isit("y")) {
890                 game.damage[i] = 10.0;
891             }
892         }
893     }
894     proutn("Examine/change events? ");
895     if (ja() == true) {
896         event *ev;
897         coord w;
898         int i;
899         for (i = 1; i < NEVENTS; i++) {
900             int key;
901             switch (i) {
902             case FSNOVA:  proutn("Supernova       "); break;
903             case FTBEAM:  proutn("T Beam          "); break;
904             case FSNAP:   proutn("Snapshot        "); break;
905             case FBATTAK: proutn("Base Attack     "); break;
906             case FCDBAS:  proutn("Base Destroy    "); break;
907             case FSCMOVE: proutn("SC Move         "); break;
908             case FSCDBAS: proutn("SC Base Destroy "); break;
909             case FDSPROB: proutn("Probe Move      "); break;
910             case FDISTR:  proutn("Distress Call   "); break;
911             case FENSLV:  proutn("Enlavement      "); break;
912             case FREPRO:  proutn("Klingon Build   "); break;
913             }
914             if (is_scheduled(i)) {
915                 proutn("%.2f", scheduled(i)-game.state.date);
916                 if (i == FENSLV || i == FREPRO) {
917                     ev = findevent(i);
918                     proutn(" in %d-%d", ev->quadrant.x,ev->quadrant.y);
919                 }
920             } else
921                 proutn("never");
922             proutn("? ");
923             chew();
924             key = scan();
925             if (key == 'n') {
926                 unschedule(i);
927                 chew();
928             } else if (key == IHREAL) {
929                 ev = schedule(i, aaitem);
930                 if (i == FENSLV || i == FREPRO) {
931                     chew();
932                     proutn("In quadrant- ");
933                     key = scan();
934                     /* IHEOL says to leave coordinates as they are */
935                     if (key != IHEOL) {
936                         if (key != IHREAL) {
937                             prout("Event %d canceled, no x coordinate.", i);
938                             unschedule(i);
939                             continue;
940                         }
941                         w.x = (int)aaitem;
942                         key = scan();
943                         if (key != IHREAL) {
944                             prout("Event %d canceled, no y coordinate.", i);
945                             unschedule(i);
946                             continue;
947                         }
948                         w.y = (int)aaitem;
949                         ev->quadrant = w;
950                     }
951                 }
952             }
953         }
954         chew();
955     }
956     proutn("Induce supernova here? ");
957     if (ja() == true) {
958         game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova = true;
959         atover(true);
960     }
961 }