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