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