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