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