Clean up nasty FORTRAN-style if statements.
[super-star-trek.git] / src / ai.c
1 #include "sst.h"
2
3 static bool tryexit(coord look, int ienm, int loccom, bool irun) 
4 /* a bad guy attempts to bug out */
5 {
6     int n;
7     coord iq;
8
9     iq.x = game.quadrant.x+(look.x+(QUADSIZE-1))/QUADSIZE - 1;
10     iq.y = game.quadrant.y+(look.y+(QUADSIZE-1))/QUADSIZE - 1;
11     if (!VALID_QUADRANT(iq.x,iq.y) ||
12         game.state.galaxy[iq.x][iq.y].supernova ||
13         game.state.galaxy[iq.x][iq.y].klingons > MAXKLQUAD-1)
14         return false; /* no can do -- neg energy, supernovae, or >MAXKLQUAD-1 Klingons */
15     if (ienm == IHR) return false; /* Romulans cannot escape! */
16     if (!irun) {
17         /* avoid intruding on another commander's territory */
18         if (ienm == IHC) {
19             for_commanders(n)
20                 if (same(game.state.kcmdr[n],iq))
21                     return false;
22             /* refuse to leave if currently attacking starbase */
23             if (same(game.battle, game.quadrant))
24                 return false;
25         }
26         /* don't leave if over 1000 units of energy */
27         if (game.kpower[loccom] > 1000.0)
28             return false;
29     }
30     /* print escape message and move out of quadrant.
31        We know this if either short or long range sensors are working */
32     if (!damaged(DSRSENS) || !damaged(DLRSENS) ||
33         game.condition == docked) {
34         crmena(true, ienm, sector, game.ks[loccom]);
35         prout(_(" escapes to %s (and regains strength)."),
36               cramlc(quadrant, iq));
37     }
38     /* handle local matters related to escape */
39     game.quad[game.ks[loccom].x][game.ks[loccom].y] = IHDOT;
40     game.ks[loccom] = game.ks[game.nenhere];
41     game.kavgd[loccom] = game.kavgd[game.nenhere];
42     game.kpower[loccom] = game.kpower[game.nenhere];
43     game.kdist[loccom] = game.kdist[game.nenhere];
44     game.klhere--;
45     game.nenhere--;
46     if (game.condition != docked)
47         newcnd();
48     /* Handle global matters related to escape */
49     game.state.galaxy[game.quadrant.x][game.quadrant.y].klingons--;
50     game.state.galaxy[iq.x][iq.y].klingons++;
51     if (ienm==IHS) {
52         game.ishere = false;
53         game.iscate = false;
54         game.ientesc = false;
55         game.isatb = 0;
56         schedule(FSCMOVE, 0.2777);
57         unschedule(FSCDBAS);
58         game.state.kscmdr=iq;
59     }
60     else {
61         for_commanders(n) {
62             if (same(game.state.kcmdr[n], game.quadrant)) {
63                 game.state.kcmdr[n]=iq;
64                 break;
65             }
66         }
67         game.comhere = false;
68     }
69     return true; /* success */
70 }
71
72
73 static void movebaddy(coord com, int loccom, feature ienm)
74 /* tactical movement for the bad guys */
75 {
76     int motion, mdist, nsteps, mx, my, ll;
77     coord next, look;
78     int krawlx, krawly;
79     bool success, irun = false;
80     int attempts;
81     /* This should probably be just game.comhere + game.ishere */
82     int nbaddys = game.skill >= SKILL_EXPERT ?
83         (int)((game.comhere*2 + game.ishere*2+game.klhere*1.23+game.irhere*1.5)/2.0):
84         (game.comhere + game.ishere);
85     double dist1, forces;
86
87     dist1 = game.kdist[loccom];
88     mdist = dist1 + 0.5; /* Nearest integer distance */
89
90     /* If SC, check with spy to see if should hi-tail it */
91     if (ienm==IHS &&
92         (game.kpower[loccom] <= 500.0 || (game.condition==docked && !damaged(DPHOTON)))) {
93         irun = true;
94         motion = -QUADSIZE;
95     }
96     else {
97         /* decide whether to advance, retreat, or hold position */
98 /* Algorithm:
99  * Enterprise has "force" based on condition of phaser and photon torpedoes.
100  If both are operating full strength, force is 1000. If both are damaged,
101  force is -1000. Having shields down subtracts an additional 1000.
102
103  * Enemy has forces equal to the energy of the attacker plus
104  100*(K+R) + 500*(C+S) - 400 for novice through good levels OR
105  346*K + 400*R + 500*(C+S) - 400 for expert and emeritus.
106
107  Attacker Initial energy levels (nominal):
108  Klingon   Romulan   Commander   Super-Commander
109  Novice    400        700        1200        
110  Fair      425        750        1250
111  Good      450        800        1300        1750
112  Expert    475        850        1350        1875
113  Emeritus  500        900        1400        2000
114  VARIANCE   75        200         200         200
115
116  Enemy vessels only move prior to their attack. In Novice - Good games
117  only commanders move. In Expert games, all enemy vessels move if there
118  is a commander present. In Emeritus games all enemy vessels move.
119
120  *  If Enterprise is not docked, an agressive action is taken if enemy
121  forces are 1000 greater than Enterprise.
122
123  Agressive action on average cuts the distance between the ship and
124  the enemy to 1/4 the original.
125
126  *  At lower energy advantage, movement units are proportional to the
127  advantage with a 650 advantage being to hold ground, 800 to move forward
128  1, 950 for two, 150 for back 4, etc. Variance of 100.
129
130  If docked, is reduced by roughly 1.75*game.skill, generally forcing a
131  retreat, especially at high skill levels.
132
133  *  Motion is limited to skill level, except for SC hi-tailing it out.
134  */
135
136         forces = game.kpower[loccom]+100.0*game.nenhere+400*(nbaddys-1);
137         if (!game.shldup) forces += 1000; /* Good for enemy if shield is down! */
138         if (!damaged(DPHASER) || !damaged(DPHOTON)) {
139             if (damaged(DPHASER)) /* phasers damaged */
140                 forces += 300.0;
141             else
142                 forces -= 0.2*(game.energy - 2500.0);
143             if (damaged(DPHOTON)) /* photon torpedoes damaged */
144                 forces += 300.0;
145             else
146                 forces -= 50.0*game.torps;
147         }
148         else {
149             /* phasers and photon tubes both out! */
150             forces += 1000.0;
151         }
152         motion = 0;
153         if (forces <= 1000.0 && game.condition != docked) /* Typical situation */
154             motion = ((forces+200.0*Rand())/150.0) - 5.0;
155         else {
156             if (forces > 1000.0) /* Very strong -- move in for kill */
157                 motion = (1.0-square(Rand()))*dist1 + 1.0;
158             if (game.condition==docked && (game.options & OPTION_BASE)) /* protected by base -- back off ! */
159                 motion -= game.skill*(2.0-square(Rand()));
160         }
161         if (idebug)
162             proutn("=== MOTION = %d, FORCES = %1.2f, ", motion, forces);
163         /* don't move if no motion */
164         if (motion==0)
165             return;
166         /* Limit motion according to skill */
167         if (abs(motion) > game.skill)
168             motion = (motion < 0) ? -game.skill : game.skill;
169     }
170     /* calculate preferred number of steps */
171     nsteps = motion < 0 ? -motion : motion;
172     if (motion > 0 && nsteps > mdist) nsteps = mdist; /* don't overshoot */
173     if (nsteps > QUADSIZE) nsteps = QUADSIZE; /* This shouldn't be necessary */
174     if (nsteps < 1) nsteps = 1; /* This shouldn't be necessary */
175     if (idebug) {
176         proutn("NSTEPS = %d:", nsteps);
177     }
178     /* Compute preferred values of delta X and Y */
179     mx = game.sector.x - com.x;
180     my = game.sector.y - com.y;
181     if (2.0 * abs(mx) < abs(my))
182         mx = 0;
183     if (2.0 * abs(my) < abs(game.sector.x-com.x))
184         my = 0;
185     if (mx != 0)
186         mx = mx*motion < 0 ? -1 : 1;
187     if (my != 0)
188         my = my*motion < 0 ? -1 : 1;
189     next = com;
190     /* main move loop */
191     for (ll = 0; ll < nsteps; ll++) {
192         if (idebug)
193             proutn(" %d", ll+1);
194         /* Check if preferred position available */
195         look.x = next.x + mx;
196         look.y = next.y + my;
197         krawlx = mx < 0 ? 1 : -1;
198         krawly = my < 0 ? 1 : -1;
199         success = false;
200         attempts = 0; /* Settle mysterious hang problem */
201         while (attempts++ < 20 && !success) {
202             if (look.x < 1 || look.x > QUADSIZE) {
203                 if (motion < 0 && tryexit(look, ienm, loccom, irun))
204                     return;
205                 if (krawlx == mx || my == 0)
206                     break;
207                 look.x = next.x + krawlx;
208                 krawlx = -krawlx;
209             }
210             else if (look.y < 1 || look.y > QUADSIZE) {
211                 if (motion < 0 && tryexit(look, ienm, loccom, irun))
212                     return;
213                 if (krawly == my || mx == 0)
214                     break;
215                 look.y = next.y + krawly;
216                 krawly = -krawly;
217             }
218             else if ((game.options & OPTION_RAMMING) && game.quad[look.x][look.y] != IHDOT) {
219                 /* See if we should ram ship */
220                 if (game.quad[look.x][look.y] == game.ship &&
221                     (ienm == IHC || ienm == IHS)) {
222                     ram(true, ienm, com);
223                     return;
224                 }
225                 if (krawlx != mx && my != 0) {
226                     look.x = next.x + krawlx;
227                     krawlx = -krawlx;
228                 }
229                 else if (krawly != my && mx != 0) {
230                     look.y = next.y + krawly;
231                     krawly = -krawly;
232                 }
233                 else
234                     break; /* we have failed */
235             }
236             else
237                 success = true;
238         }
239         if (success) {
240             next = look;
241             if (idebug)
242                 proutn(cramlc(neither, next));
243         }
244         else
245             break; /* done early */
246         
247     }
248     if (idebug)
249         skip(1);
250     /* Put commander in place within same quadrant */
251     game.quad[com.x][com.y] = IHDOT;
252     game.quad[next.x][next.y] = ienm;
253     if (!same(next, com)) {
254         /* it moved */
255         game.ks[loccom] = next;
256         game.kdist[loccom] = game.kavgd[loccom] = distance(game.sector, next);
257         if (!damaged(DSRSENS) || game.condition == docked) {
258             proutn("***");
259             cramen(ienm);
260             proutn(_(" from %s"), cramlc(2, com));
261             if (game.kdist[loccom] < dist1)
262                 proutn(_(" advances to "));
263             else
264                 proutn(_(" retreats to "));
265             prout(cramlc(sector, next));
266         }
267     }
268 }
269
270 void movcom(void) 
271 /* move a commander */
272 {
273     coord w; 
274     int i;
275
276     if (idebug)
277         prout("== MOVCOM");
278
279     /* Figure out which Klingon is the commander (or Supercommander)
280        and do move */
281     if (game.comhere) 
282         for_local_enemies(i) {
283             w = game.ks[i];
284             if (game.quad[w.x][w.y] == IHC) {
285                 movebaddy(w, i, IHC);
286                 break;
287             }
288         }
289     if (game.ishere) 
290         for_local_enemies(i) {
291             w = game.ks[i];
292             if (game.quad[w.x][w.y] == IHS) {
293                 movebaddy(w, i, IHS);
294                 break;
295             }
296         }
297     /* if skill level is high, move other Klingons and Romulans too!
298        Move these last so they can base their actions on what the
299        commander(s) do. */
300     if (game.skill >= SKILL_EXPERT && (game.options & OPTION_MVBADDY)) 
301         for_local_enemies(i) {
302             w = game.ks[i];
303             if (game.quad[w.x][w.y] == IHK || game.quad[w.x][w.y] == IHR)
304                 movebaddy(w, i, game.quad[w.x][w.y]);
305         }
306
307     sortkl();
308 }
309
310 static bool movescom(coord iq, bool flag, bool *ipage) 
311 /* commander movement helper */
312 {
313     int i;
314
315     if (same(iq, game.quadrant) || !VALID_QUADRANT(iq.x, iq.y) ||
316         game.state.galaxy[iq.x][iq.y].supernova ||
317         game.state.galaxy[iq.x][iq.y].klingons > MAXKLQUAD-1) 
318         return 1;
319     if (flag) {
320         /* Avoid quadrants with bases if we want to avoid Enterprise */
321         for_starbases(i)
322             if (same(game.state.baseq[i], iq)) 
323                 return true;
324     }
325     if (game.justin && !game.iscate)
326         return true;
327     /* do the move */
328     game.state.galaxy[game.state.kscmdr.x][game.state.kscmdr.y].klingons--;
329     game.state.kscmdr = iq;
330     game.state.galaxy[game.state.kscmdr.x][game.state.kscmdr.y].klingons++;
331     if (game.ishere) {
332         /* SC has scooted, Remove him from current quadrant */
333         game.iscate=false;
334         game.isatb=0;
335         game.ishere = false;
336         game.ientesc = false;
337         unschedule(FSCDBAS);
338         for_local_enemies(i) 
339             if (game.quad[game.ks[i].x][game.ks[i].y] == IHS)
340                 break;
341         game.quad[game.ks[i].x][game.ks[i].y] = IHDOT;
342         game.ks[i] = game.ks[game.nenhere];
343         game.kdist[i] = game.kdist[game.nenhere];
344         game.kavgd[i] = game.kavgd[game.nenhere];
345         game.kpower[i] = game.kpower[game.nenhere];
346         game.klhere--;
347         game.nenhere--;
348         if (game.condition!=docked)
349             newcnd();
350         sortkl();
351     }
352     /* check for a helpful planet */
353     for (i = 0; i < game.inplan; i++) {
354         if (same(game.state.plnets[i].w, game.state.kscmdr) &&
355             game.state.plnets[i].crystals == present) {
356             /* destroy the planet */
357             DESTROY(&game.state.plnets[i]);
358             game.state.galaxy[game.state.kscmdr.x][game.state.kscmdr.y].planet = NOPLANET;
359             if (!damaged(DRADIO) || game.condition == docked) {
360                 if (!*ipage)
361                     pause_game(true);
362                 *ipage = true;
363                 prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"));
364                 proutn(_("   a planet in "));
365                 proutn(cramlc(quadrant, game.state.kscmdr));
366                 prout(_(" has been destroyed"));
367                 prout(_("   by the Super-commander.\""));
368             }
369             break;
370         }
371     }
372     return false; /* looks good! */
373 }
374                         
375 void scom(bool *ipage)
376 /* move the Super Commander */
377 {
378     int i, i2, j, ideltax, ideltay, ifindit, iwhichb;
379     coord iq, sc, ibq;
380     int basetbl[BASEMAX+1];
381     double bdist[BASEMAX+1];
382     bool flag;
383
384     if (idebug)
385         prout("== SCOM");
386
387     /* Decide on being active or passive */
388     flag = ((NKILLC+NKILLK)/(game.state.date+0.01-game.indate) < 0.1*game.skill*(game.skill+1.0) ||
389             (game.state.date-game.indate) < 3.0);
390     if (!game.iscate && flag) {
391         /* compute move away from Enterprise */
392         ideltax = game.state.kscmdr.x-game.quadrant.x;
393         ideltay = game.state.kscmdr.y-game.quadrant.y;
394         if (sqrt(ideltax*(double)ideltax+ideltay*(double)ideltay) > 2.0) {
395             /* circulate in space */
396             ideltax = game.state.kscmdr.y-game.quadrant.y;
397             ideltay = game.quadrant.x-game.state.kscmdr.x;
398         }
399     }
400     else {
401         /* compute distances to starbases */
402         if (game.state.rembase <= 0) {
403             /* nothing left to do */
404             unschedule(FSCMOVE);
405             return;
406         }
407         sc = game.state.kscmdr;
408         for_starbases(i) {
409             basetbl[i] = i;
410             bdist[i] = distance(game.state.baseq[i], sc);
411         }
412         if (game.state.rembase > 1) {
413             /* sort into nearest first order */
414             bool iswitch;
415             do {
416                 iswitch = false;
417                 for (i=1; i < game.state.rembase-1; i++) {
418                     if (bdist[i] > bdist[i+1]) {
419                         int ti = basetbl[i];
420                         double t = bdist[i];
421                         bdist[i] = bdist[i+1];
422                         bdist[i+1] = t;
423                         basetbl[i] = basetbl[i+1];
424                         basetbl[i+1] =ti;
425                         iswitch = true;
426                     }
427                 }
428             } while (iswitch);
429         }
430         /* look for nearest base without a commander, no Enterprise, and
431            without too many Klingons, and not already under attack. */
432         ifindit = iwhichb = 0;
433
434         for_starbases(i2) {
435             i = basetbl[i2];    /* bug in original had it not finding nearest*/
436             ibq = game.state.baseq[i];
437             if (same(ibq, game.quadrant) || same(ibq, game.battle) ||
438                 game.state.galaxy[ibq.x][ibq.y].supernova ||
439                 game.state.galaxy[ibq.x][ibq.y].klingons > MAXKLQUAD-1) 
440                 continue;
441             /* if there is a commander, an no other base is appropriate,
442                we will take the one with the commander */
443             for_commanders (j) {
444                 if (same(ibq, game.state.kcmdr[j]) && ifindit!= 2) {
445                     ifindit = 2;
446                     iwhichb = i;
447                     break;
448                 }
449             }
450             if (j > game.state.remcom) { /* no commander -- use this one */
451                 ifindit = 1;
452                 iwhichb = i;
453                 break;
454             }
455         }
456         if (ifindit==0) return; /* Nothing suitable -- wait until next time*/
457         ibq = game.state.baseq[iwhichb];
458         /* decide how to move toward base */
459         ideltax = ibq.x - game.state.kscmdr.x;
460         ideltay = ibq.y - game.state.kscmdr.y;
461     }
462     /* Maximum movement is 1 quadrant in either or both axis */
463     if (ideltax > 1)
464         ideltax = 1;
465     if (ideltax < -1)
466         ideltax = -1;
467     if (ideltay > 1)
468         ideltay = 1;
469     if (ideltay < -1)
470         ideltay = -1;
471
472     /* try moving in both x and y directions */
473     iq.x = game.state.kscmdr.x + ideltax;
474     iq.y = game.state.kscmdr.y + ideltax;
475     if (movescom(iq, flag, ipage)) {
476         /* failed -- try some other maneuvers */
477         if (ideltax==0 || ideltay==0) {
478             /* attempt angle move */
479             if (ideltax != 0) {
480                 iq.y = game.state.kscmdr.y + 1;
481                 if (movescom(iq, flag, ipage)) {
482                     iq.y = game.state.kscmdr.y - 1;
483                     movescom(iq, flag, ipage);
484                 }
485             }
486             else {
487                 iq.x = game.state.kscmdr.x + 1;
488                 if (movescom(iq, flag, ipage)) {
489                     iq.x = game.state.kscmdr.x - 1;
490                     movescom(iq, flag, ipage);
491                 }
492             }
493         }
494         else {
495             /* try moving just in x or y */
496             iq.y = game.state.kscmdr.y;
497             if (movescom(iq, flag, ipage)) {
498                 iq.y = game.state.kscmdr.y + ideltay;
499                 iq.x = game.state.kscmdr.x;
500                 movescom(iq, flag, ipage);
501             }
502         }
503     }
504     /* check for a base */
505     if (game.state.rembase == 0) {
506         unschedule(FSCMOVE);
507     }
508     else for_starbases(i) {
509         ibq = game.state.baseq[i];
510         if (same(ibq, game.state.kscmdr) && same(game.state.kscmdr, game.battle)) {
511             /* attack the base */
512             if (flag) return; /* no, don't attack base! */
513             game.iseenit = false;
514             game.isatb = 1;
515             schedule(FSCDBAS, 1.0 +2.0*Rand());
516             if (is_scheduled(FCDBAS)) 
517                 postpone(FSCDBAS, scheduled(FCDBAS)-game.state.date);
518             if (damaged(DRADIO) && game.condition != docked)
519                 return; /* no warning */
520             game.iseenit = true;
521             if (!*ipage)
522                 pause_game(true);
523             *ipage = true;
524             proutn(_("Lt. Uhura-  \"Captain, the starbase in "));
525             proutn(cramlc(quadrant, game.state.kscmdr));
526             skip(1);
527             prout(_("   reports that it is under attack from the Klingon Super-commander."));
528             proutn(_("   It can survive until stardate %d.\""),
529                    (int)scheduled(FSCDBAS));
530             if (!game.resting)
531                 return;
532             prout(_("Mr. Spock-  \"Captain, shall we cancel the rest period?\""));
533             if (ja() == false)
534                 return;
535             game.resting = false;
536             game.optime = 0.0; /* actually finished */
537             return;
538         }
539     }
540     /* Check for intelligence report */
541     if (
542         !idebug &&
543         (Rand() > 0.2 ||
544          (damaged(DRADIO) && game.condition != docked) ||
545          !game.state.galaxy[game.state.kscmdr.x][game.state.kscmdr.y].charted))
546         return;
547     if (!*ipage)
548         pause_game(true);
549     *ipage = true;
550     prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"));
551     proutn(_("   the Super-commander is in "));
552     proutn(cramlc(quadrant, game.state.kscmdr));
553     prout(".\"");
554     return;
555 }
556
557 void movetho(void)
558 /* move the Tholian */
559 {
560     int idx, idy, im, i;
561     /* Move the Tholian */
562     if (!game.ithere || game.justin)
563         return;
564
565     if (game.tholian.x == 1 && game.tholian.y == 1) {
566         idx = 1; idy = QUADSIZE;
567     }
568     else if (game.tholian.x == 1 && game.tholian.y == QUADSIZE) {
569         idx = QUADSIZE; idy = QUADSIZE;
570     }
571     else if (game.tholian.x == QUADSIZE && game.tholian.y == QUADSIZE) {
572         idx = QUADSIZE; idy = 1;
573     }
574     else if (game.tholian.x == QUADSIZE && game.tholian.y == 1) {
575         idx = 1; idy = 1;
576     }
577     else {
578         /* something is wrong! */
579         game.ithere = false;
580         return;
581     }
582
583     /* Do nothing if we are blocked */
584     if (game.quad[idx][idy]!= IHDOT && game.quad[idx][idy]!= IHWEB)
585         return;
586     game.quad[game.tholian.x][game.tholian.y] = IHWEB;
587
588     if (game.tholian.x != idx) {
589         /* move in x axis */
590         im = fabs((double)idx - game.tholian.x)/((double)idx - game.tholian.x);
591         while (game.tholian.x != idx) {
592             game.tholian.x += im;
593             if (game.quad[game.tholian.x][game.tholian.y]==IHDOT)
594                 game.quad[game.tholian.x][game.tholian.y] = IHWEB;
595         }
596     }
597     else if (game.tholian.y != idy) {
598         /* move in y axis */
599         im = fabs((double)idy - game.tholian.y)/((double)idy - game.tholian.y);
600         while (game.tholian.y != idy) {
601             game.tholian.y += im;
602             if (game.quad[game.tholian.x][game.tholian.y]==IHDOT)
603                 game.quad[game.tholian.x][game.tholian.y] = IHWEB;
604         }
605     }
606     game.quad[game.tholian.x][game.tholian.y] = IHT;
607     game.ks[game.nenhere] = game.tholian;
608
609     /* check to see if all holes plugged */
610     for_sectors(i) {
611         if (game.quad[1][i]!=IHWEB && game.quad[1][i]!=IHT)
612             return;
613         if (game.quad[QUADSIZE][i]!=IHWEB && game.quad[QUADSIZE][i]!=IHT)
614             return;
615         if (game.quad[i][1]!=IHWEB && game.quad[i][1]!=IHT)
616             return;
617         if (game.quad[i][QUADSIZE]!=IHWEB && game.quad[i][QUADSIZE]!=IHT)
618             return;
619     }
620     /* All plugged up -- Tholian splits */
621     game.quad[game.tholian.x][game.tholian.y]=IHWEB;
622     dropin(IHBLANK);
623     crmena(true, IHT, sector, game.tholian);
624     prout(_(" completes web."));
625     game.ithere = false;
626     game.nenhere--;
627     return;
628 }