Rewrite to get ride of FORTRANish galaxy and newstuff arrays. It's
[super-star-trek.git] / sst.c
1 #define INCLUDED        // Define externs here
2 #include <ctype.h>
3 #include <getopt.h>
4 #include <time.h>
5 #include "sstlinux.h"
6 #include "sst.h"
7
8 #ifndef SSTDOC
9 #define SSTDOC  "sst.doc"
10 #endif
11         
12 /*
13
14 Dave Matuszek says:
15
16    SRSCAN, MOVE, PHASERS, CALL, STATUS, IMPULSE, PHOTONS, ABANDON,
17    LRSCAN, WARP, SHIELDS, DESTRUCT, CHART, REST, DOCK, QUIT, and DAMAGE
18    were in the original non-"super" version.
19
20    Tholians weren't in the original. Dave is dubious about their merits.
21
22    Planets and dilithium crystals weren't in the original.  Dave is OK
23    with this idea. He says the bit about the Galileo getting
24    turned into a McDonald's is "consistant with our original vision".
25
26    Dave also says the Space Thingy should not be preserved across saved
27    games, so you can't prove to others that you've seen it.  He says it
28    shouldn't fire back, either.  It should do nothing except scream and
29    disappear when hit by photon torpedos.  It's OK that it may move
30    when attacked, but it didn't in the original.
31
32    The Faerie Queen, black holes, and time warping were in the original.
33
34 Here are Tom Almy's changes:
35
36    Compared to original version, I've changed the "help" command to
37    "call" and the "terminate" command to "quit" to better match
38    user expectations. The DECUS version apparently made those changes
39    as well as changing "freeze" to "save". However I like "freeze".
40
41    When I got a later version of Super Star Trek that I was converting
42    from, I added the emexit command.
43
44    That later version also mentions srscan and lrscan working when
45    docked (using the starbase's scanners), so I made some changes here
46    to do this (and indicating that fact to the player), and then realized
47    the base would have a subspace radio as well -- doing a Chart when docked
48    updates the star chart, and all radio reports will be heard. The Dock
49    command will also give a report if a base is under attack.
50
51    Movecom no longer reports movement if sensors are damaged so you wouldn't
52    otherwise know it.
53
54    Also added:
55
56    1. Better base positioning at startup
57
58    2. Deathray improvement (but keeping original failure alternatives)
59
60    3. Tholian Web.
61
62    4. Enemies can ram the Enterprise. Regular Klingons and Romulans can
63       move in Expert and Emeritus games. This code could use improvement.
64
65    5. The deep space probe looks interesting! DECUS version
66
67    6. Perhaps cloaking to be added later? BSD version
68
69 Here are Stas Sergeev's changes:
70
71    1. The Space Thingy can be shoved, if you ram it, and can fire back if 
72       fired upon.
73
74    2. The Tholian can be hit with phasers
75
76    3. When you are docked, base covers you with an almost invincible shields 
77       (a commander can still ram you, or a Romulan can destroy the base,
78       or a SCom can even succeed with direct attack IIRC, but this rarely 
79       happens).
80
81    4. SCom can't escape from you if no more enemies remain (without this, 
82       chasing SCom can take an eternity).
83
84    5. Probe target you enter is now the destination quadrant. Before I don't 
85       remember what it was, but it was something I had difficulty using)
86
87    6. Secret password is now autogenerated.
88
89    7. "Plaque" is adjusted for A4 paper :-)
90
91    8. Phasers now tells you how much energy needed, but only if the computer 
92        is alive.
93
94    9. Planets are auto-scanned when you enter the quadrant.
95
96    10. Mining or using crystals in presense of enemy now yields an attack.
97        There are other minor adjustments to what yields an attack
98        and what does not.
99
100    11. Ramming a black hole is no longer instant death.  There is a
101        chance you might get timewarped instead.
102
103    12. "freeze" command reverts to "save", most people will understand this
104         better anyway.
105
106    13. Screen-oriented interface, with sensor scans always up.
107
108 Eric Raymond's changes:
109
110  Mainly, I translated this C code out of FORTRAN into C -- created #defines
111    for a lot of magic numbers and refactored the heck out of it.
112
113    1. "sos" and "call" becomes "mayday", "freeze" and "save" are both good.
114
115    2. Status report now indicates when dilithium crystals are on board.
116
117    3. Per Dave Matuszek's remarks, Thingy state is not saved across games.
118    */
119
120 /* the input queue */
121 static char line[128], *linep = line;
122
123 static struct 
124 {
125     char *name;
126     int value;
127     unsigned long option;
128 }
129 commands[] = {
130 #define SRSCAN  0
131         {"SRSCAN",      SRSCAN,         OPTION_TTY},
132 #define STATUS  1
133         {"STATUS",      STATUS,         OPTION_TTY},
134 #define REQUEST 2
135         {"REQUEST",     REQUEST,        OPTION_TTY},
136 #define LRSCAN  3
137         {"LRSCAN",      LRSCAN,         OPTION_TTY},
138 #define PHASERS 4
139         {"PHASERS",     PHASERS,        0},
140 #define TORPEDO 5
141         {"TORPEDO",     TORPEDO,        0},
142         {"PHOTONS",     TORPEDO,        0},
143 #define MOVE    6
144         {"MOVE",        MOVE,           0},
145 #define SHIELDS 7
146         {"SHIELDS",     SHIELDS,        0},
147 #define DOCK    8
148         {"DOCK",        DOCK,           0},
149 #define DAMAGES 9
150         {"DAMAGES",     DAMAGES,        0},
151 #define CHART   10
152         {"CHART",       CHART,          0},
153 #define IMPULSE 11
154         {"IMPULSE",     IMPULSE,        0},
155 #define REST    12
156         {"REST",        REST,           0},
157 #define WARP    13
158         {"WARP",        WARP,           0},
159 #define SCORE   14
160         {"SCORE",       SCORE,          0},
161 #define SENSORS 15
162         {"SENSORS",     SENSORS,        OPTION_PLANETS},
163 #define ORBIT   16
164         {"ORBIT",       ORBIT,          OPTION_PLANETS},
165 #define TRANSPORT       17
166         {"TRANSPORT",   TRANSPORT,      OPTION_PLANETS},
167 #define MINE    18
168         {"MINE",        MINE,           OPTION_PLANETS},
169 #define CRYSTALS        19
170         {"CRYSTALS",    CRYSTALS,       OPTION_PLANETS},
171 #define SHUTTLE 20
172         {"SHUTTLE",     SHUTTLE,        OPTION_PLANETS},
173 #define PLANETS 21
174         {"PLANETS",     PLANETS,        OPTION_PLANETS},
175 #define REPORT  22
176         {"REPORT",      REPORT,         0},
177 #define COMPUTER        23
178         {"COMPUTER",    COMPUTER,       0},
179 #define COMMANDS        24
180         {"COMMANDS",    COMMANDS,       0},
181 #define EMEXIT  25
182         {"EMEXIT",      EMEXIT,         0},
183 #define PROBE   26
184         {"PROBE",       PROBE,          OPTION_PROBE},
185 #define SAVE    27
186         {"SAVE",        SAVE,           0},
187         {"FREEZE",      SAVE,           0},
188 #define ABANDON 28
189         {"ABANDON",     ABANDON,        0},
190 #define DESTRUCT        29
191         {"DESTRUCT",    DESTRUCT,       0},
192 #define DEATHRAY        30
193         {"DEATHRAY",    DEATHRAY,       0},
194 #define DEBUGCMD        31
195         {"DEBUG",       DEBUGCMD,       0},
196 #define MAYDAY  32
197         {"MAYDAY",      MAYDAY,         0},
198         {"SOS",         MAYDAY,         0},
199         {"CALL",        MAYDAY,         0},
200 #define QUIT    33
201         {"QUIT",        QUIT,           0},
202 #define HELP    34
203         {"HELP",        HELP,           0},
204 };
205
206 #define NUMCOMMANDS     sizeof(commands)/sizeof(commands[0])
207
208 static void listCommands(void) {
209     int i, k = 0;
210     proutn("LEGAL COMMANDS ARE:");
211     for (i = 0; i < NUMCOMMANDS; i++) {
212         if (commands[i].option && !(commands[i].option & game.options))
213             continue;
214         if (k % 5 == 0)
215             skip(1);
216         proutn("%-12s ", commands[i].name); 
217         k++;
218     }
219     skip(1);
220 }
221
222 static void helpme(void) 
223 {
224     int i, j;
225     char cmdbuf[32], *cp;
226     char linebuf[132];
227     FILE *fp;
228     /* Give help on commands */
229     int key;
230     key = scan();
231     while (TRUE) {
232         if (key == IHEOL) {
233             setwnd(prompt_window);
234             proutn("Help on what command? ");
235             key = scan();
236         }
237         setwnd(message_window);
238         if (key == IHEOL) return;
239         for (i = 0; i < NUMCOMMANDS; i++) {
240             if (strcasecmp(commands[i].name, citem)==0) {
241                 i = commands[i].value;
242                 break;
243             }
244         }
245         if (i != NUMCOMMANDS) break;
246         skip(1);
247         prout("Valid commands:");
248         listCommands();
249         key = IHEOL;
250         chew();
251         skip(1);
252     }
253     if (i == COMMANDS) {
254         strcpy(cmdbuf, " ABBREV");
255     }
256     else {
257         for (j = 0; commands[i].name[j]; j++)
258             cmdbuf[j] = toupper(commands[i].name[j]);
259         cmdbuf[j] = '\0';
260     }
261     fp = fopen(SSTDOC, "r");
262     if (fp == NULL) {
263         prout("Spock-  \"Captain, that information is missing from the");
264         prout("   computer.\"");
265         /*
266          * This used to continue: "You need to find SST.DOC and put 
267          * it in the current directory."
268          */
269         return;
270     }
271     for (;;) {
272         if (fgets(linebuf, sizeof(linebuf), fp) == NULL) {
273             prout("Spock- \"Captain, there is no information on that command.\"");
274             fclose(fp);
275             return;
276         }
277         if (linebuf[0] == '%' && linebuf[1] == '%'&& linebuf[2] == ' ') {
278             for (cp = linebuf+3; isspace(*cp); cp++)
279                 continue;
280             linebuf[strlen(linebuf)-1] = '\0';
281             if (strcasecmp(cp, cmdbuf) == 0)
282                 break;
283         }
284     }
285
286     skip(1);
287     prout("Spock- \"Captain, I've found the following information:\"");
288     skip(1);
289
290     while (fgets(linebuf, sizeof(linebuf),fp)) {
291         if (strstr(linebuf, "******"))
292             break;
293         proutn(linebuf);
294     }
295     fclose(fp);
296 }
297
298 void enqueue(char *s) 
299 {
300     strcpy(line, s);
301 }
302
303 static void makemoves(void) 
304 {
305     int i, v = 0, hitme;
306     clrscr();
307     setwnd(message_window);
308     while (TRUE) { /* command loop */
309         drawmaps(1);
310         while (TRUE)  { /* get a command */
311             hitme = FALSE;
312             justin = 0;
313             Time = 0.0;
314             i = -1;
315             chew();
316             setwnd(prompt_window);
317             clrscr();
318             proutn("COMMAND> ");
319             if (scan() == IHEOL) {
320                 makechart();
321                 continue;
322             }
323             ididit=0;
324             clrscr();
325             setwnd(message_window);
326             clrscr();
327             for (i=0; i < ABANDON; i++)
328                 if (isit(commands[i].name)) {
329                     v = commands[i].value;
330                     break;
331                 }
332             if (i < ABANDON && (!commands[i].option || (commands[i].option & game.options))) 
333                 break;
334             for (; i < NUMCOMMANDS; i++)
335                 if (strcasecmp(commands[i].name, citem) == 0) {
336                     v = commands[i].value;
337                     break;
338                 }
339             if (i < NUMCOMMANDS && (!commands[i].option || (commands[i].option & game.options))) 
340                 break;
341             listCommands();
342         }
343         commandhook(commands[i].name, TRUE);
344         switch (v) { /* command switch */
345         case SRSCAN:                 // srscan
346             srscan(SCAN_FULL);
347             break;
348         case STATUS:                 // status
349             srscan(SCAN_STATUS);
350             break;
351         case REQUEST:                   // status request 
352             srscan(SCAN_REQUEST);
353             break;
354         case LRSCAN:                    // lrscan
355             lrscan();
356             break;
357         case PHASERS:                   // phasers
358             phasers();
359             if (ididit) hitme = TRUE;
360             break;
361         case TORPEDO:                   // photons
362             photon();
363             if (ididit) hitme = TRUE;
364             break;
365         case MOVE:                      // move
366             warp(1);
367             break;
368         case SHIELDS:                   // shields
369             doshield(1);
370             if (ididit) {
371                 hitme=TRUE;
372                 shldchg = 0;
373             }
374             break;
375         case DOCK:                      // dock
376             dock(1);
377             if (ididit) attack(0);
378             break;
379         case DAMAGES:                   // damages
380             dreprt();
381             break;
382         case CHART:                     // chart
383             chart(0);
384             break;
385         case IMPULSE:                   // impulse
386             impuls();
387             break;
388         case REST:              // rest
389             wait();
390             if (ididit) hitme = TRUE;
391             break;
392         case WARP:              // warp
393             setwrp();
394             break;
395         case SCORE:                // score
396             score();
397             break;
398         case SENSORS:                   // sensors
399             sensor(TRUE);
400             break;
401         case ORBIT:                     // orbit
402             orbit();
403             if (ididit) hitme = TRUE;
404             break;
405         case TRANSPORT:                 // transport "beam"
406             beam();
407             break;
408         case MINE:                      // mine
409             mine();
410             if (ididit) hitme = TRUE;
411             break;
412         case CRYSTALS:                  // crystals
413             usecrystals();
414             if (ididit) hitme = TRUE;
415             break;
416         case SHUTTLE:                   // shuttle
417             shuttle();
418             if (ididit) hitme = TRUE;
419             break;
420         case PLANETS:                   // Planet list
421             preport();
422             break;
423         case REPORT:                    // Game Report 
424             report();
425             break;
426         case COMPUTER:                  // use COMPUTER!
427             eta();
428             break;
429         case COMMANDS:
430             listCommands();
431             break;
432         case EMEXIT:            // Emergency exit
433             clrscr();   // Hide screen
434             freeze(TRUE);       // forced save
435             exit(1);            // And quick exit
436             break;
437         case PROBE:
438             probe();            // Launch probe
439             if (ididit) hitme = TRUE;
440             break;
441         case ABANDON:                   // Abandon Ship
442             abandn();
443             break;
444         case DESTRUCT:                  // Self Destruct
445             dstrct();
446             break;
447         case SAVE:                      // Save Game
448             freeze(FALSE);
449             clrscr();
450             if (skill > SKILL_GOOD)
451                 prout("WARNING--Saved games produce no plaques!");
452             break;
453         case DEATHRAY:          // Try a desparation measure
454             deathray();
455             if (ididit) hitme = TRUE;
456             break;
457         case DEBUGCMD:          // What do we want for debug???
458 #ifdef DEBUG
459             debugme();
460 #endif
461             break;
462         case MAYDAY:            // Call for help
463             help();
464             if (ididit) hitme = TRUE;
465             break;
466         case QUIT:
467             alldone = 1;        // quit the game
468 #ifdef DEBUG
469             if (idebug) score();
470 #endif
471             break;
472         case HELP:
473             helpme();   // get help
474             break;
475         }
476         commandhook(commands[i].name, FALSE);
477         for (;;) {
478             if (alldone) break;         // Game has ended
479 #ifdef DEBUG
480             if (idebug) prout("2500");
481 #endif
482             if (Time != 0.0) {
483                 events();
484                 if (alldone) break;             // Events did us in
485             }
486             if (game.state.galaxy[quadx][quady].supernova) { // Galaxy went Nova!
487                 atover(0);
488                 continue;
489             }
490             if (hitme && justin==0) {
491                 attack(2);
492                 if (alldone) break;
493                 if (game.state.galaxy[quadx][quady].supernova) {        // went NOVA! 
494                     atover(0);
495                     hitme = TRUE;
496                     continue;
497                 }
498             }
499             break;
500         }
501         if (alldone) break;
502     }
503 }
504
505
506 int main(int argc, char **argv) 
507 {
508     int i, option;
509
510     game.options = OPTION_ALL &~ OPTION_IOMODES;
511     if (getenv("TERM"))
512         game.options |= OPTION_CURSES;
513     else
514         game.options |= OPTION_TTY;
515
516     while ((option = getopt(argc, argv, "t")) != -1) {
517         switch (option) {
518         case 't':
519             game.options |= OPTION_TTY;
520             game.options &=~ OPTION_CURSES;
521             break;
522         default:
523             fprintf(stderr, "usage: sst [-t] [startcommand...].\n");
524             exit(0);
525         }
526     }
527
528     randomize();
529     iostart();
530
531     line[0] = '\0';
532     for (i = optind; i < argc;  i++) {
533         strcat(line, argv[i]);
534         strcat(line, " ");
535     }
536     while (TRUE) { /* Play a game */
537         setwnd(fullscreen_window);
538 #ifdef DEBUG
539         prout("INITIAL OPTIONS: %0lx\n", game.options);
540 #endif /* DEBUG */
541         clrscr();
542         prelim();
543         setup(line[0] == '\0');
544         if (alldone) {
545             score();
546             alldone = 0;
547         }
548         else makemoves();
549         skip(1);
550         stars();
551         skip(1);
552
553         if (tourn && alldone) {
554             proutn("Do you want your score recorded?");
555             if (ja()) {
556                 chew2();
557                 freeze(FALSE);
558             }
559         }
560         proutn("Do you want to play again? ");
561         if (!ja()) break;
562     }
563     skip(1);
564     prout("May the Great Bird of the Galaxy roost upon your home planet.");
565     return 0;
566 }
567
568
569 void cramen(int i) 
570 {
571     /* return an enemy */
572     char *s;
573         
574     switch (i) {
575     case IHR: s = "Romulan"; break;
576     case IHK: s = "Klingon"; break;
577     case IHC: s = "Commander"; break;
578     case IHS: s = "Super-commander"; break;
579     case IHSTAR: s = "Star"; break;
580     case IHP: s = "Planet"; break;
581     case IHB: s = "Starbase"; break;
582     case IHBLANK: s = "Black hole"; break;
583     case IHT: s = "Tholian"; break;
584     case IHWEB: s = "Tholian web"; break;
585     case IHQUEST: s = "Stranger"; break;
586     default: s = "Unknown??"; break;
587     }
588     proutn(s);
589 }
590
591 char *cramlc(enum loctype key, int x, int y)
592 {
593     static char buf[32];
594     buf[0] = '\0';
595     if (key == quadrant) strcpy(buf, "Quadrant ");
596     else if (key == sector) strcpy(buf, "Sector ");
597     sprintf(buf+strlen(buf), "%d - %d", x, y);
598     return buf;
599 }
600
601 void crmena(int i, int enemy, int key, int x, int y) 
602 {
603     if (i == 1) proutn("***");
604     cramen(enemy);
605     proutn(" at ");
606     proutn(cramlc(key, x, y));
607 }
608
609 void crmshp(void) 
610 {
611     char *s;
612     switch (ship) {
613     case IHE: s = "Enterprise"; break;
614     case IHF: s = "Faerie Queene"; break;
615     default:  s = "Ship???"; break;
616     }
617     proutn(s);
618 }
619
620 void stars(void) 
621 {
622     prouts("******************************************************");
623     skip(1);
624 }
625
626 double expran(double avrage) 
627 {
628     return -avrage*log(1e-7 + Rand());
629 }
630
631 double Rand(void) {
632         return rand()/(1.0 + (double)RAND_MAX);
633 }
634
635 void iran(int size, int *i, int *j) 
636 {
637     *i = Rand()*(size*1.0) + 1.0;
638     *j = Rand()*(size*1.0) + 1.0;
639 }
640
641 void chew(void)
642 {
643     linep = line;
644     *linep = 0;
645 }
646
647 void chew2(void) 
648 {
649     /* return IHEOL next time */
650     linep = line+1;
651     *linep = 0;
652 }
653
654 int scan(void) 
655 {
656     int i;
657     char *cp;
658
659     // Init result
660     aaitem = 0.0;
661     *citem = 0;
662
663     // Read a line if nothing here
664     if (*linep == 0) {
665         if (linep != line) {
666             chew();
667             return IHEOL;
668         }
669         cgetline(line, sizeof(line));
670         fflush(stdin);
671         if (curwnd==prompt_window){
672             clrscr();
673             setwnd(message_window);
674             clrscr();
675         }
676         linep = line;
677     }
678     // Skip leading white space
679     while (*linep == ' ') linep++;
680     // Nothing left
681     if (*linep == 0) {
682         chew();
683         return IHEOL;
684     }
685     if (isdigit(*linep) || *linep=='+' || *linep=='-' || *linep=='.') {
686         // treat as a number
687         i = 0;
688         if (sscanf(linep, "%lf%n", &aaitem, &i) < 1) {
689             linep = line; // Invalid numbers are ignored
690             *linep = 0;
691             return IHEOL;
692         }
693         else {
694             // skip to end
695             linep += i;
696             return IHREAL;
697         }
698     }
699     // Treat as alpha
700     cp = citem;
701     while (*linep && *linep!=' ') {
702         if ((cp - citem) < 9) *cp++ = tolower(*linep);
703         linep++;
704     }
705     *cp = 0;
706     return IHALPHA;
707 }
708
709 int ja(void) 
710 {
711     chew();
712     while (TRUE) {
713         scan();
714         chew();
715         if (*citem == 'y') return TRUE;
716         if (*citem == 'n') return FALSE;
717         proutn("Please answer with \"Y\" or \"N\": ");
718     }
719 }
720
721 void huh(void) 
722 {
723     chew();
724     skip(1);
725     prout("Beg your pardon, Captain?");
726 }
727
728 int isit(char *s) 
729 {
730     /* New function -- compares s to scanned citem and returns true if it
731        matches to the length of s */
732
733     return strncasecmp(s, citem, max(1, strlen(citem))) == 0;
734
735 }
736
737 #ifdef DEBUG
738 void debugme(void) 
739 {
740     proutn("Reset levels? ");
741     if (ja() != 0) {
742         if (energy < inenrg) energy = inenrg;
743         shield = inshld;
744         torps = intorps;
745         lsupres = inlsr;
746     }
747     proutn("Reset damage? ");
748     if (ja() != 0) {
749         int i;
750         for (i=0; i < NDEVICES; i++) 
751             if (game.damage[i] > 0.0) 
752                 game.damage[i] = 0.0;
753         stdamtim = 1e30;
754     }
755     proutn("Toggle idebug? ");
756     if (ja() != 0) {
757         idebug = !idebug;
758         if (idebug) prout("Debug output ON");
759         else prout("Debug output OFF");
760     }
761     proutn("Cause selective damage? ");
762     if (ja() != 0) {
763         int i, key;
764         for (i=0; i < NDEVICES; i++) {
765             proutn("Kill ");
766             proutn(device[i]);
767             proutn("? ");
768             chew();
769             key = scan();
770             if (key == IHALPHA &&  isit("y")) {
771                 game.damage[i] = 10.0;
772                 if (i == DRADIO) stdamtim = game.state.date;
773             }
774         }
775     }
776     proutn("Examine/change events? ");
777     if (ja() != 0) {
778         int i;
779         for (i = 1; i < NEVENTS; i++) {
780             int key;
781             if (game.future[i] == 1e30) continue;
782             switch (i) {
783             case FSNOVA:  proutn("Supernova       "); break;
784             case FTBEAM:  proutn("T Beam          "); break;
785             case FSNAP:   proutn("Snapshot        "); break;
786             case FBATTAK: proutn("Base Attack     "); break;
787             case FCDBAS:  proutn("Base Destroy    "); break;
788             case FSCMOVE: proutn("SC Move         "); break;
789             case FSCDBAS: proutn("SC Base Destroy "); break;
790             }
791             proutn("%.2f", game.future[i]-game.state.date);
792             chew();
793             proutn("  ?");
794             key = scan();
795             if (key == IHREAL) {
796                 game.future[i] = game.state.date + aaitem;
797             }
798         }
799         chew();
800     }
801 }
802 #endif