1 /* c_jovian_ai.cc 1.14 95/12/28 00:41:30 */
2
3
4 // xspacewarp by Greg Walker (gow@math.orst.edu)
5
6 // This is free software. Non-profit redistribution and/or modification
7 // is allowed and welcome.
8
9
10 // These are the "artificial intelligence" routines for jovians. Any
11 // upgrades of the Jovian AI should only require modifying the
12 // functions in this file.
13 // Probabilities are represented by floats in [0,1].
14 //
15 // pick_action() just decides the general kind of action for the jovian to make
16 // (MOVE, SHOOT, LEAP, NONE). After calling pick_action(), either
17 // pick_direction(), pick_target(), or pick_sector() depending if pick_action()
18 // returned MOVE, SHOOT, or LEAP respectively.
19 // pick_direction(), pick_target(), and pick_sector() provide details on
20 // what direction to move in, who to shoot, and what sector to go to.
21 // After pick_direction(), pick_target() or pick_sector() are called,
22 // the Decision struct gets filled in and is then ready to be passed to the
23 // act() method.
24
25 #include "c_jovian.hh"
26 #include "c_jovian_ai.hh" // params and inlines used in these ai routines
27 #include "c_sector.hh"
28 #include <iostream>
29 #include <stdlib.h> // rand()
30 #include "common.hh"
31 #include "params.hh"
32 #include "globals.hh" // access to universe[][]
33 #include "space_objects.hh"
34
35
36 // pick_action() fills in these values:
37
38 static Point target; // the nearest target or {-1, -1} if no target
39 static long sdist; // square of distance to nearest target or -1
40
41
42 // returns probability of shooting target
43
44 static float pshoot(void);
45
46
47 // initialize the non-static AI specific data
48
init_ai_flags()49 void Jovian::init_ai_flags()
50 {
51 mustmove = false;
52 retreat_flag = false;
53 forcedvert = false;
54 forcedhoriz = false;
55 israiding = false;
56 }
57
58
59 // initialize the static AI specific data
60
init_ai()61 void Jovian::init_ai()
62 {
63 raidflag = false;
64 }
65
66
67 // returns a general description of what the jovian should do
68 // (MOVE, SHOOT, LEAP, NONE)
69
pick_action()70 Action Jovian::pick_action()
71 {
72 Point base_pos, endv_pos, jov_pos; // Positions of base, endever and this
73 // jovian.
74 long base_sqdist, endv_sqdist; // squares of distances to base and endever
75
76 float fight_speed, raid_speed, leap_speed; // Rates of jovian actions based on
77 // skill level & c_jovian_ai.hh.
78 // fight_speed refers to when either
79 // the endever or a base being
80 // raided is in same sector as the
81 // jovian. raid_speed refers to
82 // when only a base is around.
83 // leap_speed refers to when no
84 // base/endever is around.
85
86 // fight_speed, raid_speed and leap_speed are determined as linear
87 // functions of skill level.
88
89 fight_speed = evallinear(gamestate.skill, MINSKILL, SLOWFIGHT,
90 MAXSKILL, FASTFIGHT);
91 raid_speed = evallinear(gamestate.skill, MINSKILL, SLOWRAID,
92 MAXSKILL, FASTRAID);
93 leap_speed = evallinear(gamestate.skill, MINSKILL, SLOWLEAP,
94 MAXSKILL, FASTLEAP);
95
96 // Query sector as to whether a base or the endever are around.
97
98 base_pos = universe[urow-1][ucol-1].getbase();
99 endv_pos = universe[urow-1][ucol-1].getendever();
100 jov_pos = center();
101
102 // Determine what to do (MOVE, SHOOT, LEAP, NONE). decision is based
103 // on whether no targets are around or if a base is around or if the
104 // endever is around or if both a base and the endever are around.
105
106 if (endv_pos.x >= 0 && base_pos.x >= 0) // both endever and base are around
107 {
108 if (israiding) // endever has arrived, raid over.
109 {
110 raidflag = false;
111 israiding = false;
112 }
113
114 // stuff for deciding to make retreats
115
116 if (retreat_flag) // once jov decides to retreat, stick to it
117 if (on_edge() &&
118 !shot.inprogress && // leaping now would leave an abandoned shot
119 f_rand() <= time_to_prob(RETREAT_SPEED))
120 return (LEAP);
121 else if (f_rand() <= time_to_prob(RETREAT_SPEED))
122 return (MOVE);
123 else
124 return (NONE);
125 else if (getthrusters() < RETREAT_THRUST &&
126 getwarpdrive() < RETREAT_WARP &&
127 getfasers() < RETREAT_FASER &&
128 getshields() < RETREAT_SHIELD &&
129 getwarpdrive() >= JOVMINWARPERG && // no retreat if no warp
130 (1 + rand()%100) <= RETREAT_PROB)
131 {
132 retreat_flag = true; // run away
133 forcedvert = false; // start with a blank slate when retreating
134 forcedhoriz = false; // ditto
135 mustmove = false; // ditto
136 if (on_edge() &&
137 !shot.inprogress && // leaping now would leave an abandoned shot
138 f_rand() <= time_to_prob(RETREAT_SPEED))
139 return (LEAP);
140 else if (f_rand() <= time_to_prob(RETREAT_SPEED))
141 return (MOVE);
142 else
143 return (NONE);
144 }
145
146 // stuff for deciding whether to attack
147
148 // put location and distance of nearest target in static storage
149 base_sqdist = sqdist(base_pos, jov_pos);
150 endv_sqdist = sqdist(endv_pos, jov_pos);
151
152 if (endv_sqdist <= base_sqdist) // choose endever as target
153 {
154 target = endv_pos;
155 sdist = endv_sqdist;
156 }
157 else // choose base as target
158 {
159 target = base_pos;
160 sdist = base_sqdist;
161 }
162
163 // make sure target selected before returning a forced decision
164
165 if (mustmove)
166 {
167 if (f_rand() <= time_to_prob(fight_speed))
168 return (MOVE); // forced move
169 else
170 return (NONE); // to slow things down
171 }
172
173 if (f_rand() <= time_to_prob(fight_speed)) // then either shoot or move
174 {
175 if (f_rand() <= pshoot())
176 return (SHOOT);
177 else
178 return (MOVE);
179 }
180 else
181 return (NONE);
182 }
183 else if (endv_pos.x >= 0 && base_pos.x < 0) // only endever is around
184 {
185 if (israiding) // endever has arrived & base gone, so raid over.
186 {
187 raidflag = false;
188 israiding = false;
189 }
190
191 // stuff for deciding to make retreats (copied from above)
192
193 if (retreat_flag) // once jov decides to retreat, stick to it
194 if (on_edge() &&
195 !shot.inprogress && // leaping now would leave an abandoned shot
196 f_rand() <= time_to_prob(RETREAT_SPEED))
197 return (LEAP);
198 else if (f_rand() <= time_to_prob(RETREAT_SPEED))
199 return (MOVE);
200 else
201 return (NONE);
202 else if (getthrusters() < RETREAT_THRUST &&
203 getwarpdrive() < RETREAT_WARP &&
204 getfasers() < RETREAT_FASER &&
205 getshields() < RETREAT_SHIELD &&
206 getwarpdrive() >= JOVMINWARPERG && // no retreat if no warp
207 (1 + rand()%100) <= RETREAT_PROB)
208 {
209 retreat_flag = true; // run away
210 forcedvert = false; // start with a blank slate when retreating
211 forcedhoriz = false; // ditto
212 mustmove = false; // ditto
213 if (on_edge() &&
214 !shot.inprogress && // leaping now would leave an abandoned shot
215 f_rand() <= time_to_prob(RETREAT_SPEED))
216 return (LEAP);
217 else if (f_rand() <= time_to_prob(RETREAT_SPEED))
218 return (MOVE);
219 else
220 return (NONE);
221 }
222
223 // stuff for deciding whether to attack
224
225 // put in static storage
226 target = endv_pos;
227 sdist = sqdist(endv_pos, jov_pos);
228
229 // make sure target selected before returning a forced decision
230
231 if (mustmove)
232 {
233 if (f_rand() <= time_to_prob(fight_speed))
234 return (MOVE); // forced move
235 else
236 return (NONE); // to slow things down
237 }
238
239 if (f_rand() <= time_to_prob(fight_speed)) // then either shoot or move
240 {
241 if (f_rand() <= pshoot())
242 return (SHOOT);
243 else
244 return (MOVE);
245 }
246 else
247 return (NONE);
248 }
249 else if (endv_pos.x < 0 && base_pos.x >= 0) // only base is around
250 {
251 if (retreat_flag) // jov out of danger now
252 {
253 retreat_flag = false;
254 forcedvert = false; // start with a blank slate now jov is safe
255 forcedhoriz = false; // ditto
256 mustmove = false; // ditto
257 }
258
259 // put in static storage
260 target = base_pos;
261 sdist = sqdist(base_pos, jov_pos);
262
263 // make sure target selected before returning a forced decision
264
265 if (mustmove)
266 {
267 if (f_rand() <= time_to_prob(fight_speed))
268 return (MOVE); // forced move
269 else
270 return (NONE); // to slow things down
271 }
272
273 // If raidflag is already set, then skip this.
274 // divide the probability by Jovian::raidable since we are allowing
275 // at most one jovian to mount a raid on a given call on jovian_to().
276
277 if (!raidflag && (f_rand() <= time_to_prob(raid_speed)/raidable))
278 {
279 raidflag = true;
280 israiding = true;
281 }
282
283 if (israiding) // this jov is attacking a base
284 {
285 if (f_rand() <= time_to_prob(fight_speed)) // either shoot or move
286 {
287 if (f_rand() <= pshoot())
288 return (SHOOT);
289 else
290 return (MOVE);
291 }
292 else
293 return (NONE);
294 }
295 else
296 return (NONE);
297 }
298 else // no targets around
299 {
300 if (israiding) // base is gone, raid over.
301 {
302 raidflag = false;
303 israiding = false;
304 }
305
306 if (retreat_flag) // jov out of danger now
307 {
308 retreat_flag = false;
309 forcedvert = false; // start with a blank slate now jov is safe
310 forcedhoriz = false; // ditto
311 mustmove = false; // ditto
312 }
313
314 if (leapflag) // a jovian already leaped on this call to
315 return (NONE); // jovian_to(); so this jovian cannot leap.
316
317 // assign values to static storage indicating no targets around
318 target.x = -1;
319 target.y = -1;
320 sdist = -1;
321
322 // divide the probability by Jovian::leapable since we are allowing
323 // at most one jovian to leap on a given call on jovian_to().
324
325 if (f_rand() <= time_to_prob(leap_speed)/leapable)
326 {
327 leapflag = true;
328 return (LEAP);
329 }
330 else
331 return (NONE);
332 }
333 }
334
335
pick_direction()336 Direction Jovian::pick_direction()
337 {
338 Direction horiz, vert;
339 Point jov_pos = center();
340
341 static int horizcount; // must move FORCEDHORIZ steps horizontally
342 // to clear an obstacle
343 static Direction horizdir; // move in this consistent horiz direction
344 // until the obstacle is cleared.
345
346 static int vertcount; // Similar to horizcount, but FORCEDVERT
347 static Direction vertdir; // Similar to horizdir
348
349
350 if (retreat_flag)
351 return (retreat_direction());
352
353 // determine which horizontal direction to prefer
354
355 switch (sgn(target.x - jov_pos.x))
356 {
357 case -1: // target is to left
358 horiz = LEFT;
359 break;
360 case 0:
361 if (mustmove) // must move horizontal to clear obstacle
362 {
363 forcedhoriz = true;
364 horizcount = FORCEDHORIZ;
365 horizdir = (rand()%2)? LEFT:RIGHT; // randomly select a horiz preference
366 return (horizdir);
367 }
368 else
369 horiz = STAY;
370 break;
371 case 1: // target is to right
372 horiz = RIGHT;
373 break;
374 }
375
376 // determine which vertical direction to prefer
377
378 switch (sgn(target.y - jov_pos.y))
379 {
380 case -1: // target is to left
381 vert = UP;
382 break;
383 case 0:
384 if (mustmove) // must move vertical to clear obstacle
385 {
386 forcedvert = true;
387 vertcount = FORCEDVERT;
388 vertdir = (rand()%2)? UP:DOWN; // randomly select a vert preference
389 return (vertdir);
390 }
391 else
392 vert = STAY;
393 break;
394 case 1: // target is to right
395 vert = DOWN;
396 break;
397 }
398
399 // how to respond when jovian is blocked by a star or other jovian
400
401 if (forcedhoriz)
402 {
403 if (--horizcount == 1) // keep moving horizontally in some consistent
404 forcedhoriz = false; // direction until the obstacle is passed.
405 return (horizdir);
406 }
407 else if (forcedvert)
408 {
409 if (--vertcount == 1) // keep moving vertically in some consistent
410 forcedvert = false; // direction until the obstacle is passed.
411 return (vertdir);
412 }
413
414 // decide whether to go vertical or horizontal when not blocked by obstacle
415
416 if (horiz == STAY) // not random
417 return (vert);
418 else if (vert == STAY) // not random
419 return (horiz);
420 else
421 return ((rand()%2)? horiz:vert);
422 }
423
424
425 // returns a sector for the jovian to leap to or {-1, -1} if no available
426 // sectors exist.
427
pick_sector()428 Ucoors Jovian::pick_sector()
429 {
430 Ucoors dests[UROWS*UCOLS]; // Destinations:
431 // The set of all available sectors to leap to.
432 // A sector is "available" if it contains a
433 // base or contains the endever. Also, to be
434 // "available", a sector must contain less
435 // than MAXJOVSECT jovians.
436
437 Ucoors nowhere = {-1, -1}; // Return this if no available sectors.
438 int ndests = 0; // The number of available destinations.
439 int i, j;
440
441 // look for available sectors
442
443 if (retreat_flag) // find sectors not containing the endever
444 {
445 for (i = 0; i < UROWS; i++)
446 {
447 for (j = 0; j < UCOLS; j++)
448 {
449 if (universe[i][j].getendever().x < 0 &&
450 universe[i][j].getjovpop() < MAXJOVSECT) // no endever & few jovs
451 {
452 dests[ndests].urow = i+1;
453 dests[ndests].ucol = j+1;
454 ndests++;
455 }
456 }
457 }
458 }
459 else // find sectors having bases and/or the endever
460 {
461 for (i = 0; i < UROWS; i++)
462 {
463 for (j = 0; j < UCOLS; j++)
464 {
465 if ((universe[i][j].getbase().x < 0) &&
466 (universe[i][j].getendever().x < 0)) // no base and no endever
467 {
468 continue;
469 }
470 else if (universe[i][j].getjovpop() < MAXJOVSECT)
471 {
472 dests[ndests].urow = i+1; // i,j is an available sector
473 dests[ndests].ucol = j+1;
474 ndests++;
475 }
476 }
477 }
478 }
479
480 if (ndests == 0)
481 return (nowhere);
482 else
483 return (dests[rand() % ndests]);
484 }
485
486
487 // This function actually does nothing. pick_action() computes the nearest
488 // target and stores its location staticly in "target".
489
pick_target()490 Point Jovian::pick_target()
491 {
492 return (target);
493 }
494
495
496 // Decide what direction to move in when jovian is in retreat mode.
497
retreat_direction()498 Direction Jovian::retreat_direction()
499 {
500 Direction horiz, vert; // directions to closest horiz/vert sector edges
501 int horiz_dist, vert_dist; // shortest horiz/vert distances to sector edge
502 static Direction dir; // for saving the previous movement direction
503 static int horizcount; // must move icon_len steps horizontally
504 // to clear an obstacle
505
506 // find direction giving shortest path to the edge of the sector
507
508 horiz = RIGHT;
509 horiz_dist = SECTCOLS - 2 - col; // number steps needed to RIGHT to escape
510 if (col - 1 < horiz_dist)
511 {
512 horiz = LEFT;
513 horiz_dist = col - 1;
514 }
515
516 vert = DOWN;
517 vert_dist = SECTROWS - 1 - row; // number steps needed DOWN to escape
518 if (row < vert_dist)
519 {
520 vert = UP;
521 vert_dist = row;
522 }
523
524 // decide what direction to take
525
526 if (mustmove) // last retreat movement got blocked
527 {
528 forcedhoriz = false;
529 if (dir == LEFT || dir == RIGHT) // last movement was horizontal
530 {
531 dir = (rand()%2)? UP:DOWN; // randomly pick up or down
532 }
533 else // last move was vert. move horiz icon_len steps to clear obstacle.
534 {
535 forcedhoriz = true; // flag to ensure consistent direction next time
536 horizcount = 1; // count icon_len steps
537 dir = (rand()%2)? LEFT:RIGHT; // randomly pick horiz direction
538 }
539 }
540 else if (forcedhoriz) // move horiz some consistent direction for 3 steps
541 {
542 horizcount++;
543 if (horizcount > icon_len) // should have cleared obstacle by now
544 {
545 forcedhoriz = false;
546 dir = (vert_dist < horiz_dist)? vert:horiz;
547 }
548 }
549 else // no obstacles. move toward nearest sector edge.
550 {
551 dir = (vert_dist < horiz_dist)? vert:horiz;
552 }
553
554 return (dir);
555 }
556
557
558
559
560 // Return a float in [0,1] representing the probability that the
561 // jovian will shoot at the target determined in pick_action()
562 // and stored staticly in "target". This probability is modeled
563 // as a cubic polynomial function of the squared distance
564 // between the jovian and its target. A cubic Lagrange
565 // polynomial is fit through four points in the plane whose y
566 // coors represent the probability of shooting (0 to 1) and
567 // whose x coors are the squared euclidean distances from
568 // the jovian to the potential target (x ranges from 0 to
569 // SECTDIAGSQ).
570
pshoot(void)571 static float pshoot(void)
572 {
573 // the four Lagrange interpolation points
574
575 FPoint p0 = {P0_X, P0_Y};
576 FPoint p1 = {P1_X, P1_Y};
577 FPoint p2 = {P2_X, P2_Y};
578 FPoint p3 = {P3_X, P3_Y};
579
580 float d = (float)sdist; // square of distance between jovian and target
581 float prob; // the return value
582
583 // evaluate Lagrange interpolating polynomial at d
584
585 prob = p0.y*((d - p1.x)/(p0.x - p1.x))*((d - p2.x)/(p0.x - p2.x))*
586 ((d - p3.x)/(p0.x - p3.x)) +
587 p1.y*((d - p0.x)/(p1.x - p0.x))*((d - p2.x)/(p1.x - p2.x))*
588 ((d - p3.x)/(p1.x - p3.x)) +
589 p2.y*((d - p0.x)/(p2.x - p0.x))*((d - p1.x)/(p2.x - p1.x))*
590 ((d - p3.x)/(p2.x - p3.x)) +
591 p3.y*((d - p0.x)/(p3.x - p0.x))*((d - p1.x)/(p3.x - p1.x))*
592 ((d - p2.x)/(p3.x - p2.x));
593
594 return (prob);
595 }
596
597 // end
598