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