Logo Search packages:      
Sourcecode: xconq version File versions  Download package

mplayer.c

/* Implementation of the "mplayer" AI in Xconq.
   Copyright (C) 1987-1989, 1991-2000 Stanley T. Shebs.

Xconq is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.  See the file COPYING.  */

#include "conq.h"
#include "kpublic.h"
#include "ai.h"

extern void register_mplayer(AI_ops *ops);

static Theater *tmptheater;

static Side *anewside;

/* Local function declarations. */

static void mplayer_init(Side *side);
static void mplayer_init_turn(Side *side);
static void mplayer_create_strategy(Side *side);
static void reset_strategy(Side *side);
static void analyze_the_game(Side *side);
static void determine_subgoals(Side *side);
static void review_theaters(Side *side);
static void create_initial_theaters(Side *side);
static Theater *create_theater(Side *side);
static void remove_theater(Side *side, Theater *theater);
static void move_theater_cell(int x, int y);
static void remove_small_theaters(Side *side);
static void compute_theater_bounds(Side *side);
static void review_goals(Side *side);
static void mplayer_review_units(Side *side);
static void update_side_strategy(Side *side);
static void decide_theater_needs(Side *side, Theater *theater);
static void estimate_strengths(Side *side);
static void decide_resignation(Side *side);
static void add_goal(Side *side, Goal *goal);
static Goal *has_goal(Side *side, GoalType goaltype);
static Goal *has_unsatisfied_goal(Side *side, GoalType goaltype);
static void mplayer_decide_plan(Side *side, Unit *unit);
static int mplayer_adjust_plan(Side *side, Unit *unit);
static int need_explorers(Side *side);
static void mplayer_react_to_task_result(Side *side, Unit *unit, Task *task, TaskOutcome rslt);
static void change_to_adjacent_theater(Side *side, Unit *unit);
static void mplayer_react_to_new_side(Side *side, Side *side2);
static void mplayer_finish_movement(Side *side);
static Unit *search_for_available_transport(Unit *unit, int purpose);
static void mplayer_rethink_plan(Unit *unit);
static char *mplayer_at_desig(Side *side, int x, int y);
static int mplayer_theater_at(Side *side, int x, int y);
static int mplayer_read_strengths(Side *side);
static Obj *mplayer_save_state(Side *side);
#if 0
static void mplayer_react_to_unit_loss(Side *side, Unit *unit);
#endif

void
register_mplayer(AI_ops *ops)
{
    ops->name = "mplayer";
    ops->help = "general AI that can play any game";
    ops->to_init = mplayer_init;
    ops->to_init_turn = mplayer_init_turn;
    ops->to_decide_plan = mplayer_decide_plan;
    ops->to_react_to_task_result = mplayer_react_to_task_result;
    ops->to_react_to_new_side = mplayer_react_to_new_side;
    ops->to_adjust_plan = mplayer_adjust_plan;
    ops->to_finish_movement = mplayer_finish_movement;
    ops->to_save_state = mplayer_save_state;
    ops->region_at = mplayer_theater_at;
    ops->at_desig = mplayer_at_desig;
};

static void
mplayer_init(Side *side)
{
    Unit *unit;

    if (game_class == gc_none) {
      game_class = find_game_class();
    }

    /* Delete any old strategy object in case we just switched AI type. */
    if (side->ai != NULL) {
      free(side->ai);
      side->ai = NULL;
    }    
    /* Then always create a new strategy from scratch. */
    mplayer_create_strategy(side);
    /* If the side has no units at the moment, it doesn't really need to
       plan. */
    if (!side_has_units(side))
      return;
    /* Compute an initial estimation of units on each side. */
    /* (Needed for save/restore consistency, otherwise not
       critical to do here.) */
    estimate_strengths(side);
    /* Study the scorekeepers and such, decide how to play the game. */
    analyze_the_game(side);
    /* Reset plans of any units that were not doing anything. */
    for_all_side_units(side, unit) {
      if (in_play(unit) && ai_controlled(unit)) {
          net_force_replan(side, unit, TRUE);
      }
    }
}

/* At the beginning of each turn, make plans and review the situation. */

static void
mplayer_init_turn(Side *side)
{
    int u, u2;

    /* Cases where we no longer need to run. */
    if (!side->ingame)
      return;
    /* A side without units hasn't got anything to do but wait. */
    /* (should account for possible units on controlled sides) */
    if (!side_has_units(side))
      return;
    /* Mplayers in a hacked game will not play,
       unless they're being debugged. */
    if (compromised && !DebugM)
      return;
    update_all_progress_displays("ai turn init start", side->id);
    DMprintf("%s mplayer init turn\n", side_desig(side));
    /* Make sure a strategy object exists. */
    if (ai(side) == NULL)
      mplayer_create_strategy(side);
    /* Look over the game design we're playing with. */
    analyze_the_game(side);

    if (ai(side)->report_not_understood) {
      notify_all("%s AI doesn't understand scoring in this game!", short_side_title(side));
      ai(side)->report_not_understood = FALSE;
    }

    /* code specific to the "time" game */
    if (game_class == gc_time) {
      for_all_unit_types(u) {
          if (ai(side)->develop_status[u] == RS_DEVELOP_ASSIGNED) {
            u2 = ai(side)->develop_on[u];
            if (!needs_develop (side, u2)) {
                /* develop done, start upgrading */
                DMprintf("%s has completed develop on %s\n",
                       side_desig(side), u_type_name(u2));
                ai(side)->develop_status[u] = RS_UPGRADE_NEEDED;
            }
          }
      }
    }

    /* If this game is one that can be won, as opposed to
       just dinking around, figure how to win it. */
    if (ai(side)->trytowin) {
      /* Check out the current goal tree first. */
      review_goals(side);
      /* Goal analysis might have triggered resignation. */
      if (!side->ingame)
        goto done;
      /* Check out all the theaters. */
      review_theaters(side);
      /* Check out all of our units. */
      mplayer_review_units(side);
      /* (should be integrated better) */
      mplayer_finish_movement(side);
      /* Decide on the new current plan. */
      update_side_strategy(side);
      /* Propagate this to individual unit plans. */
      update_unit_plans(side);
    } else {
      update_unit_plans_randomly(side);
    }
  done:
    update_all_progress_displays("", side->id);
    DMprintf("%s mplayer init turn done\n", side_desig(side));
}

/* Create and install an entirely new strategy object for the side. */

void
mplayer_create_strategy(Side *side)
{
    Strategy *strategy = (Strategy *) xmalloc(sizeof(Strategy));

    /* Put the specific structure into a generic slot. */
    side->ai = (struct a_ai *) strategy;
    /* Allocate a table of pointers to theaters, for access via small numbers
       rather than full pointers. */
    strategy->theatertable = (Theater **) xmalloc(127 * sizeof(Theater *));
    /* Allocate a layer of indexes into the theater table. */
    strategy->areatheaters = malloc_area_layer(char);
    /* Allocate random things. */
    /* Arrays for unit types. */
    strategy->actualmix = (short *) xmalloc(numutypes * sizeof(short));
    strategy->expectedmix = (short *) xmalloc(numutypes * sizeof(short));
    strategy->idealmix = (short *) xmalloc(numutypes * sizeof(short));
    strategy->develop_status = (short *) xmalloc(numutypes * sizeof(short));
    strategy->develop_on     = (short *) xmalloc(numutypes * sizeof(short));
    /* Arrays for terrain types. */
    strategy->terrainguess = (short *) xmalloc(numttypes * sizeof(short));
    strategy->writable_state = lispnil;
    /* Set everything to correct initial values. */
    reset_strategy(side);
}

/* Put all the right initial values into the strategy, but don't allocate anything. */

static void
reset_strategy(Side *side)
{
    int u, u2, t, dir;
    Strategy *strategy = (Strategy *) side->ai;

    /* Remember when we did this. */
    strategy->creationdate = g_turn();
    /* Null out various stuff. */
    strategy->numgoals = 0;
    strategy->theaters = NULL;
    /* Actually we start with no theaters, but it's convenient to leave entry 0
       in the theater table pointing to NULL. */
    strategy->numtheaters = 1;
    /* Clear pointers to special-purpose theaters. */
    strategy->homefront = NULL;
    for_all_directions(dir) {
      strategy->perimeters[dir] = NULL;
      strategy->midranges[dir] = NULL;
      strategy->remotes[dir] = NULL;
    }
    strategy->explorersneeded = 0;
    /* Reset the summation of our exploration needs. */
    for_all_unit_types(u) {
      strategy->actualmix[u] = 0;
      strategy->expectedmix[u] = 0;
      strategy->idealmix[u] = 0;
      strategy->develop_status[u] = 0;
      strategy->develop_on[u] = 0;

      /* code specific to the "time" game */
      if (game_class == gc_time) {
          for_all_unit_types(u2) {
            if (needs_develop (side, u2) && can_develop_on(u, u2)) {
                strategy->develop_status[u] = RS_DEVELOP_NEEDED;
                strategy->develop_on[u] = u2;
                DMprintf("%s can develop on %s (to level %d)\n",
                       u_type_name(u), u_type_name(u2),
                       u_tech_to_build(u2));
            }
          }
      }
    }
    for_all_terrain_types(t) {
      strategy->terrainguess[t] = 0;
    }
    strategy->analyzegame = TRUE;
    /* Analyze the game and decide our basic goals. */
    analyze_the_game(side);
}

/* Look over the game design and decide what we're supposed to be doing,
   if anything at all.  This just sets up toplevel goals based on the
   game design, does not evaluate goals or any such. */

static void
analyze_the_game(Side *side)
{
    int maybedraw, i;
    Goal *goal;

    if (ai(side)->analyzegame) {
      if (should_try_to_win(side)) {
          ai(side)->trytowin = TRUE;
          /* This is our whole purpose in the game. */
          goal = create_goal(GOAL_WON_GAME, side, TRUE);
          add_goal(side, goal);
          /* Now figure what exactly we have to do in order to win. */
          determine_subgoals(side);
          /* Machine will want to keep playing as long as it thinks
             it has a chance to win. */
          maybedraw = FALSE;
      } else {
          ai(side)->trytowin = FALSE;
          /* Since the side is not trying to win anything, it will be
             pretty laidback about whether to keep the game going. */
          maybedraw = TRUE;
      }
      /* Be trusting about game saves, at least for now. (The problem
         is that a human player could escape fate by saving the game
         and then either editing the saved game or just throwing it
         away.) */
      if (TRUE != side->willingtosave /* (should) and decision delegated to AI */)
        net_set_willing_to_save(side, TRUE);
      if (maybedraw != side->willingtodraw /* (should) and decision delegated to AI */)
        try_to_draw(side, maybedraw, "mplayer");
      ai(side)->analyzegame = FALSE;
      /* Summarize our analysis of this game. */
      DMprintf("%s will try to %s this game\n",
             side_desig(side),
             ai(side)->trytowin ? "win" : "have fun in");
      for (i = 0; i < ai(side)->numgoals; ++i) {
          goal = ai(side)->goals[i];
          DMprintf("%s has %s\n", side_desig(side), goal_desig(goal));
      }
    }
}

static void
determine_subgoals(Side *side)
{
    int numvicgoals, understood;
    Unit *unit;
    Side *side2;
    Scorekeeper *sk;
    Goal *goal;

    understood = TRUE;
    /* Look at each scorekeeper and decide on appropriate goals. */
    for_all_scorekeepers(sk) {
      /* (should test who scorekeeper applies to) */
      if (match_keyword(sk->body, K_LAST_SIDE_WINS)
          || match_keyword(sk->body, K_LAST_ALLIANCE_WINS)) {
          /* We want to "kick butt" - *everybody* else's butt. */
          for_all_sides(side2) {
            if (!trusted_side(side, side2) && side2->ingame) {
                /* Our goals include preventing other sides from accomplishing
                   theirs. */
                goal = create_goal(GOAL_WON_GAME, side2, FALSE);
                add_goal(side, goal);
                /* (should add "search-and-destroy" as corollaries) */
            }
          }
          /* Add goals to protect our own units. */
          numvicgoals = 0;
          for_all_side_units(side, unit) {
            if (point_value(unit) > 0  /* (should be "n most valuable") */
                && in_play(unit)
                && numvicgoals < 10) {
                goal = create_goal(GOAL_VICINITY_HELD, side, TRUE);
                goal->args[0] = unit->x;  goal->args[1] = unit->y;
                goal->args[2] = goal->args[3] = 2;
                add_goal(side, goal);
                ++numvicgoals;
            }
          }
      } else if (sk->initial != -10001) {
          /* This is a numerical scorekeeper whose value we want to maximize. */
          if (consp(sk->body) && match_keyword(car(sk->body), K_SET)) {
            if (consp(cadr(sk->body)) && match_keyword(car(cadr(sk->body)), K_SUM)) {
                understood = FALSE;
            } else {
                understood = FALSE;
            }
          } else {
            understood = FALSE;
          }
      } else {
          understood = FALSE;
      }
    }
    
    if (!understood) {
      /* Can't notify anybody yet, no windows up, so record */
      ai(side)->report_not_understood = TRUE;
      DMprintf("%s AI doesn't understand scoring in this game!", short_side_title(side));
    }
    /* We might develop a sudden interest in exploration. */
    /* (but should only be if information is really important to winning) */
    if (!side->see_all) {
      if (!g_terrain_seen()) {
          add_goal(side, create_goal(GOAL_WORLD_KNOWN, side, TRUE));
      }
      /* It will be important to keep track of other sides' units
         as much as possible. */
      for_all_sides(side2) {
          if (side != side2) {
            goal = create_goal(GOAL_POSITIONS_KNOWN, side, TRUE);
            goal->args[0] = (long) side2;
            add_goal(side, goal);
          }
      }
      /* Also add the general goal of knowing where indeps are. */
      goal = create_goal(GOAL_POSITIONS_KNOWN, side, TRUE);
      goal->args[0] = (long) NULL;
      add_goal(side, goal);
    }
}

/* Do a combination of analyzing existing theaters and creating new ones. */

static void
review_theaters(Side *side)
{
    int x, y, u, s, pop, totnumunits;
    int firstcontact = FALSE;
    int homefound = FALSE;
    Unit *unit;
    UnitView *uview;
    Side *firstcontactside, *homefoundside, *otherside, *side2;
    Theater *theater;

    /* Create some theaters if none exist. */
    if (ai(side)->theaters == NULL) {
      create_initial_theaters(side);
      compute_theater_bounds(side);
    }
    for_all_theaters(side, theater) {
      theater->allied_units = 0;
      theater->makers = 0;
      theater->unexplored = 0;
      theater->border = FALSE;
      theater->allied_bases = 0;
      for_all_unit_types(u) {
          theater->numassigned[u] = 0;
          theater->numneeded[u] = 0;
          theater->numenemies[u] = 0;
          theater->numsuspected[u] = theater->numsuspectedmax[u] = 0;
          theater->numtotransport[u] = 0;
      }
      if (people_sides_defined()) {
          for (s = 0; s <= numsides; ++s)
            theater->people[s] = 0;
      }
      theater->units_lost /= 2;
    }
    /* Now look at all the units that we can. */
    for_all_side_units(side, unit) {
      if (in_play(unit)) {
          theater = unit_theater(unit);
          if (theater != NULL) {
            ++(theater->allied_units);
            ++(theater->numassigned[unit->type]);
            if (isbase(unit))
              ++(theater->allied_bases);
            if (unit->plan
                && unit->plan->waitingfortransport)
              ++(theater->numtotransport[unit->type]);
          }
      }
    }
    /* (should also analyze allies etc) */
    /* Now look at the whole world. */
    for_all_interior_cells(x, y) {
      theater = theater_at(side, x, y);
      if (theater != NULL) {
          if (side->see_all) {
            /* We get to look at the real units. */
            for_all_stack(x, y, unit) {
                /* what about occupants? */
                if (in_play(unit)
                  && !trusted_side(side, unit->side)
                  && (!indep(unit)
                      || u_point_value(unit->type) > 0)) {
                  if (enemy_side(side, unit->side))
                    ++(theater->numenemies[unit->type]);
                  if (ai(side)->contacted[unit->side->id] == 0) {
                      ai(side)->contacted[unit->side->id] = 1;
                      if (!indep(unit)) {
                        firstcontact = TRUE;
                        firstcontactside = unit->side;
                      }
                  }
                  if (ai(side)->homefound[unit->side->id] == 0
                      && !mobile(unit->type)) {
                      ai(side)->homefound[unit->side->id] = 1;
                      if (!indep(unit)) {
                        homefound = TRUE;
                        homefoundside = unit->side;
                      }
                  }
                }
            }
            if (people_sides_defined()) {
                pop = people_side_at(x, y);
                if (pop != NOBODY) {
                  ++(theater->people[pop]);
                  if (ai(side)->homefound[pop] == 0) {
                      ai(side)->homefound[pop] = 1;
                      if (pop != 0) {
                        homefound = TRUE;
                        homefoundside = side_n(pop);
                      }
                  }
                }
            }
          } else {
            /* We must satisfy ourselves with unit views. */
            if (terrain_view(side, x, y) == UNSEEN) {
                ++(theater->unexplored);
            } else {
                for_all_view_stack(side, x, y, uview) {
                  side2 = view_side(uview);
                  if (enemy_side(side, side2)) {
                      /* Note that we assume indeps are enemies
                         here. */
                      u = view_type(uview);
                      if (u_point_value(u) > 0) {
                        ++(theater->numsuspected[u]);
                        ++(theater->numsuspectedmax[u]);
                      }
                  }
                }
                if (people_sides_defined()) {
                  pop = people_side_at(x, y);
                  if (pop != NOBODY) {
                      ++(theater->people[pop]);
                  }
                }
            }
          }
      }
    }
    for_all_theaters(side, theater) {
      theater->x = (theater->xmin + theater->xmax) / 2;
      theater->y = (theater->ymin + theater->ymax) / 2;
      theater->enemystrengthmin = theater->enemystrengthmax = 0;
      for_all_unit_types(u) {
          theater->enemystrengthmin +=
            theater->numenemies[u] + theater->numsuspected[u];
      }
      theater->enemystrengthmax = theater->enemystrengthmin;
    }
    if (firstcontact || homefound) {
      for_all_side_units(side, unit) {
          if (ai_controlled(unit)) {
            net_force_replan(side, unit, FALSE);
            set_unit_theater(unit, NULL);
            update_unit_display(side, unit, TRUE);
          }
      }
    }
    for_all_theaters(side, theater) {
      DMprintf("%s theater \"%s\" at %d,%d from %d,%d to %d,%d (size %d)\n",
             side_desig(side), theater->name, theater->x, theater->y,
             theater->xmin, theater->ymin, theater->xmax, theater->ymax,
             theater->size);
      /* Summarize what we know about the theater. */
      DMprintf("%s theater \"%s\"", side_desig(side), theater->name);
      if (!side->see_all && theater->unexplored > 0) {
          DMprintf(" unexplored %d", theater->unexplored);
      }
      DMprintf(" enemy %d", theater->enemystrengthmin);
      if (theater->enemystrengthmin != theater->enemystrengthmax) {
          DMprintf("-%d", theater->enemystrengthmax);
      }
      for_all_unit_types(u) {
          if (theater->numenemies[u] + theater->numsuspected[u] > 0) {
            DMprintf(" %3s %d", u_type_name(u), theater->numenemies[u]);
            if (theater->numsuspected[u] > 0) {
                DMprintf("+%d", theater->numsuspected[u]);
            }
          }
      }
      if (people_sides_defined()) {
          DMprintf(" people");
          for (s = 0; s <= numsides; ++s) {
            if (theater->people[s] > 0) {
                DMprintf(" s%d %d", s, theater->people[s]);
            }
          }
      }
      DMprintf("\n");
      totnumunits = 0;
      for_all_unit_types(u) {
          totnumunits +=
            (theater->numassigned[u] + theater->numneeded[u] + theater->numtotransport[u]);
      }
      if (totnumunits > 0) {
          /* Summarize the status of our own units in this theater. */
          DMprintf("%s theater \"%s\" has ", side_desig(side), theater->name);
          for_all_unit_types(u) {
            if (theater->numassigned[u] + theater->numneeded[u] + theater->numtotransport[u] > 0) {
                DMprintf(" %d %3s", theater->numassigned[u], u_type_name(u));
                  if (theater->numneeded[u] > 0) {
                      DMprintf(" (of %d needed)", theater->numneeded[u]);
                  }
                  if (theater->numtotransport[u] > 0) {
                      DMprintf(" (%d awaiting transport)", theater->numtotransport[u]);
                  }
            }
          }
          DMprintf("\n");
      }
    }
    /* Also summarize contacts. */
    for_all_sides(otherside) {
      if (otherside != side) {
          if (ai(side)->contacted[otherside->id]) {
            DMprintf("%s contacted s%d", side_desig(side), otherside->id);
            if (ai(side)->homefound[otherside->id]) {
                DMprintf(", home found");
            }
            DMprintf("\n");
          }
      }
    }
}

/* Set up the initial set of theaters. */

static void
create_initial_theaters(Side *side)
{
    int x, y, dir, dist, i, j;
    int xmin, ymin, xmax, ymax;
    int homeradius, perimradius, midradius, xxx;
    int numthx, numthy, thwid, thhgt;
    Unit *unit;
    Theater *homefront, *enemyarea, *theater;
    Theater *gridtheaters[8][8];
    Strategy *strategy = ai(side);
    
    for (i = 0; i < 8; ++i) {
      for (j = 0; j < 8; ++j) {
          gridtheaters[i][j] = NULL;
      }
    }
    /* Compute bbox of initial (should also do enemy?) units. */
    xmin = area.width;  ymin = area.height;  xmax = ymax = 0;
    for_all_side_units(side, unit) {
      if (alive(unit) /* and other preconditions? */) {
          if (unit->x < xmin)
            xmin = unit->x;
          if (unit->y < ymin)
            ymin = unit->y;
          if (unit->x > xmax)
            xmax = unit->x;
          if (unit->y > ymax)
            ymax = unit->y;
      }
    }
    /* Most games start with each side's units grouped closely together.
       If this is not the case, do something else. */
    if (xmax - xmin > area.width / 4 && ymax - ymin > area.height / 4) {
      /* (should do some sort of clustering of units) */
      if (0 /*people_sides_defined()*/) {
          homefront = create_theater(side);
          homefront->name = "Home Front";
          enemyarea = create_theater(side);
          enemyarea->name = "Enemy Area";
          for_all_interior_cells(x, y) {
              if (people_side_at(x, y) == side->id) {
                set_theater_at(side, x, y, homefront);
              } else {
                set_theater_at(side, x, y, enemyarea);
              }
          }
      } else {
          /* Divide the world up along a grid. */
          numthx = (area.width  > 60 ? (area.width  > 120 ? 7 : 5) : 3);
          numthy = (area.height > 60 ? (area.height > 120 ? 7 : 5) : 3);
          thwid = max(8, area.width / numthx);
          thhgt = max(8, area.height / numthy);
          for_all_interior_cells(x, y) {
            i = x / thwid;  j = y / thhgt;
            if (gridtheaters[i][j] == NULL) {
                theater = create_theater(side);
                sprintf(spbuf, "Grid %d,%d", i, j);
                theater->name = copy_string(spbuf);
                theater->x = x;  theater->y = y;
                gridtheaters[i][j] = theater;
            } else {
                theater = gridtheaters[i][j];
            }
            set_theater_at(side, x, y, theater);
          }
      }
      return;
    } else {
      /* Always create a first theater that covers the starting area. */
      homefront = create_theater(side);
      homefront->name = "Home Front";
      /* Calculate startxy if not already available. */
      if (side->startx < 0 && side->starty < 0)
        calc_start_xy(side);
      homefront->x = side->startx;  homefront->y = side->starty;
      strategy->homefront = homefront;
      homeradius = max(5, g_radius_min());
      perimradius = max(homeradius + 5, g_separation_min() - homeradius);
      midradius = max(perimradius + 10, g_separation_min() * 2);
      xxx = max((side->startx - perimradius), (area.width - side->startx - perimradius));
      xxx /= 2;
      midradius = min(midradius, perimradius + xxx);
      for_all_interior_cells(x, y) {
          dist = distance(x, y, side->startx, side->starty);
          if (people_sides_defined()
            && people_side_at(x, y) == side->id
            && dist < (perimradius - 3)) {
            set_theater_at(side, x, y, homefront);
          } else {
            if (dist < homeradius) {
                set_theater_at(side, x, y, homefront);
            } else {
                dir = approx_dir(x - side->startx, y - side->starty);
                if (dist < perimradius) {
                  if (strategy->perimeters[dir] == NULL) {
                      theater = create_theater(side);
                      sprintf(spbuf, "Perimeter %s", dirnames[dir]);
                      theater->name = copy_string(spbuf);
                      theater->x = x;  theater->y = y;
                      strategy->perimeters[dir] = theater;
                  } else {
                      theater = strategy->perimeters[dir];
                  }
                } else if (dist < midradius) {
                  if (strategy->midranges[dir] == NULL) {
                      theater = create_theater(side);
                      sprintf(spbuf, "Midrange %s", dirnames[dir]);
                      theater->name = copy_string(spbuf);
                      theater->x = x;  theater->y = y;
                      strategy->midranges[dir] = theater;
                  } else {
                      theater = strategy->midranges[dir];
                  }
                } else {
                  if (strategy->remotes[dir] == NULL) {
                      theater = create_theater(side);
                      sprintf(spbuf, "Remote %s", dirnames[dir]);
                      theater->name = copy_string(spbuf);
                      theater->x = x;  theater->y = y;
                      strategy->remotes[dir] = theater;
                  } else {
                      theater = strategy->remotes[dir];
                  }
                }
                set_theater_at(side, x, y, theater);
            }
          }
      }  
    }
    remove_small_theaters(side);
    /* Assign all units to the theater they're currently in. */
    /* (how do reinforcements get handled? mplayer should get hold of perhaps) */
    for_all_side_units(side, unit) {
      if (in_play(unit) /* and other preconditions? */) {
          set_unit_theater(unit, theater_at(side, unit->x, unit->y));
      }
    }
}

/* Create a single theater object and link it into the list of
   theaters. */

/* (should be able to re-use theaters in already in theater table) */

static Theater *
create_theater(Side *side)
{
    Theater *theater = (Theater *) xmalloc(sizeof(Theater));
    
    if (ai(side)->numtheaters > MAXTHEATERS)
      return NULL;
    theater->id = (ai(side)->numtheaters)++;
    theater->name = "?";
    theater->maingoal = NULL;
    theater->people = (int *) xmalloc ((numsides + 1) * sizeof(int));
    /* (should alloc other array slots too) */
    /* Connect theater into a linked list. */
    theater->next = ai(side)->theaters;
    ai(side)->theaters = theater;
    /* Install it into the theater table also. */
    ai(side)->theatertable[theater->id] = theater;
    return theater;
}

/* Clear all references to the theater and remove it from the list.
   Note that the theater size must already be zero. */

static void
remove_theater(Side *side, Theater *theater)
{
    int dir;
    Theater *prev;

    if (ai(side)->homefront == theater)
      ai(side)->homefront = NULL;
    for_all_directions(dir) {
      if (ai(side)->perimeters[dir] == theater)
        ai(side)->perimeters[dir] = NULL;
      if (ai(side)->midranges[dir] == theater)
        ai(side)->midranges[dir] = NULL;
      if (ai(side)->remotes[dir] == theater)
        ai(side)->remotes[dir] = NULL;
    }
    if (ai(side)->theaters == theater)
      ai(side)->theaters = theater->next;
    else {
      prev = NULL;
      for_all_theaters(side, prev) {
          if (prev->next == theater) {
            prev->next = theater->next;
            break;
          }
      }
      /* If prev still null, badness */
    }
    --(ai(side)->numtheaters);
}

static void
move_theater_cell(int x, int y)
{
    int dir, x1, y1;
    Theater *theater2;

    if (theater_at(tmpside, x, y) == tmptheater) {
      for_all_directions(dir) {
          if (interior_point_in_dir(x, y, dir, &x1, &y1)) {
            theater2 = theater_at(tmpside, x1, y1);
            if (theater2 != NULL && theater2 != tmptheater) {
                set_theater_at(tmpside, x, y, theater2);
                ++(theater2->size);
                /* (should recompute bbox too) */
                --(tmptheater->size);
            }
          }
      }
    }
}

static void
remove_small_theaters(Side *side)
{
    int domore;
    Theater *theater;

    compute_theater_bounds(side);
    domore = TRUE;
    while (domore) {
      domore = FALSE;
      for_all_theaters(side, theater) {
          if (between(1, theater->size, 5)) {
            tmpside = side;
            tmptheater = theater;
            apply_to_area(theater->x, theater->y, 6, move_theater_cell);
            if (theater->size == 0) {
                remove_theater(side, theater);
                /* Have to start over now. */
                domore = TRUE;
                break;
            }
          }
      }
    }
    /* Redo, many random changes to bounds. */
    compute_theater_bounds(side);
}

/* Compute the size and bounding box of each theater.  This should be run
   each time theaters change in size or shape. */

static void
compute_theater_bounds(Side *side)
{
    int x, y;
    Theater *theater;

    for_all_theaters(side, theater) {
      theater->size = 0;
      theater->xmin = theater->ymin = -1;
      theater->xmax = theater->ymax = -1;
    }
    for_all_interior_cells(x, y) {
      theater = theater_at(side, x, y);
      if (theater != NULL) {
          ++(theater->size);
          /* Compute bounding box of theater if not already done. */
          if (theater->xmin < 0 || x < theater->xmin)
            theater->xmin = x;
          if (theater->ymin < 0 || y < theater->ymin)
            theater->ymin = y;
          if (theater->xmax < 0 || x > theater->xmax)
            theater->xmax = x;
          if (theater->ymax < 0 || y > theater->ymax)
            theater->ymax = y;
      }
    }
}

/* Examine the goals to see what has been accomplished and what still
   needs to be done. */

static void
review_goals(Side *side)
{
    int i;
    Scorekeeper *sk;
    Goal *goal;
    Side *side2;
    Strategy *strategy = ai(side);

    /* First check on our friends and enemies. */
    for_all_sides(side2) {
      /* If they're not trusting us, we don't want to trust them. */
      /* (should be able to update this immediately after other side
           changes trust) */
      if (!trusted_side(side2, side) && trusted_side(side, side2))
        net_set_trust(side, side2, FALSE);
    }
    for (i = 0; i < strategy->numgoals; ++i) {
      goal = strategy->goals[i];
      DMprintf("%s has %s\n", side_desig(side), goal_desig(goal));
    }
    /* Should look at certainty of each goal and decide whether to keep or
       drop it, and mention in debug output also. */
    /* Also think about resigning. */
    if (g_ai_may_resign()
         && keeping_score()) {
      for_all_scorekeepers(sk) {
          if (symbolp(sk->body)
            && (match_keyword(sk->body, K_LAST_SIDE_WINS)
                || match_keyword(sk->body, K_LAST_ALLIANCE_WINS))) {
            decide_resignation(side);
          }
      }
    }
}

static void
estimate_strengths(Side *side)
{
    int u, sn1, x, y;
    Side *side1, *side2;
    Strategy *strategy = ai(side);
    Unit *unit;
    UnitView *uview;

    for_all_sides(side1) {
      sn1 = side1->id;
      for_all_unit_types(u) {
          strategy->strengths[sn1][u] = 0;
      }
      /* this lets us count even semi-trusted allies' units accurately... */
      if (side1 == side || allied_side(side, side1)) {
          for_all_side_units(side1, unit) {
            /* Note that we count off-area units, since they are
               reinforcements usually. */
            if (alive(unit) && completed(unit)) {
                ++(strategy->strengths[sn1][unit->type]);
            }
          }
      }
    }
    if (side->see_all
      || (!strategy->initial_strengths_computed 
          && !mplayer_read_strengths(side))) {
      /* If we can see everything, we can add up units accurately.
         We also allow initial strengths to be known accurately;
         this prevents the AI from resigning just because an
         important unit appears for the first time, even though all
         players know that it was one of the starting units. */
      for_all_cells(x, y) {
          for_all_stack(x, y, unit) {
            side2 = unit->side;
            if (side2 != NULL
                && !(side2 == side || allied_side(side, side2))) {
                if (completed(unit)) {
                    ++(strategy->strengths[side2->id][unit->type]);
                }
            }
          }
      }
    } else {
      /* Look at the current view to get enemy strength. */
      /* This is too easily faked, and doesn't know about hiding units... */
      /* Should also discount old data. */
      for_all_cells(x, y) {
          for_all_view_stack(side, x, y, uview) {
            side2 = view_side(uview);
            /* Count only units on other sides. */
            if (!(side2 == side || allied_side(side, side2)))
              ++(strategy->strengths[side2->id][view_type(uview)]);
          }
      }
    }
    /* Estimate point values. */
    /* (should try to account for individual units with special point
       values) */
    for_all_sides(side1) {
      sn1 = side1->id;
      strategy->points[sn1] = 0;
      for_all_unit_types(u) {
          strategy->points[sn1] +=
            strategy->strengths[sn1][u] * u_point_value(u);
      }
    }
    /* Estimate how many of each type in allied group. */
    for_all_sides(side1) {
      sn1 = side1->id;
      for_all_unit_types(u) {
          strategy->alstrengths[sn1][u] = strategy->strengths[sn1][u];
          for_all_sides(side2) {
            if (side1 != side2 && allied_side(side1, side2)) {
                strategy->alstrengths[sn1][u] +=
                  strategy->strengths[side2->id][u];
            }
          }
      }
      strategy->alpoints[sn1] = strategy->points[sn1];
      for_all_sides(side2) {
          if (side1 != side2 && allied_side(side1, side2)) {
            strategy->alpoints[sn1] += strategy->points[side2->id];
          }
      }
    }
    /* The first time we estimate strength, record it specially.
       Later we will use this to compute which sides are getting
       stronger/weaker as time goes on. */
    if (!strategy->initial_strengths_computed) {
      if (!mplayer_read_strengths(side)) {
          for_all_sides(side1) {
            sn1 = side1->id;
            strategy->points0[sn1] = strategy->points[sn1];
            strategy->alpoints0[sn1] = strategy->alpoints[sn1];
            for_all_unit_types(u) {
                strategy->strengths0[sn1][u] =
                  strategy->strengths[sn1][u];
                strategy->alstrengths0[sn1][u] =
                  strategy->alstrengths[sn1][u];
            }
          }
      }
      ai_save_state(side);
      strategy->initial_strengths_computed = TRUE;
    }
    /* If we're calling strength estimation because a new side has
       come into existence, use the current strengths as the new
       side's initial strengths. */
    if (anewside != NULL) {
      sn1 = anewside->id;
      strategy->points0[sn1] = strategy->points[sn1];
      strategy->alpoints0[sn1] = strategy->alpoints[sn1];
      for_all_unit_types(u) {
          strategy->strengths0[sn1][u] =
            strategy->strengths[sn1][u];
          strategy->alstrengths0[sn1][u] =
            strategy->alstrengths[sn1][u];
      }
      /* Have to redo the saveable state also; force this by
         blasting any existing recordable state (it should all be
         re-creatable from mplayer's internal state). */
      strategy->writable_state = lispnil;
      ai_save_state(side);
    }
    /* Dump out a detailed listing of our estimates. */
    if (DebugM) {
      for_all_sides(side1) {
          sn1 = side1->id;
          DMprintf("%s ", side_desig(side));
          DMprintf("est init streng of %s: ", side_desig(side1));
          for_all_unit_types(u) {
            DMprintf(" %d", strategy->strengths0[sn1][u]);
          }
          DMprintf(" (%d points)\n", strategy->points0[sn1]);
          DMprintf("%s ", side_desig(side));
          DMprintf("est curr streng of %s: ", side_desig(side1));
          for_all_unit_types(u) {
            DMprintf(" %d", strategy->strengths[sn1][u]);
          }
          DMprintf(" (%d points)\n", strategy->points[sn1]);
          DMprintf("%s ", side_desig(side));
          DMprintf("est init allied of %s: ", side_desig(side1));
          for_all_unit_types(u) {
            DMprintf(" %d", strategy->alstrengths0[sn1][u]);
          }
          DMprintf(" (%d points)\n", strategy->alpoints0[sn1]);
          DMprintf("%s ", side_desig(side));
          DMprintf("est curr allied of %s: ", side_desig(side1));
          for_all_unit_types(u) {
            DMprintf(" %d", strategy->alstrengths[sn1][u]);
          }
          DMprintf(" (%d points)\n", strategy->alpoints[sn1]);
      }
    }
}

/* Sometimes there is no point in going on, but be careful not to be
   too pessimistic.  Right now we only give up if no hope at all.
   Currently this is only used if there is a last-side-wins
   scorekeeper; it would need to be modified considerably to be useful
   with scorekeepers in general. */

static void
decide_resignation(Side *side)
{
    int sn, sn1, ratio, ratio0, chance = 0, chance1;
    Side *side1;
    Strategy *strategy = ai(side);

    /* Disable resignation during the first 10 turns. This fixes problem 
    with mplayer resigning at the start of the game if it has one settler
    (1 point) and some other side already built a city (10 points). */
    if (g_turn() < 10)
        return;
    sn = side->id;
    estimate_strengths(side);
    /* If our estimate of our own points is zero, then we're about to
       lose the game anyway, so just return and avoid screwing up
       ratio calcs below. */
    if (strategy->alpoints[sn] <= 0)
      return;
    for_all_sides(side1) {
      if (side != side1 && side1->ingame && !allied_side(side, side1)) {
          sn1 = side1->id;
          /* Note that ratio calculations always scale up by 100, so
             that we can do finer comparisons without needing
             floating point. */
          ratio = (strategy->alpoints[sn1] * 100) / strategy->alpoints[sn];
          /* If the AI can see all enemy units everywhere, it resigns much 
          too easily. (Iceland should not quit just because there are lots of
          Chineese troops in China) (should ultimately test for proximity 
          to enemy)*/
          if (side->see_all) {
            ratio /= 5;
          /* code specific to the "time" game */
          /* the mplayer can severely underestimates its own strength */
          } else if (game_class == gc_time) {
            ratio /= 3;
          }
          if (strategy->alpoints0[sn] > 0) {
            ratio0 =
              (strategy->alpoints0[sn1] * 100) / strategy->alpoints0[sn];
            /* If we estimated 0 points for some side's initial
               strength, then our estimate is bad; assume
               parity. */
            if (ratio0 <= 0)
              ratio0 = 100;
            /* This formula basically calls for no resignation if
               ratio is no more than twice what it was initially,
               50% chance if ratio is four times what it was (if
               we started out even, then we're outnumbered 4 to
               1), and interpolates for ratios in between. */
            chance1 = (((ratio * 100) / ratio0) - 200) / 5;
            chance1 = max(chance1, 0);
          } else {
            /* work by absolute ratios */
            if (ratio > 400) {
                chance1 = ratio / 10;
            }
          }
          /* The overall chance is determined by the most threatening
             side or alliance. */
          chance = max(chance, chance1);
      }
    }
    /* Never go all the way to 100%; perhaps the lopsided ratio is
       just a temporary setback. */
    chance = min(chance, 90);

    /* Whether or not we actually resign, we may be willing to go for
       a draw if other players want to. */
    /* (Note that toggling this flag is not exactly poker-faced
       behavior, but I doubt human players will be able to derive much
       advantage, since they'll already have a pretty good idea if the
       AI is in trouble or not.) */
    try_to_draw(side, (chance > 0), "mplayer");
    /* Maybe resign. */
    if (chance > 0) {
      if (probability(chance)) {
          give_up(side, "mplayer");
      }
    }
}

/* Go through all our units (and allied ones?). */

static void
mplayer_review_units(Side *side)
{
    int u, u2, cp, cpmin, any;
    int numoffensive[MAXUTYPES], numdefensive[MAXUTYPES];
    Unit *unit, *occ, *unit2;
    Plan *plan;
    Theater *oldtheater, *theater;

    /* This code is specific to the "time" game. */
    if (game_class == gc_time) {
      for_all_unit_types(u) {
          u2 = ai(side)->develop_on[u];
          if (ai(side)->develop_status[u] == RS_DEVELOP_ASSIGNED) {
            /* is anyone developing? */
            unit2 = NULL;
            for_all_side_units(side, unit) {
                if (unit->type==u && in_play(unit) &&
                  unit->plan && unit->plan->aicontrol) {
                  if (unit->plan->tasks &&
                      unit->plan->tasks->type == TASK_DEVELOP &&
                      unit->plan->tasks->args[0] == u2) 
                    unit2 = unit;
                }
            }
            if (unit2 != NULL) {
                DMprintf("%s is developing for %s on %s (level %d/%d)\n",
                       unit_desig(unit2), side_desig(side),
                       u_type_name(u2),
                       side->tech[u2], u_tech_to_build(u2));
            } else {
                DMprintf("no %s is developing for %s on %s!\n",
                       u_type_name(u), side_desig(side),
                       u_type_name(u2));
                ai(side)->develop_status[u] = RS_DEVELOP_NEEDED;
            }
          }
          if (ai(side)->develop_status[u] == RS_DEVELOP_NEEDED
            && needs_develop (side, u2)) {
            /* pick for develop a unit not building; 
               if all are building, choose the one which started last */
            unit2 = NULL;
            cpmin = 9999;
            any = 0;
            for_all_side_units(side, unit) {
                if (unit->type == u
                  && in_play(unit)
                  && unit->plan
                  && unit->plan->aicontrol) {
                  any = 1;
                  cp = 0;
                  occ = NULL;
                  if ((unit->plan->tasks != NULL
                       && unit->plan->tasks->type == TASK_BUILD))
                    occ = find_unit_to_complete(unit, unit->plan->tasks);
                  if (occ != NULL) {
                      cp = occ->cp - uu_creation_cp(u,occ->type);
                      if (uu_cp_per_build(u,u2) > 0)
                        cp /= uu_cp_per_build(u,u2);
                  }
                  if (cp < cpmin) {
                      unit2 = unit;
                      cpmin = cp;
                  }
                }
            }
            if (unit2 == NULL) {
                if (any)
                  DMprintf("no %s is available to develop for %s on %s!\n",
                         u_type_name(u), side_desig(side),
                         u_type_name(u2));
            } else {
                if (assign_to_develop_on(side, unit2, u2)) {
                  ai(side)->develop_status[u] =
                    RS_DEVELOP_ASSIGNED;
                }
            }
          }
      }
    }

    for_all_unit_types(u) {
      numoffensive[u] = numdefensive[u] = 0;
    }
    for_all_side_units(side, unit) {
      if (in_play(unit) && ai_controlled(unit)) {
          /* Count plan types. */
          switch (unit->plan->type) {
            case PLAN_OFFENSIVE:
            case PLAN_EXPLORATORY:
            ++numoffensive[unit->type];
            break;
            case PLAN_DEFENSIVE:
            ++numdefensive[unit->type];
            break;
            default:
              break;
          }
      }
    }

    for_all_side_units(side, unit) {
      if (in_play(unit) && ai_controlled(unit)) {
          /* code specific to the "time" game */
          if (game_class == gc_time) {
            u = unit->type;
            u2 = ai(side)->develop_on[u];

            /* should we upgrade? */
            if (ai(side)->develop_status[u] == RS_UPGRADE_NEEDED) {
                cp = 0;
                occ = NULL;
                if ((unit->plan->tasks != NULL &&
                   unit->plan->tasks->type == TASK_BUILD))
                  occ = find_unit_to_complete(unit, unit->plan->tasks);
                if (occ != NULL) {
                  cp = occ->cp - uu_creation_cp(u,occ->type);
                  if (uu_cp_per_build(u,u2)>0)
                    cp /= uu_cp_per_build(u,u2);
                }
                if (occ != NULL && occ->type==u2) {
                  /* already upgrading */
                  DMprintf("%s is upgrading to %s (%d/%d cp)\n",
                         unit_desig(unit), u_type_name(u2),
                         occ->cp, u_cp(occ->type));
                } else if (cp >= u_cp(u2)/4) { /* rule-of-thumb... */
                  /* complete unit under construction */
                  DMprintf("%s will complete %s (now %d/%d cp) before upgrading to %s\n",
                         unit_desig(unit), u_type_name(occ->type),
                         occ->cp, u_cp(occ->type), u_type_name(u2));
                } else {
                  /* start upgrading */
                  if (occ != NULL && !fullsized(occ)) {
                      DMprintf("%s will drop work on %s (%d/%d cp) and immediately start upgrading to %s\n",
                         unit_desig(unit), u_type_name(occ->type),
                         occ->cp, u_cp(occ->type), u_type_name(u2));
                  } else {
                      DMprintf("%s will start upgrading to %s\n",
                             unit_desig(unit), u_type_name(u2));
                  }
                  net_set_unit_plan_type(side, unit, PLAN_IMPROVING);
                  net_clear_task_agenda(side, unit);
                  net_set_build_task(unit, u2, 1, 0, 0);
                }
            }
          }

          plan = unit->plan;
          oldtheater = unit_theater(unit);
          /* Goal might have become satisfied. */
          if (plan->maingoal) {
            if (goal_truth(side, plan->maingoal) == 100) {
                DMprintf("%s %s satisfied, removing\n",
                       unit_desig(unit), goal_desig(plan->maingoal));
                net_force_replan(side, unit, FALSE);
                set_unit_theater(unit, NULL);
            }
          }
          /* Theater might have become explored enough (90% known). */
          if (plan->type == PLAN_EXPLORATORY
              && (theater = unit_theater(unit)) != NULL
              && theater->unexplored < theater->size / 10) {
                DMprintf("%s theater %s is mostly known\n",
                       unit_desig(unit), theater->name);
                net_force_replan(side, unit, FALSE);
                set_unit_theater(unit, NULL);
          }
          /* Don't let defense-only units pile up. */
          if (plan->type == PLAN_DEFENSIVE
            && mobile(unit->type)
            && (numoffensive[unit->type] / 3) < numdefensive[unit->type]
            /* However, don't mess with units that have specific defensive goals. */
            && (plan->maingoal == NULL
                  || (plan->maingoal->type != GOAL_UNIT_OCCUPIED
                   && plan->maingoal->type != GOAL_CELL_OCCUPIED))
            && flip_coin()) {
            DMprintf("%s one of too many on defense (%d off, %d def), replanning\n",
                   unit_desig(unit), numoffensive[unit->type], numdefensive[unit->type]);
            net_force_replan(side, unit, FALSE);
          }
          theater = unit_theater(unit);
          DMprintf("%s currently assigned to %s",
                 unit_desig(unit),
                 (theater ? theater->name : "no theater"));
          if (oldtheater != theater) {
            DMprintf(" (was %s)",
                   (oldtheater ? oldtheater->name : "no theater"));
          }
          DMprintf("\n");
      }
    }
}

/* Look at our current overall strategy and hack it as needed. */

static void
update_side_strategy(Side *side)
{
    Theater *theater;

    DMprintf("%s updating strategy\n", side_desig(side));
    /* Add something to add/update theaters as things open up. (?) */
    for_all_theaters(side, theater) {
      decide_theater_needs(side, theater);
    }
}

/* Figure out how many units to request for each area. */

static void
decide_theater_needs(Side *side, Theater *theater)
{
    if (theater->unexplored > 0) {
      /* Exploration is less important when 90% of a theater is known. */
      if (theater->unexplored > (theater->size / 10)) {
          ++(ai(side)->explorersneeded);
      }
      /* Should look for good exploration units. */
      theater->importance = 50;  /* should depend on context */
/*    theater->reinforce = EXPLORE_AREA;  */
#if 0
    } else if (0 /* few enemies? */) {
      if (theater->allied_makers == 0
          && theater->makers > 0
          && theater->nearby) {
          theater->reinforce = GUARD_BORDER_TOWN + 2 * theater->makers;
      } else if (theater->makers > 0) {
          theater->reinforce = (theater->border ? GUARD_BORDER_TOWN :
                          GUARD_TOWN) + 2 * theater->allied_makers;
      } else if (theater->allied_bases > 0) {
          theater->reinforce = (theater->border ? GUARD_BORDER: GUARD_BASE);
      } else if (theater->border) {
          theater->reinforce = NO_UNITS;
      } else {
          theater->reinforce = NO_UNITS;
      }
    } else {
      if (theater->allied_makers > 0) {
          theater->reinforce = DEFEND_TOWN + 5 * theater->makers;
      } else if (theater->allied_bases > 0) {
          theater->reinforce = DEFEND_BASE + theater->allied_bases;
      } else {
          theater->reinforce = 0 /* DEFEND_AREA */;
      }
#endif
    }
}

/* Push a new goal onto the side's list of goals. */

/* (this should only add goals that are not already present) */

static void
add_goal(Side *side, Goal *goal)
{
    if (ai(side)->numgoals < MAXGOALS) {
      ai(side)->goals[(ai(side)->numgoals)++] = goal;
      DMprintf("%s added %s\n", side_desig(side), goal_desig(goal));
    } else {
      DMprintf("%s has no room for %s\n",
             side_desig(side), goal_desig(goal));
    }
}

/* Return any goal of the given type. */

static Goal *
has_goal(Side *side, GoalType goaltype)
{
    int i;
    Goal *goal;

    for (i = 0; i < ai(side)->numgoals; ++i) {
      goal = ai(side)->goals[i];
      if (goal != NULL && goal->type == goaltype) {
          return goal;
      }
    }
    return NULL;
}

/* Return an unfulfilled goal of the given type. */

static Goal *
has_unsatisfied_goal(Side *side, GoalType goaltype)
{
    int i;
    Goal *goal;

    for (i = 0; i < ai(side)->numgoals; ++i) {
      goal = ai(side)->goals[i];
      if (goal != NULL && goal->type == goaltype
          && goal_truth(side, goal) < 100) {
          return goal;
      }
    }
    return NULL;
}

/* This is for when a unit needs a plan and asks its side for one. */

static void
mplayer_decide_plan(Side *side, Unit *unit)
{
    int u = unit->type;
    Plan *plan = unit->plan;
    Goal *goal;

    /* code specific to the "time" game */
    /* don't mess up with units developing or upgrading */
    if (game_class == gc_time) {
      if (ai(side)->develop_status[u] == RS_UPGRADE_NEEDED
          && plan->tasks != NULL
          && plan->tasks->type == TASK_BUILD)
        return;
      if (ai(side)->develop_status[u] == RS_DEVELOP_ASSIGNED
          && plan->tasks != NULL
          && plan->tasks->type == TASK_DEVELOP)
        return;
    }
    /* If we're not trying to win at this game, then make the unit act
       randomly. */
    if (!ai(side)->trytowin) {
      net_set_unit_plan_type(side, unit, PLAN_RANDOM);
      net_clear_task_agenda(side, unit);
      return;
    }

    switch (plan->type) {
      case PLAN_PASSIVE:
      case PLAN_NONE:
      if (mobile(u)) {
          if (u_colonizer_worth(u) > 0) {
            assign_to_colonize(side, unit);
            return;
          }
          /* Maybe assign to exploration. */
          if (has_goal(side, GOAL_WORLD_KNOWN)) {
            if (need_explorers(side) 
                && u_explorer_worth(u) > 0
                && flip_coin()) {
                /* also limit to a total percentage, in case
                   exploration needs are very high */
                assign_to_exploration(side, unit);
                return;
            }
          }
          /* Maybe assign to collecting materials. */
          if ((goal = has_unsatisfied_goal(side, GOAL_HAS_MATERIAL_TYPE))) {
            if (need_this_type_to_collect(side, u, goal->args[0])) {
                assign_to_collection(side, unit, goal->args[0]);
                return;
            }
          }
          /* Assign most units to offense, save some for defense. */
          if (u_offensive_worth(u) > 0
            && probability(75)) {
              assign_to_offense(side, unit);
          } else if (u_defensive_worth(u) > 0) {
              assign_to_defense(side, unit);
          /* In the unlikely case that mobile units can build anything. */
          } else if (can_build_attackers(side, u)) {
            assign_to_offense_support(side, unit);
          } else if (can_build_defenders(side, u)) {
            assign_to_defense_support(side, unit);
          } else {
          }
      } else {
          /* Unit doesn't move. */
          if (can_build_colonizers(side, u)
            /* (should fine-tune this test) */
            && probability(60)) {
            assign_to_colonization_support(side, unit);
          } else if (has_unsatisfied_goal(side, GOAL_VICINITY_HELD)
            && can_build_attackers(side, u)) {
            assign_to_offense_support(side, unit);
          } else if (has_goal(side, GOAL_WORLD_KNOWN)
            && need_explorers(side)
            && can_build_explorers(side, u)) {
            assign_to_explorer_construction(side, unit);
          } else if (can_build_attackers(side, u)) {
            assign_to_offense_support(side, unit);
          } else if (can_build_defenders(side, u)) {
            assign_to_defense_support(side, unit);
          } else if (u_defensive_worth(u) > 0) {
            assign_to_defense(side, unit);
          }
      }
      break;
      case PLAN_OFFENSIVE:
      /* leave plan alone */
      break;
      case PLAN_COLONIZING:
      /* leave plan alone */
      break;
      case PLAN_IMPROVING:
      /* leave plan alone */
      break;
      case PLAN_EXPLORATORY:
      /* leave plan alone */
      break;
      case PLAN_DEFENSIVE:
      /* leave plan alone */
      break;
      default:
      break;
    }
}

static int
mplayer_adjust_plan(Side *side, Unit *unit)
{
    int u3, m;
    Task *task;

    /* Non-mobile units may be able to execute on a plan by
       construction; here we let the mplayer decide which type
       to construct. */
    if ((unit->plan->type == PLAN_OFFENSIVE
       || unit->plan->type == PLAN_COLONIZING
       || unit->plan->type == PLAN_IMPROVING
       || unit->plan->type == PLAN_EXPLORATORY)
      && !mobile(unit->type)
      && unit->plan->aicontrol
      && !unit->plan->asleep
      && unit->plan->tasks == NULL
      ) {
      u3 = preferred_build_type(side, unit, unit->plan->type);
      if (is_unit_type(u3)) {
          task = unit->plan->tasks;
          if (task == NULL || task->type != TASK_BUILD) {
            DMprintf("%s directed to build %s\n",
                   unit_desig(unit), u_type_name(u3));
            net_set_build_task(unit, u3, 2, 0, 0);
            for_all_material_types(m) {
                int consump = um_consumption_on_creation(u3, m);

                if (consump > 0
                  && side_has_treasury(side, m)
                  && um_takes_from_treasury(unit->type, m)) {
                  Goal *goal;

                  goal = create_goal(GOAL_HAS_MATERIAL_TYPE,
                                 side, TRUE);
                  goal->args[0] = m;
                  goal->args[1] = 3 * consump;
                  add_goal(side, goal);
                }
            }
          } else {
            DMprintf("%s already building, leaving alone\n",
                   unit_desig(unit));
          }
          /* Only do one at a time, wait for next go-around for
             next unit. */
          /* (why?) */
          return FALSE;
      }
    }
    /* If a collecting unit achieves its goal, cancel its plan. */
    if (unit->plan->type == PLAN_IMPROVING
      && mobile(unit->type)
      && unit->plan->aicontrol
      && !unit->plan->asleep
      && unit->plan->tasks == NULL
      ) {
      net_force_replan(side, unit, FALSE);
    }
    if (unit->plan->waitingfortasks
      && unit->plan->aicontrol
      ) {
      net_force_replan(side, unit, FALSE);
    }
    if (!unit->plan->reserve
      && g_units_may_go_into_reserve()
      && unit->plan->execs_this_turn > 10 * max(1, u_acp(unit->type))) {
      net_set_unit_reserve(side, unit, TRUE, FALSE);
    }
    /* Look at more units. */
    return TRUE;
}

static int
need_explorers(Side *side)
{
    int s, numcontacted = 0, numfound = 0;

    for (s = 1; s <= numsides; ++s) {
      if (s == side->id)
        continue;
      if (ai(side)->contacted[s])
        ++numcontacted;
      if (ai(side)->homefound[s])
        ++numfound;
    }
    if (numcontacted == 0) {
      /* If we've haven't found anybody, always explore. */
      return TRUE;
    } else if (numfound == 0) {
      /* If we've made contact but haven't found their home base,
         we still need to explore, but not so much. */
      return probability(50);
    } else if (numfound < numsides - 1) {
      /* If we haven't found everybody's home base, we still need
         to have a few units continuing to explore. */
      return probability(10);
    } else {
      /* If everybody has been found, then we likely have more
         pressing concerns; don't do more exploration. */
      return FALSE;
    }
}

/* This is a hook that runs after each task is executed. */

static void
mplayer_react_to_task_result(Side *side, Unit *unit, Task *task, TaskOutcome rslt)
{
    int dx, dy, x1, y1, fact;
    Unit *occ;
    Theater *theater;

    /* React to an apparent blockage. */
    if (rslt == TASK_FAILED
      && task != NULL
      && task->type == TASK_MOVE_TO
      && task->retrynum > 2) {
      if (desired_direction_impassable(unit, task->args[0], task->args[1])) {
          if (could_be_ferried(unit, task->args[0], task->args[1])) {
            if (unit->plan->type == PLAN_EXPLORATORY && flip_coin()) {
                DMprintf("%s blocked while exploring, ", unit_desig(unit));
                      if (flip_coin()) {
                  DMprintf("changing theaters\n");
                  change_to_adjacent_theater(side, unit);
                } else {
                  DMprintf("changing goal within theater\n");
                  /* Clear the existing goal and create a new one. */
                  assign_explorer_to_theater(side, unit,
                                       unit_theater(unit));
                }
                return;
            } else if (flip_coin()) {
                DMprintf("%s blocked, will wait for transport\n",
                       unit_desig(unit));
                theater = theater_at(side, unit->x, unit->y);
                if (theater != NULL) {
                  ++(theater->numtotransport[unit->type]);
                }
                net_set_unit_reserve(side, unit, TRUE, FALSE);
                net_set_unit_waiting_for_transport(side, unit, TRUE);
                return;
            }
          } else {
            if (unit->occupant) {
                DMprintf("%s blocked while transporting, will sit briefly\n",
                       unit_desig(unit));
                net_set_unit_reserve(side, unit, TRUE, FALSE);
                for_all_occupants(unit, occ) {
                  net_wake_unit(side, occ, FALSE);
                }
                return;
            }
            /* Another option is to transfer to another theater.
               This is especially useful when exploring. */
            if (unit->plan->type == PLAN_EXPLORATORY && flip_coin()) {
                DMprintf("%s blocked while exploring, changing theaters\n",
                       unit_desig(unit));
                change_to_adjacent_theater(side, unit);
                return;
            }
          }
          /* Try moving sideways. */
          if (probability(80)) {
            dx = task->args[0] - unit->x;  dy = task->args[1] - unit->y;
            fact = (flip_coin() ? 50 : -50);
            x1 = unit->x - ((fact * dy) / 100);
            y1 = unit->y + ((fact * dx) / 100);
            if (inside_area(x1, y1))
              net_push_move_to_task(unit, x1, y1, 1);
          }
          return;
      } else if (blocked_by_enemy(unit, task->args[0], task->args[1], TRUE)) {
          /* (should decide if allowable risk to passengers) */
          DMprintf("%s blocked by enemy\n", unit_desig(unit));
          if (!attack_blockage(side, unit, task->args[0], task->args[1], TRUE)) {
            if (blocked_by_enemy(unit, task->args[0], task->args[1], FALSE)) {
                attack_blockage(side, unit, task->args[0], task->args[1], FALSE);
            } else {
                /* (should move sideways?) */
            }
          }
      } else {
          /* what to do about other failures? */
      }
      return;
    }
    /* React to inability to resupply by trying to build a base. */
    if (rslt == TASK_FAILED
      && task != NULL
      && task->type == TASK_RESUPPLY
      && task->retrynum > 2) {
      net_set_unit_reserve(side, unit, FALSE, FALSE);
      build_depot_for_self(side, unit);
    }
}

/* Reassign the given unit to another theater, usually because it is
   unable to fulfill its goal in its current theater. */

static void
change_to_adjacent_theater(Side *side, Unit *unit)
{
    int dir;
    Theater *theater, *newtheater = NULL;

    theater = unit_theater(unit);
    if (theater != NULL) {
      for_all_directions(dir) {
          /* If we have a radial pattern of theaters, randomly
             choose between inward/outward or right/left. */
          if (theater == ai(side)->perimeters[dir]) {
            if (probability(20) && ai(side)->midranges[dir] != NULL)
              newtheater = ai(side)->midranges[dir];
            else
              newtheater = ai(side)->perimeters[flip_coin() ? left_dir(dir) : right_dir(dir)];
            break;
          }
          if (theater == ai(side)->midranges[dir]) {
            if (probability(20) && ai(side)->perimeters[dir] != NULL)
              newtheater = ai(side)->perimeters[dir];
            else if (probability(20) && ai(side)->remotes[dir] != NULL)
              newtheater = ai(side)->remotes[dir];
            else
              newtheater = ai(side)->midranges[flip_coin() ? left_dir(dir) : right_dir(dir)];
            break;
          }
          if (theater == ai(side)->remotes[dir]) {
            if (probability(20) && ai(side)->midranges[dir] != NULL)
              newtheater = ai(side)->midranges[dir];
            else
              newtheater = ai(side)->remotes[flip_coin() ? left_dir(dir) : right_dir(dir)];
            break;
          }
      }
      /* (should add grid case also?) */
      if (newtheater != NULL) {
          assign_explorer_to_theater(side, unit, newtheater);
      }
    }
}

/* This function is called whenever a new side appears in the game.  It
   mainly needs to make that any allocated data is resized appropriately. */

static void
mplayer_react_to_new_side(Side *side, Side *side2)
{
    /* (Assumes we call this right after adding each new side) */
    int oldnumsides = numsides - 1;
    int *newpeople, s;
    Theater *theater;

    for_all_theaters(side, theater) {
      /* Grow any people count arrays if present. */
      if (theater->people != NULL) {
          newpeople = (int *) xmalloc ((numsides + 1) * sizeof(int));
          for (s = 0; s <= oldnumsides; ++s)
            newpeople[s] = theater->people[s];
          free(theater->people);
          theater->people = newpeople;
      }
    }
    anewside = side2;
    estimate_strengths(side);
    anewside = NULL;
}

/* At the end of a turn, re-evaluate the plans of some units in case
   the situation changed. */

static void
mplayer_finish_movement(Side *side)
{
    int u, scan;
    Unit *unit;
    Theater *theater;

    scan = FALSE;
    for_all_theaters(side, theater) {
      for_all_unit_types(u) {
          if (theater->numtotransport[u] > 0) {
            scan = TRUE;
            break;
          }
      }
      if (scan)
        break;
    }
    if (scan) {
      /* Find a unit needing transport. */
      for_all_side_units(side, unit) {
          if (is_active(unit)
            && ai_controlled(unit)
            && unit->plan
            && unit->plan->waitingfortransport) {
            search_for_available_transport(unit, 0);
            /* Whether or not the search succeeded, stop waiting
               to see if we get transportation. */
            net_set_unit_waiting_for_transport(side, unit, FALSE);
          }
      }
    }
    for_all_side_units(side, unit) {
      if (is_active(unit) && ai_controlled(unit)) {
          mplayer_rethink_plan(unit);
      }
    }
}

/* Given a unit and a reason to be needing transport, look around for
   something suitable.  We need a transport that has available space
   and is not doing something else more important. */
/* (should also choose transports that can reach the unit's
   destination or at least close by) */

static Unit *
search_for_available_transport(Unit *unit, int purpose)
{
    int dist, closestdist = area.maxdim;
    Unit *transport, *closesttransport = NULL;
    Theater *theater = unit_theater(unit);

    /* (more efficient to search adjacent cells first?) */
    for_all_side_units(unit->side, transport) {
      if (is_active(transport)
          && mobile(transport->type)
          && could_carry(transport->type, unit->type)
          && can_carry(transport, unit)
          && transport->act != NULL /* not quite correct, but to fix bug */
          && (purpose == 1 ? accelerator(transport->type, unit->type) : TRUE)
          ) {
          /* Don't grab at units being moved manually. */
          if (!ai_controlled(transport))
            continue;
          /* Maybe this one is already coming to get somebody. */
          if (transport->plan
            && transport->plan->tasks != NULL
            && transport->plan->tasks->type == TASK_PICKUP) {
            if (transport->plan->tasks->args[0] == unit->id)
              return transport;
            /* Picking up somebody else - don't hijack. */
            continue;
          }
          if (transport->plan
            && transport->plan->tasks != NULL
            && transport->plan->tasks->type == TASK_MOVE_TO
            && transport->plan->tasks->next != NULL
            && transport->plan->tasks->next->type == TASK_PICKUP) {
            if (transport->plan->tasks->next->args[0] == unit->id)
              return transport;
            /* Picking up somebody else - don't hijack. */
            continue;
          }
          dist = distance(unit->x, unit->y, transport->x, transport->y);
          if (dist < closestdist || (dist == closestdist && flip_coin())) {
            closesttransport = transport;
            closestdist = dist;
          }
          /* If transport already adjacent, no need to keep looking. */
          if (closestdist <= 1)
            break;
      }
    }
    if (closesttransport != NULL && closesttransport->plan != NULL) {
      net_clear_task_agenda(unit->side, unit);
      /* (could inherit unit's goal, but not needed) */
      if (unit->plan)
        net_set_unit_plan_type(unit->side, closesttransport,
                         unit->plan->type);
      net_set_unit_main_goal(unit->side, closesttransport, NULL);
      net_push_pickup_task(closesttransport, unit);
      net_push_move_to_task(closesttransport, unit->x, unit->y, 1);
      net_push_occupy_task(unit, closesttransport);
      /* No longer count this unit as needing transport. */
      if (theater != NULL) {
          --(theater->numtotransport[unit->type]);
          set_unit_theater(closesttransport, theater);
      }
      DMprintf("%s will be picked up by closest transport %s\n",
               unit_desig(unit), unit_desig(closesttransport));
      return closesttransport;
    }
    return NULL;
}

/* For units with plans and that are under AI control, consider
   changing the current plan/tasks. */

static void
mplayer_rethink_plan(Unit *unit)
{
    int dist, x1, y1;
    Task *toptask = unit->plan->tasks, *nexttask = NULL;
    Plan *plan = unit->plan;
    Unit *transport;

    if (toptask)
      nexttask = toptask->next;
    /* If we have a long ways to go, see if there is a transport available that
       can get us there faster.  */
    if (toptask != NULL
      && (toptask->type == TASK_HIT_UNIT
          || (toptask->type == TASK_MOVE_TO
            && nexttask != NULL
            && nexttask->type == TASK_HIT_UNIT))
        && !plan->reserve
        && !plan->asleep
        && !plan->waitingfortransport
        && (unit->transport == NULL || !mobile(unit->transport->type))
        && ((dist = distance(unit->x, unit->y,
                       toptask->args[0], toptask->args[1]))
            >= 4 * u_acp(unit->type))
        && accelerable(unit->type)
        ) {
        DMprintf("%s looking for transport to accelerate with;\n", unit_desig(unit));
        transport = search_for_available_transport(unit, 1);
        if (transport != NULL) {
          net_push_sentry_task(unit, max(1, dist / max(1, u_acp(transport->type))));
          if (g_units_may_go_into_reserve())
            net_set_unit_reserve(unit->side, unit, TRUE, FALSE);
          net_set_unit_waiting_for_transport(unit->side, unit, FALSE);
        } else {
          DMprintf("  found nothing\n");
        }
    }
    if (unit->plan->type == PLAN_OFFENSIVE
        && toptask != NULL
        && toptask->type == TASK_MOVE_TO
        && distance(unit->x, unit->y, toptask->args[0], toptask->args[1])
            >= min(2, u_acp(unit->type))
        && enemy_close_by(unit->side, unit, 1 /* 2 would be better? */, &x1, &y1)
        ) {
      net_push_hit_unit_task(unit, x1, y1, NONUTYPE, -1);
      DMprintf("%s sees enemy close by, will attack it\n", unit_desig(unit));
    }
    /* (should also notice fire opportunities) */
    /* If we see somebody that could be captured and help us explore, set up
       to produce capturers. */
    if (!mobile(unit->type)
      && (unit->plan->type == PLAN_EXPLORATORY || unit->plan->type == PLAN_OFFENSIVE)
      ) {
      int range = 4, rslt, x, y;

      DMprintf("%s searching for useful capture within %d in order to choose build; found ",
             unit_desig(unit), range);
      tmpunit = unit;
      rslt = search_around(unit->x, unit->y, range, useful_captureable_here,
                       &x, &y, 1);
      if (rslt && is_unit_type(tmputype)) {
          DMprintf("%s at %d,%d", u_type_name(tmputype), x, y);
          if (toptask != NULL
            && toptask->type == TASK_BUILD
            && uu_capture(toptask->args[0], tmputype)
            ) {
            /* Already doing the right thing. */
            DMprintf(" - already building %s", u_type_name(toptask->args[0]));
          } else {
            /* (should find best type that can capture quickly,
                schedule to build it) */
            DMprintf(" - duhhh, what now?");
          }
      } else {
          DMprintf("nothing");
      }
      DMprintf("\n");
    }
}

#if 0
static void
mplayer_react_to_unit_loss(side, unit)
Side *side;
Unit *unit;
{
    int x = unit->x, y = unit->y;
    Theater *th;

    if (!inside_area(x, y)) {
      x = unit->prevx;  y = unit->prevy;
    }
    if (!inside_area(x, y))
      return;
    /* Count the unit as having been lost in a particular theater. */
    if (ai(side) && (th = theater_at(side, x, y)) != NULL) {
      ++(th->units_lost);
    }
}
#endif

/* This is used by interfaces to display the name of the theater in
   use at a given point. */

static char *
mplayer_at_desig(Side *side, int x, int y)
{
    Theater *theater;

    if (ai(side) == NULL)
      return "";
    theater = theater_at(side, x, y);
    return (theater ? theater->name : "<no theater>");
}

/* This is used by interfaces to display boundaries between theaters,
   by comparing the numbers returned. */

static int
mplayer_theater_at(Side *side, int x, int y)
{
    Theater *theater;

    if (ai(side) == NULL)
      return 0;
    theater = theater_at(side, x, y);
    return (theater ? theater->id : 0);
}

/* Collect initial strength information stored in the mplayer's
   private saved data. */

static int
mplayer_read_strengths(Side *side)
{
    int sn1, u, found = FALSE;
    char *propname;
    Obj *props, *bdg, *rest, *sidebdg, *urest;
    Side *side1;
    Strategy *strategy = ai(side);

    props = find_at_key(side->aidata, "mplayer");
    for (; props != lispnil; props = cdr(props)) {
      bdg = car(props);
      propname = c_string(car(bdg));
      if (strcmp(propname, "strengths0") == 0) {
          found = TRUE;
          rest = cdr(bdg);
          for_all_sides(side1) {
            sn1 = side1->id;
            sidebdg = car(rest);
            urest = cadr(sidebdg);
            for_all_unit_types(u) {
                strategy->strengths0[sn1][u] = c_number(car(urest));
                urest = cdr(urest);
            }
            rest = cdr(rest);
          }
      } else if (strcmp(propname, "alstrengths0") == 0) {
          found = TRUE;
          rest = cdr(bdg);
          for_all_sides(side1) {
            sn1 = side1->id;
            sidebdg = car(rest);
            urest = cadr(sidebdg);
            for_all_unit_types(u) {
                strategy->alstrengths0[sn1][u] = c_number(car(urest));
                urest = cdr(urest);
            }
            rest = cdr(rest);
          }
      } else if (strcmp(propname, "points0") == 0) {
          found = TRUE;
          rest = cdr(bdg);
          for_all_sides(side1) {
            sn1 = side1->id;
            strategy->points0[sn1] = c_number(car(rest));
            rest = cdr(rest);
          }
      } else if (strcmp(propname, "alpoints0") == 0) {
          found = TRUE;
          rest = cdr(bdg);
          for_all_sides(side1) {
            sn1 = side1->id;
            strategy->alpoints0[sn1] = c_number(car(rest));
            rest = cdr(rest);
          }
      } else {
      }
    }
    return found;
}

/* Write out any state that the mplayer must preserve.  We don't
   actually write; instead we build a Lisp object and pass that back
   to the writing routines. */

static Obj *
mplayer_save_state(Side *side)
{
    int sn1, u;
    Obj *rslt, *vallist, *uvallist;
    Side *side1;
    Strategy *strategy = ai(side);

    rslt = lispnil;
    /* Just return last result if it's already been computed. */
    if (strategy->writable_state != lispnil || xmalloc_warnings)
      return strategy->writable_state;
    /* We're pushing bindings onto a list, so do in reverse of desired order. */
    vallist = lispnil;
    for_all_sides(side1) {
      sn1 = side1->id;
      uvallist = lispnil;
      for_all_unit_types(u) {
          uvallist = cons(new_number(strategy->alstrengths0[sn1][u]), uvallist);
      }
      uvallist = reverse(uvallist);
      push_binding(&vallist, new_number(sn1), uvallist);
    }
    vallist = reverse(vallist);
    push_cdr_binding(&rslt, intern_symbol("alstrengths0"), vallist);
    vallist = lispnil;
    for_all_sides(side1) {
      sn1 = side1->id;
      uvallist = lispnil;
      for_all_unit_types(u) {
          uvallist = cons(new_number(strategy->strengths0[sn1][u]), uvallist);
      }
      uvallist = reverse(uvallist);
      push_binding(&vallist, new_number(sn1), uvallist);
    }
    vallist = reverse(vallist);
    push_cdr_binding(&rslt, intern_symbol("strengths0"), vallist);
    vallist = lispnil;
    for_all_sides(side1) {
      sn1 = side1->id;
      vallist = cons(new_number(strategy->alpoints0[sn1]), vallist);
    }
    vallist = reverse(vallist);
    push_cdr_binding(&rslt, intern_symbol("alpoints0"), vallist);
    vallist = lispnil;
    for_all_sides(side1) {
      sn1 = side1->id;
      vallist = cons(new_number(strategy->points0[sn1]), vallist);
    }
    vallist = reverse(vallist);
    push_cdr_binding(&rslt, intern_symbol("points0"), vallist);
    strategy->writable_state = rslt;
    return rslt;
}

Generated by  Doxygen 1.6.0   Back to index