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