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