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