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