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