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