This hopefully fixes the "lockup on supernova" problem.
[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_commanders(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]=FOREVER;
53         game.state.isx=iqx;
54         game.state.isy=iqy;
55     }
56     else {
57         for_commanders(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 = 0; ll < nsteps; ll++) {
189 #ifdef DEBUG
190         if (idebug) {
191             prout("%d", ll+1);
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) 
277         for_local_enemies(i) {
278             ix = game.kx[i];
279             iy = game.ky[i];
280             if (game.quad[ix][iy] == IHC) {
281                 movebaddy(ix, iy, i, IHC);
282                 break;
283             }
284         }
285     if (ishere) 
286         for_local_enemies(i) {
287             ix = game.kx[i];
288             iy = game.ky[i];
289             if (game.quad[ix][iy] == IHS) {
290                 movebaddy(ix, iy, i, IHS);
291                 break;
292             }
293         }
294     /* if skill level is high, move other Klingons and Romulans too!
295        Move these last so they can base their actions on what the
296        commander(s) do. */
297     if (skill >= SKILL_EXPERT) for_local_enemies(i) {
298         ix = game.kx[i];
299         iy = game.ky[i];
300         if (game.quad[ix][iy] == IHK || game.quad[ix][iy] == IHR)
301             movebaddy(ix, iy, i, game.quad[ix][iy]);
302     }
303
304     sortkl();
305 }
306
307 static int movescom(int iqx, int iqy, int flag, int *ipage) 
308 {
309     int i;
310
311     if ((iqx==quadx && iqy==quady) ||
312         !VALID_QUADRANT(iqx, iqy) ||
313         game.state.galaxy[iqx][iqy].supernova ||
314         game.state.galaxy[iqx][iqy].klingons > 8) 
315         return 1;
316     if (flag) {
317         /* Avoid quadrants with bases if we want to avoid Enterprise */
318         for_starbases(i)
319             if (game.state.baseqx[i]==iqx && game.state.baseqy[i]==iqy) return 1;
320     }
321     if (justin && !iscate) return 1;
322     /* do the move */
323     game.state.galaxy[game.state.isx][game.state.isy].klingons--;
324     game.state.isx = iqx;
325     game.state.isy = iqy;
326     game.state.galaxy[game.state.isx][game.state.isy].klingons++;
327     if (ishere) {
328         /* SC has scooted, Remove him from current quadrant */
329         iscate=0;
330         isatb=0;
331         ishere=0;
332         ientesc=0;
333         game.future[FSCDBAS]=FOREVER;
334         for_local_enemies(i) 
335             if (game.quad[game.kx[i]][game.ky[i]] == IHS) break;
336         game.quad[game.kx[i]][game.ky[i]] = IHDOT;
337         game.kx[i] = game.kx[nenhere];
338         game.ky[i] = game.ky[nenhere];
339         game.kdist[i] = game.kdist[nenhere];
340         game.kavgd[i] = game.kavgd[nenhere];
341         game.kpower[i] = game.kpower[nenhere];
342         klhere--;
343         nenhere--;
344         if (condit!=IHDOCKED) newcnd();
345         sortkl();
346     }
347     /* check for a helpful planet */
348     for (i = 0; i < inplan; i++) {
349         if (game.state.plnets[i].x==game.state.isx && game.state.plnets[i].y==game.state.isy &&
350             game.state.plnets[i].crystals == 1) {
351             /* destroy the planet */
352             DESTROY(&game.state.plnets[i]);
353             game.state.galaxy[game.state.isx][game.state.isy].planets -= 1;
354             if (game.damage[DRADIO] == 0.0 || condit == IHDOCKED) {
355                 if (*ipage==0) pause_game(1);
356                 *ipage = 1;
357                 prout("Lt. Uhura-  \"Captain, Starfleet Intelligence reports");
358                 proutn("   a planet in ");
359                 proutn(cramlc(quadrant, game.state.isx, game.state.isy));
360                 prout(" has been destroyed");
361                 prout("   by the Super-commander.\"");
362             }
363             break;
364         }
365     }
366     return 0; /* looks good! */
367 }
368                         
369 void scom(int *ipage)
370 {
371     int i, i2, j, ideltax, ideltay, ibqx, ibqy, sx, sy, ifindit, iwhichb;
372     int iqx, iqy;
373     int basetbl[BASEMAX+1];
374     double bdist[BASEMAX+1];
375     int flag;
376 #ifdef DEBUG
377     if (idebug) prout("SCOM");
378 #endif
379
380     /* Decide on being active or passive */
381     flag = ((game.state.killc+game.state.killk)/(game.state.date+0.01-indate) < 0.1*skill*(skill+1.0) ||
382             (game.state.date-indate) < 3.0);
383     if (iscate==0 && flag) {
384         /* compute move away from Enterprise */
385         ideltax = game.state.isx-quadx;
386         ideltay = game.state.isy-quady;
387         if (sqrt(ideltax*(double)ideltax+ideltay*(double)ideltay) > 2.0) {
388             /* circulate in space */
389             ideltax = game.state.isy-quady;
390             ideltay = quadx-game.state.isx;
391         }
392     }
393     else {
394         /* compute distances to starbases */
395         if (game.state.rembase <= 0) {
396             /* nothing left to do */
397             game.future[FSCMOVE] = FOREVER;
398             return;
399         }
400         sx = game.state.isx;
401         sy = game.state.isy;
402         for_starbases(i) {
403             basetbl[i] = i;
404             ibqx = game.state.baseqx[i];
405             ibqy = game.state.baseqy[i];
406             bdist[i] = sqrt(square(ibqx-sx) + square(ibqy-sy));
407         }
408         if (game.state.rembase > 1) {
409             /* sort into nearest first order */
410             int iswitch;
411             do {
412                 iswitch = 0;
413                 for (i=1; i < game.state.rembase-1; i++) {
414                     if (bdist[i] > bdist[i+1]) {
415                         int ti = basetbl[i];
416                         double t = bdist[i];
417                         bdist[i] = bdist[i+1];
418                         bdist[i+1] = t;
419                         basetbl[i] = basetbl[i+1];
420                         basetbl[i+1] =ti;
421                         iswitch = 1;
422                     }
423                 }
424             } while (iswitch);
425         }
426         /* look for nearest base without a commander, no Enterprise, and
427            without too many Klingons, and not already under attack. */
428         ifindit = iwhichb = 0;
429
430         for_starbases(i2) {
431             i = basetbl[i2];    /* bug in original had it not finding nearest*/
432             ibqx = game.state.baseqx[i];
433             ibqy = game.state.baseqy[i];
434             if ((ibqx == quadx && ibqy == quady) ||
435                 (ibqx == batx && ibqy == baty) ||
436                 game.state.galaxy[ibqx][ibqy].supernova ||
437                 game.state.galaxy[ibqx][ibqy].klingons > 8) 
438                 continue;
439             /* if there is a commander, an no other base is appropriate,
440                we will take the one with the commander */
441             for_commanders (j) {
442                 if (ibqx==game.state.cx[j] && ibqy==game.state.cy[j] && ifindit!= 2) {
443                     ifindit = 2;
444                     iwhichb = i;
445                     break;
446                 }
447             }
448             if (j > game.state.remcom) { /* no commander -- use this one */
449                 ifindit = 1;
450                 iwhichb = i;
451                 break;
452             }
453         }
454         if (ifindit==0) return; /* Nothing suitable -- wait until next time*/
455         ibqx = game.state.baseqx[iwhichb];
456         ibqy = game.state.baseqy[iwhichb];
457         /* decide how to move toward base */
458         ideltax = ibqx - game.state.isx;
459         ideltay = ibqy - game.state.isy;
460     }
461     /* Maximum movement is 1 quadrant in either or both axis */
462     if (ideltax > 1) ideltax = 1;
463     if (ideltax < -1) ideltax = -1;
464     if (ideltay > 1) ideltay = 1;
465     if (ideltay < -1) ideltay = -1;
466
467     /* try moving in both x and y directions */
468     iqx = game.state.isx + ideltax;
469     iqy = game.state.isy + ideltax;
470     if (movescom(iqx, iqy, flag, ipage)) {
471         /* failed -- try some other maneuvers */
472         if (ideltax==0 || ideltay==0) {
473             /* attempt angle move */
474             if (ideltax != 0) {
475                 iqy = game.state.isy + 1;
476                 if (movescom(iqx, iqy, flag, ipage)) {
477                     iqy = game.state.isy - 1;
478                     movescom(iqx, iqy, flag, ipage);
479                 }
480             }
481             else {
482                 iqx = game.state.isx + 1;
483                 if (movescom(iqx, iqy, flag, ipage)) {
484                     iqx = game.state.isx - 1;
485                     movescom(iqx, iqy, flag, ipage);
486                 }
487             }
488         }
489         else {
490             /* try moving just in x or y */
491             iqy = game.state.isy;
492             if (movescom(iqx, iqy, flag, ipage)) {
493                 iqy = game.state.isy + ideltay;
494                 iqx = game.state.isx;
495                 movescom(iqx, iqy, flag, ipage);
496             }
497         }
498     }
499     /* check for a base */
500     if (game.state.rembase == 0) {
501         game.future[FSCMOVE] = FOREVER;
502     }
503     else for_starbases(i) {
504         ibqx = game.state.baseqx[i];
505         ibqy = game.state.baseqy[i];
506         if (ibqx==game.state.isx && ibqy == game.state.isy && game.state.isx != batx && game.state.isy != baty) {
507             /* attack the base */
508             if (flag) return; /* no, don't attack base! */
509             iseenit = 0;
510             isatb=1;
511             game.future[FSCDBAS] = game.state.date + 1.0 +2.0*Rand();
512             if (game.future[FCDBAS] < FOREVER) game.future[FSCDBAS] +=
513                 game.future[FCDBAS]-game.state.date;
514             if (game.damage[DRADIO] > 0 && condit != IHDOCKED)
515                 return; /* no warning */
516             iseenit = 1;
517             if (*ipage == 0)  pause_game(1);
518             *ipage=1;
519             proutn("Lt. Uhura-  \"Captain, the starbase in ");
520             proutn(cramlc(quadrant, game.state.isx, game.state.isy));
521             skip(1);
522             prout("   reports that it is under attack from the Klingon Super-commander.");
523             proutn("   It can survive until stardate %d.\"",
524                    (int)game.future[FSCDBAS]);
525             if (resting==0) return;
526             prout("Mr. Spock-  \"Captain, shall we cancel the rest period?\"");
527             if (ja()==0) return;
528             resting = 0;
529             Time = 0.0; /* actually finished */
530             return;
531         }
532     }
533     /* Check for intelligence report */
534     if (
535 #ifdef DEBUG
536         idebug==0 &&
537 #endif
538         (Rand() > 0.2 ||
539          (game.damage[DRADIO] > 0.0 && condit != IHDOCKED) ||
540          !game.state.galaxy[game.state.isx][game.state.isy].charted))
541         return;
542     if (*ipage==0) pause_game(1);
543     *ipage = 1;
544     prout("Lt. Uhura-  \"Captain, Starfleet Intelligence reports");
545     proutn("   the Super-commander is in ");
546     proutn(cramlc(quadrant, game.state.isx, game.state. isy));
547     prout(".\"");
548     return;
549 }
550
551 void movetho(void)
552 {
553     int idx, idy, im, i, dum, my;
554     /* Move the Tholian */
555     if (ithere==0 || justin == 1) return;
556
557     if (ithx == 1 && ithy == 1) {
558         idx = 1; idy = QUADSIZE;
559     }
560     else if (ithx == 1 && ithy == QUADSIZE) {
561         idx = QUADSIZE; idy = QUADSIZE;
562     }
563     else if (ithx == QUADSIZE && ithy == QUADSIZE) {
564         idx = QUADSIZE; idy = 1;
565     }
566     else if (ithx == QUADSIZE && ithy == 1) {
567         idx = 1; idy = 1;
568     }
569     else {
570         /* something is wrong! */
571         ithere = 0;
572         return;
573     }
574
575     /* Do nothing if we are blocked */
576     if (game.quad[idx][idy]!= IHDOT && game.quad[idx][idy]!= IHWEB) return;
577     game.quad[ithx][ithy] = IHWEB;
578
579     if (ithx != idx) {
580         /* move in x axis */
581         im = fabs((double)idx - ithx)/((double)idx - ithx);
582         while (ithx != idx) {
583             ithx += im;
584             if (game.quad[ithx][ithy]==IHDOT) game.quad[ithx][ithy] = IHWEB;
585         }
586     }
587     else if (ithy != idy) {
588         /* move in y axis */
589         im = fabs((double)idy - ithy)/((double)idy - ithy);
590         while (ithy != idy) {
591             ithy += im;
592             if (game.quad[ithx][ithy]==IHDOT) game.quad[ithx][ithy] = IHWEB;
593         }
594     }
595     game.quad[ithx][ithy] = IHT;
596     game.kx[nenhere]=ithx;
597     game.ky[nenhere]=ithy;
598
599     /* check to see if all holes plugged */
600     for_sectors(i) {
601         if (game.quad[1][i]!=IHWEB && game.quad[1][i]!=IHT) return;
602         if (game.quad[QUADSIZE][i]!=IHWEB && game.quad[QUADSIZE][i]!=IHT) return;
603         if (game.quad[i][1]!=IHWEB && game.quad[i][1]!=IHT) return;
604         if (game.quad[i][QUADSIZE]!=IHWEB && game.quad[i][QUADSIZE]!=IHT) return;
605     }
606     /* All plugged up -- Tholian splits */
607     game.quad[ithx][ithy]=IHWEB;
608     dropin(IHBLANK, &dum, &my);
609     crmena(1,IHT, 2, ithx, ithy);
610     prout(" completes web.");
611     ithere = ithx = ithy = 0;
612     nenhere--;
613     return;
614 }