/* c_jovian_ai.cc 1.14 95/12/28 00:41:30 */ // xspacewarp by Greg Walker (gow@math.orst.edu) // This is free software. Non-profit redistribution and/or modification // is allowed and welcome. // These are the "artificial intelligence" routines for jovians. Any // upgrades of the Jovian AI should only require modifying the // functions in this file. // Probabilities are represented by floats in [0,1]. // // pick_action() just decides the general kind of action for the jovian to make // (MOVE, SHOOT, LEAP, NONE). After calling pick_action(), either // pick_direction(), pick_target(), or pick_sector() depending if pick_action() // returned MOVE, SHOOT, or LEAP respectively. // pick_direction(), pick_target(), and pick_sector() provide details on // what direction to move in, who to shoot, and what sector to go to. // After pick_direction(), pick_target() or pick_sector() are called, // the Decision struct gets filled in and is then ready to be passed to the // act() method. #include "c_jovian.hh" #include "c_jovian_ai.hh" // params and inlines used in these ai routines #include "c_sector.hh" #include #include // rand() #include "common.hh" #include "params.hh" #include "globals.hh" // access to universe[][] #include "space_objects.hh" // pick_action() fills in these values: static Point target; // the nearest target or {-1, -1} if no target static long sdist; // square of distance to nearest target or -1 // returns probability of shooting target static float pshoot(void); // initialize the non-static AI specific data void Jovian::init_ai_flags() { mustmove = false; retreat_flag = false; forcedvert = false; forcedhoriz = false; israiding = false; } // initialize the static AI specific data void Jovian::init_ai() { raidflag = false; } // returns a general description of what the jovian should do // (MOVE, SHOOT, LEAP, NONE) Action Jovian::pick_action() { Point base_pos, endv_pos, jov_pos; // Positions of base, endever and this // jovian. long base_sqdist, endv_sqdist; // squares of distances to base and endever float fight_speed, raid_speed, leap_speed; // Rates of jovian actions based on // skill level & c_jovian_ai.hh. // fight_speed refers to when either // the endever or a base being // raided is in same sector as the // jovian. raid_speed refers to // when only a base is around. // leap_speed refers to when no // base/endever is around. // fight_speed, raid_speed and leap_speed are determined as linear // functions of skill level. fight_speed = evallinear(gamestate.skill, MINSKILL, SLOWFIGHT, MAXSKILL, FASTFIGHT); raid_speed = evallinear(gamestate.skill, MINSKILL, SLOWRAID, MAXSKILL, FASTRAID); leap_speed = evallinear(gamestate.skill, MINSKILL, SLOWLEAP, MAXSKILL, FASTLEAP); // Query sector as to whether a base or the endever are around. base_pos = universe[urow-1][ucol-1].getbase(); endv_pos = universe[urow-1][ucol-1].getendever(); jov_pos = center(); // Determine what to do (MOVE, SHOOT, LEAP, NONE). decision is based // on whether no targets are around or if a base is around or if the // endever is around or if both a base and the endever are around. if (endv_pos.x >= 0 && base_pos.x >= 0) // both endever and base are around { if (israiding) // endever has arrived, raid over. { raidflag = false; israiding = false; } // stuff for deciding to make retreats if (retreat_flag) // once jov decides to retreat, stick to it if (on_edge() && !shot.inprogress && // leaping now would leave an abandoned shot f_rand() <= time_to_prob(RETREAT_SPEED)) return (LEAP); else if (f_rand() <= time_to_prob(RETREAT_SPEED)) return (MOVE); else return (NONE); else if (getthrusters() < RETREAT_THRUST && getwarpdrive() < RETREAT_WARP && getfasers() < RETREAT_FASER && getshields() < RETREAT_SHIELD && getwarpdrive() >= JOVMINWARPERG && // no retreat if no warp (1 + rand()%100) <= RETREAT_PROB) { retreat_flag = true; // run away forcedvert = false; // start with a blank slate when retreating forcedhoriz = false; // ditto mustmove = false; // ditto if (on_edge() && !shot.inprogress && // leaping now would leave an abandoned shot f_rand() <= time_to_prob(RETREAT_SPEED)) return (LEAP); else if (f_rand() <= time_to_prob(RETREAT_SPEED)) return (MOVE); else return (NONE); } // stuff for deciding whether to attack // put location and distance of nearest target in static storage base_sqdist = sqdist(base_pos, jov_pos); endv_sqdist = sqdist(endv_pos, jov_pos); if (endv_sqdist <= base_sqdist) // choose endever as target { target = endv_pos; sdist = endv_sqdist; } else // choose base as target { target = base_pos; sdist = base_sqdist; } // make sure target selected before returning a forced decision if (mustmove) { if (f_rand() <= time_to_prob(fight_speed)) return (MOVE); // forced move else return (NONE); // to slow things down } if (f_rand() <= time_to_prob(fight_speed)) // then either shoot or move { if (f_rand() <= pshoot()) return (SHOOT); else return (MOVE); } else return (NONE); } else if (endv_pos.x >= 0 && base_pos.x < 0) // only endever is around { if (israiding) // endever has arrived & base gone, so raid over. { raidflag = false; israiding = false; } // stuff for deciding to make retreats (copied from above) if (retreat_flag) // once jov decides to retreat, stick to it if (on_edge() && !shot.inprogress && // leaping now would leave an abandoned shot f_rand() <= time_to_prob(RETREAT_SPEED)) return (LEAP); else if (f_rand() <= time_to_prob(RETREAT_SPEED)) return (MOVE); else return (NONE); else if (getthrusters() < RETREAT_THRUST && getwarpdrive() < RETREAT_WARP && getfasers() < RETREAT_FASER && getshields() < RETREAT_SHIELD && getwarpdrive() >= JOVMINWARPERG && // no retreat if no warp (1 + rand()%100) <= RETREAT_PROB) { retreat_flag = true; // run away forcedvert = false; // start with a blank slate when retreating forcedhoriz = false; // ditto mustmove = false; // ditto if (on_edge() && !shot.inprogress && // leaping now would leave an abandoned shot f_rand() <= time_to_prob(RETREAT_SPEED)) return (LEAP); else if (f_rand() <= time_to_prob(RETREAT_SPEED)) return (MOVE); else return (NONE); } // stuff for deciding whether to attack // put in static storage target = endv_pos; sdist = sqdist(endv_pos, jov_pos); // make sure target selected before returning a forced decision if (mustmove) { if (f_rand() <= time_to_prob(fight_speed)) return (MOVE); // forced move else return (NONE); // to slow things down } if (f_rand() <= time_to_prob(fight_speed)) // then either shoot or move { if (f_rand() <= pshoot()) return (SHOOT); else return (MOVE); } else return (NONE); } else if (endv_pos.x < 0 && base_pos.x >= 0) // only base is around { if (retreat_flag) // jov out of danger now { retreat_flag = false; forcedvert = false; // start with a blank slate now jov is safe forcedhoriz = false; // ditto mustmove = false; // ditto } // put in static storage target = base_pos; sdist = sqdist(base_pos, jov_pos); // make sure target selected before returning a forced decision if (mustmove) { if (f_rand() <= time_to_prob(fight_speed)) return (MOVE); // forced move else return (NONE); // to slow things down } // If raidflag is already set, then skip this. // divide the probability by Jovian::raidable since we are allowing // at most one jovian to mount a raid on a given call on jovian_to(). if (!raidflag && (f_rand() <= time_to_prob(raid_speed)/raidable)) { raidflag = true; israiding = true; } if (israiding) // this jov is attacking a base { if (f_rand() <= time_to_prob(fight_speed)) // either shoot or move { if (f_rand() <= pshoot()) return (SHOOT); else return (MOVE); } else return (NONE); } else return (NONE); } else // no targets around { if (israiding) // base is gone, raid over. { raidflag = false; israiding = false; } if (retreat_flag) // jov out of danger now { retreat_flag = false; forcedvert = false; // start with a blank slate now jov is safe forcedhoriz = false; // ditto mustmove = false; // ditto } if (leapflag) // a jovian already leaped on this call to return (NONE); // jovian_to(); so this jovian cannot leap. // assign values to static storage indicating no targets around target.x = -1; target.y = -1; sdist = -1; // divide the probability by Jovian::leapable since we are allowing // at most one jovian to leap on a given call on jovian_to(). if (f_rand() <= time_to_prob(leap_speed)/leapable) { leapflag = true; return (LEAP); } else return (NONE); } } Direction Jovian::pick_direction() { Direction horiz, vert; Point jov_pos = center(); static int horizcount; // must move FORCEDHORIZ steps horizontally // to clear an obstacle static Direction horizdir; // move in this consistent horiz direction // until the obstacle is cleared. static int vertcount; // Similar to horizcount, but FORCEDVERT static Direction vertdir; // Similar to horizdir if (retreat_flag) return (retreat_direction()); // determine which horizontal direction to prefer switch (sgn(target.x - jov_pos.x)) { case -1: // target is to left horiz = LEFT; break; case 0: if (mustmove) // must move horizontal to clear obstacle { forcedhoriz = true; horizcount = FORCEDHORIZ; horizdir = (rand()%2)? LEFT:RIGHT; // randomly select a horiz preference return (horizdir); } else horiz = STAY; break; case 1: // target is to right horiz = RIGHT; break; } // determine which vertical direction to prefer switch (sgn(target.y - jov_pos.y)) { case -1: // target is to left vert = UP; break; case 0: if (mustmove) // must move vertical to clear obstacle { forcedvert = true; vertcount = FORCEDVERT; vertdir = (rand()%2)? UP:DOWN; // randomly select a vert preference return (vertdir); } else vert = STAY; break; case 1: // target is to right vert = DOWN; break; } // how to respond when jovian is blocked by a star or other jovian if (forcedhoriz) { if (--horizcount == 1) // keep moving horizontally in some consistent forcedhoriz = false; // direction until the obstacle is passed. return (horizdir); } else if (forcedvert) { if (--vertcount == 1) // keep moving vertically in some consistent forcedvert = false; // direction until the obstacle is passed. return (vertdir); } // decide whether to go vertical or horizontal when not blocked by obstacle if (horiz == STAY) // not random return (vert); else if (vert == STAY) // not random return (horiz); else return ((rand()%2)? horiz:vert); } // returns a sector for the jovian to leap to or {-1, -1} if no available // sectors exist. Ucoors Jovian::pick_sector() { Ucoors dests[UROWS*UCOLS]; // Destinations: // The set of all available sectors to leap to. // A sector is "available" if it contains a // base or contains the endever. Also, to be // "available", a sector must contain less // than MAXJOVSECT jovians. Ucoors nowhere = {-1, -1}; // Return this if no available sectors. int ndests = 0; // The number of available destinations. int i, j; // look for available sectors if (retreat_flag) // find sectors not containing the endever { for (i = 0; i < UROWS; i++) { for (j = 0; j < UCOLS; j++) { if (universe[i][j].getendever().x < 0 && universe[i][j].getjovpop() < MAXJOVSECT) // no endever & few jovs { dests[ndests].urow = i+1; dests[ndests].ucol = j+1; ndests++; } } } } else // find sectors having bases and/or the endever { for (i = 0; i < UROWS; i++) { for (j = 0; j < UCOLS; j++) { if ((universe[i][j].getbase().x < 0) && (universe[i][j].getendever().x < 0)) // no base and no endever { continue; } else if (universe[i][j].getjovpop() < MAXJOVSECT) { dests[ndests].urow = i+1; // i,j is an available sector dests[ndests].ucol = j+1; ndests++; } } } } if (ndests == 0) return (nowhere); else return (dests[rand() % ndests]); } // This function actually does nothing. pick_action() computes the nearest // target and stores its location staticly in "target". Point Jovian::pick_target() { return (target); } // Decide what direction to move in when jovian is in retreat mode. Direction Jovian::retreat_direction() { Direction horiz, vert; // directions to closest horiz/vert sector edges int horiz_dist, vert_dist; // shortest horiz/vert distances to sector edge static Direction dir; // for saving the previous movement direction static int horizcount; // must move icon_len steps horizontally // to clear an obstacle // find direction giving shortest path to the edge of the sector horiz = RIGHT; horiz_dist = SECTCOLS - 2 - col; // number steps needed to RIGHT to escape if (col - 1 < horiz_dist) { horiz = LEFT; horiz_dist = col - 1; } vert = DOWN; vert_dist = SECTROWS - 1 - row; // number steps needed DOWN to escape if (row < vert_dist) { vert = UP; vert_dist = row; } // decide what direction to take if (mustmove) // last retreat movement got blocked { forcedhoriz = false; if (dir == LEFT || dir == RIGHT) // last movement was horizontal { dir = (rand()%2)? UP:DOWN; // randomly pick up or down } else // last move was vert. move horiz icon_len steps to clear obstacle. { forcedhoriz = true; // flag to ensure consistent direction next time horizcount = 1; // count icon_len steps dir = (rand()%2)? LEFT:RIGHT; // randomly pick horiz direction } } else if (forcedhoriz) // move horiz some consistent direction for 3 steps { horizcount++; if (horizcount > icon_len) // should have cleared obstacle by now { forcedhoriz = false; dir = (vert_dist < horiz_dist)? vert:horiz; } } else // no obstacles. move toward nearest sector edge. { dir = (vert_dist < horiz_dist)? vert:horiz; } return (dir); } // Return a float in [0,1] representing the probability that the // jovian will shoot at the target determined in pick_action() // and stored staticly in "target". This probability is modeled // as a cubic polynomial function of the squared distance // between the jovian and its target. A cubic Lagrange // polynomial is fit through four points in the plane whose y // coors represent the probability of shooting (0 to 1) and // whose x coors are the squared euclidean distances from // the jovian to the potential target (x ranges from 0 to // SECTDIAGSQ). static float pshoot(void) { // the four Lagrange interpolation points FPoint p0 = {P0_X, P0_Y}; FPoint p1 = {P1_X, P1_Y}; FPoint p2 = {P2_X, P2_Y}; FPoint p3 = {P3_X, P3_Y}; float d = (float)sdist; // square of distance between jovian and target float prob; // the return value // evaluate Lagrange interpolating polynomial at d prob = p0.y*((d - p1.x)/(p0.x - p1.x))*((d - p2.x)/(p0.x - p2.x))* ((d - p3.x)/(p0.x - p3.x)) + p1.y*((d - p0.x)/(p1.x - p0.x))*((d - p2.x)/(p1.x - p2.x))* ((d - p3.x)/(p1.x - p3.x)) + p2.y*((d - p0.x)/(p2.x - p0.x))*((d - p1.x)/(p2.x - p1.x))* ((d - p3.x)/(p2.x - p3.x)) + p3.y*((d - p0.x)/(p3.x - p0.x))*((d - p1.x)/(p3.x - p1.x))* ((d - p2.x)/(p3.x - p2.x)); return (prob); } // end