|
|
DataMuseum.dkPresents historical artifacts from the history of: DKUUG/EUUG Conference tapes |
This is an automatic "excavation" of a thematic subset of
See our Wiki for more about DKUUG/EUUG Conference tapes Excavated with: AutoArchaeologist - Free & Open Source Software. |
top - metrics - downloadIndex: T m
Length: 32778 (0x800a)
Types: TextFile
Names: »mplay.c«
└─⟦b20c6495f⟧ Bits:30007238 EUUGD18: Wien-båndet, efterår 1987
└─⟦this⟧ »EUUGD18/X/Xconq/mplay.c«
/* Copyright (c) 1987, 1988 Stanley T. Shebs, University of Utah. */
/* This program may be used, copied, modified, and redistributed freely */
/* for noncommercial purposes, so long as this notice remains intact. */
/* RCS $Header: mplay.c,v 1.1 88/06/21 12:30:25 shebs Exp $ */
/* This file implements all of machine strategy. Not much room for fancy */
/* tricks, just solid basic play. The code emphasizes avoidance of mistakes */
/* instead of strategic brilliance, so machine behavior is akin to bulldozer */
/* plodding. Nevertheless, bulldozers can be very effective when they */
/* outnumber the human players... */
/* It is also very important to prevent infinite loops, so no action of the */
/* machine player is 100% certain. */
#include "config.h"
#include "misc.h"
#include "dir.h"
#include "period.h"
#include "side.h"
#include "unit.h"
#include "map.h"
#include "global.h"
/* Maximum number of unit groups that can be maintained. Need groups for */
/* both offense and defense. */
#define MAXGROUPS 80
/* the non-group */
#define NOGROUP 0
/* Group goals. */
#define HITTARGET 1
#define CAPTARGET 2
#define OCCUPYHEX 3
#define EXPLORE 4
#define DEFEND 5
/* Individual goals. */
#define NOGOAL 0
#define DRIFT 1
#define ASSAULT 2
#define DISCOVER 3
#define LOAD 4
#define APPROACH 5
#define RELOAD 6
/* Groups organize machine player activity at the multiple-unit level. */
typedef struct a_group {
short goal; /* the intended purpose of the group */
short priority; /* how important the group is */
short x, y; /* a relevant location */
short etype; /* type of a unit there (or NOTHING) */
short area; /* radius of relevance of group activity */
short size; /* number of units in the group */
} Group;
/* This structure is where machine sides keep all the plans and planning */
/* related data. */
/* Group 0 is never actually used (a sort of a dummy for various purposes). */
typedef struct a_plan {
short knowns[MAXSIDES]; /* estimated strength of other sides */
short allieds[MAXSIDES]; /* strength of other alliances */
short cx, cy; /* "centroid" of all our units */
short shouldresign; /* true if machine thinks it should resign */
short lastreplan; /* last turn we rechecked the plans */
Group group[MAXGROUPS]; /* all the groups that can be formed */
} Plan;
#define side_plan(s) ((Plan *) (s)->plan)
/* Malloced integer array accessors and modifers. */
#define aref(m,x,y) ((m)[(x)+world.width*(y)])
#define aset(m,x,y,v) ((m)[(x)+world.width*(y)] = (v))
/* General collections of numbers used by all machine players. */
int bhw[MAXUTYPES][MAXUTYPES]; /* basic worth for hitting */
int bcw[MAXUTYPES][MAXUTYPES]; /* basic worth for capturing */
int bthw[MAXUTYPES][MAXUTYPES]; /* basic worth for carrying hitters */
int btcw[MAXUTYPES][MAXUTYPES]; /* basic worth for carrying capturers */
int maxoccupant[MAXUTYPES]; /* total capacity of a transport */
int *localworth; /* for evaluation of nearby hexes */
Unit *munit; /* Unit being decided about */
Side *mside; /* Side whose unit is being decided about */
/* Init used by all machine players. Precompute useful information */
/* relating to unit types in general, and that usually gets referenced */
/* in inner loops. */
init_mplayers()
{
int u, u2, g;
Side *side;
localworth = (int *) malloc(world.width*world.height*sizeof(int));
for_all_unit_types(u) {
maxoccupant[u] = 0;
for_all_unit_types(u2) {
bhw[u][u2] = basic_hit_worth(u, u2);
bcw[u][u2] = basic_capture_worth(u, u2);
bthw[u][u2] = basic_transport_worth(u, u2);
btcw[u][u2] = basic_transport_worth(u, u2);
maxoccupant[u] += utypes[u].capacity[u2];
}
}
/* tell us about how things rated */
if (Debug) {
for_all_unit_types(u) {
for_all_unit_types(u2) printf("%5d", bhw[u][u2]);
printf("\n");
}
printf("\n");
for_all_unit_types(u) {
for_all_unit_types(u2) printf("%5d", bcw[u][u2]);
printf("\n");
}
printf("\n");
for_all_unit_types(u) {
for_all_unit_types(u2) printf("%5d", bthw[u][u2]);
printf("\n");
}
printf("\n");
for_all_unit_types(u) {
for_all_unit_types(u2) printf("%5d", btcw[u][u2]);
printf("\n");
}
printf("\n");
}
/* For all sides, because human might use "robot" option */
for_all_sides(side) {
side->plan = (long) malloc(sizeof(Plan));
side_plan(side)->shouldresign = FALSE;
side_plan(side)->cx = side_plan(side)->cy = 0;
for (g = 0; g < MAXGROUPS; ++g) {
side_plan(side)->group[g].goal = NOGROUP;
side_plan(side)->group[g].priority = 0;
}
side_plan(side)->lastreplan = -100;
}
}
/* A crude estimate of the payoff of one unit type hitting on another type. */
/* This is just for general estimation, since actual worth may depend on */
/* damage already sustained, unit's goals, etc. */
basic_hit_worth(u, e)
int u, e;
{
int worth, anti;
worth = utypes[u].hit[e] * min(utypes[e].hp, utypes[u].damage[e]);
if (utypes[e].hp > utypes[u].damage[e]) {
worth /= utypes[e].hp;
} else {
worth *= utypes[e].hp;
}
if (period.counterattack) {
anti = utypes[e].hit[u] * min(utypes[u].hp, utypes[e].damage[u]);
if (utypes[u].hp > utypes[e].damage[u]) {
anti /= utypes[u].hp;
} else {
anti *= utypes[u].hp;
}
}
if (utypes[e].territory > 0) worth *= utypes[e].territory;
worth -= anti;
return worth;
}
/* A crude estimate of the payoff of one unit type trying to capture. */
basic_capture_worth(u, e)
int u, e;
{
int worth = 0, anti = 0;
if (could_capture(u, e)) {
worth += utypes[e].territory * utypes[u].capture[e];
}
return worth;
}
/* This should account for volume also... */
basic_transport_worth(u, e)
int u, e;
{
int worth = 0, u2;
for_all_unit_types(u2) {
if (could_capture(u2, e)) {
worth += utypes[u].capacity[u2] * utypes[u2].capture[e];
}
}
worth *= utypes[u].speed;
return worth;
}
/* At the beginning of each turn, review situation, and maybe make some */
/* new plans. */
init_machine_turn(side)
Side *side;
{
if (global.time > 10 || probability(20)) decide_resignation(side);
if (!side_plan(side)->shouldresign) {
review_groups(side);
form_new_groups(side);
}
}
/* Sometimes there is no point in going on, but be careful not to be too */
/* pessimistic. */
decide_resignation(side)
Side *side;
{
int opposed, own, odds, chance;
Side *side1, *side2;
Plan *plan = side_plan(side);
for_all_sides(side1) {
plan->allieds[side_number(side1)] = plan->knowns[side_number(side1)];
for_all_sides(side2) {
if (side1 != side2 && allied_side(side1, side2)) {
plan->allieds[side_number(side1)] +=
plan->knowns[side_number(side2)];
}
}
}
own = plan->allieds[side_number(side)];
for_all_sides(side1) {
if (enemy_side(side, side1)) {
opposed = plan->allieds[side_number(side1)];
if (own == 0) {
if (opposed > 0) plan->shouldresign = TRUE;
} else {
odds = (100 * opposed) / own;
if (odds > 200) {
chance = odds / 10;
if (probability(chance)) plan->shouldresign = TRUE;
}
}
}
}
}
/* Review existing groups and get rid of useless ones. Start by recomputing */
/* the size, since we don't update when units die or get transferred. */
review_groups(side)
Side *side;
{
int g, view;
Plan *plan = side_plan(side);
Unit *unit;
for (g = 1; g < MAXGROUPS; ++g) plan->group[g].size = 0;
for_all_units(unit) {
if (unit->side == side) plan->group[unit->group].size++;
}
for (g = 1; g < MAXGROUPS; ++g) {
switch (plan->group[g].goal) {
case NOGROUP:
/* a non-existent group */
break;
case HITTARGET:
view = side_view(side, plan->group[g].x, plan->group[g].y);
if (view == EMPTY || view == UNSEEN ||
side_n(vside(view)) == NULL ||
allied_side(side, side_n(vside(view)))) {
disband_group(side, g);
}
break;
case CAPTARGET:
view = side_view(side, plan->group[g].x, plan->group[g].y);
if (view == EMPTY || view == UNSEEN ||
allied_side(side, side_n(vside(view)))) {
disband_group(side, g);
}
break;
case EXPLORE:
view = side_view(side, plan->group[g].x, plan->group[g].y);
if (view != UNSEEN) {
disband_group(side, g);
}
break;
case DEFEND:
/* should study area to decide about desirabilty of disbanding */
if (probability(3)) disband_group(side, g);
break;
case OCCUPYHEX:
/* occupying should only end if no longer a victory condition */
break;
default:
case_panic("group goal", plan->group[g].goal);
break;
}
}
}
/* Decide about the formation of new groups, but don't recreate copies of */
/* groups that already exist. */
form_new_groups(side)
Side *side;
{
int i, g, x, y, view, etype, x0, x1, x2, y1, y2, choice;
int pri, sumx = 0, sumy = 0, n = 0;
Plan *plan = side_plan(side);
Side *eside;
if (!world.known) {
if (!find_group(side, EXPLORE, -1, -1)) {
x0 = 0 + random(world.width/3);
x1 = world.width/3 + random(world.width/3);
x2 = (2*world.width)/3 + random(world.width/3);
y1 = 0 + random(world.height/2);
y2 = (3*world.height)/4 + random(world.height/2);
form_group(side, EXPLORE, x0, y1, 0);
form_group(side, EXPLORE, x1, y1, 0);
form_group(side, EXPLORE, x2, y1, 0);
form_group(side, EXPLORE, x0, y2, 0);
form_group(side, EXPLORE, x1, y2, 0);
form_group(side, EXPLORE, x2, y2, 0);
}
}
if (global.time < 10 || probability(20)) {
for (i = 0; i < MAXSIDES; ++i) plan->knowns[i] = 0;
for (y = 0; y < world.height; ++y) {
for (x = 0; x < world.width; ++x) {
view = side_view(side, x, y);
if (view != EMPTY && view != UNSEEN) {
if (side == side_n(vside(view))) {
sumx += x; sumy += y; n++;
}
}
}
}
if (n > 0) {
plan->cx = sumx / n; plan->cy = sumy / n;
}
for (y = 0; y < world.height; ++y) {
for (x = 0; x < world.width; ++x) {
view = side_view(side, x, y);
if (view != EMPTY && view != UNSEEN) {
eside = side_n(vside(view));
etype = vtype(view);
if (!allied_side(side, eside)) {
choice = (capturable(etype) ? CAPTARGET : HITTARGET);
if (!find_group(side, choice, x, y)) {
pri =
100 / (distance(x, y, plan->cx, plan->cy) + 1);
if ((g = form_group(side, choice, x, y, pri))) {
plan->group[g].etype = etype;
}
}
} else {
if (!mobile(etype) && !defended(side, x, y)) {
if ((g = form_group(side, DEFEND, x, y, 0))) {
plan->group[g].area = 3;
}
}
}
if (eside != NULL)
plan->knowns[side_number(eside)] +=
utypes[etype].territory;
}
}
}
}
/* form a hex occupation group if hex mentioned in win/lose */
}
/* Decides if unit has nothing covering it. */
defended(side, x, y)
Side *side;
int x, y;
{
int g;
Plan *plan = side_plan(side);
for (g = 1; g < MAXGROUPS; ++g) {
if ((plan->group[g].goal == DEFEND) &&
(distance(x, y, plan->group[g].x, plan->group[g].y) <=
plan->group[g].area))
return TRUE;
}
return FALSE;
}
/* When forming a group, first pick out an unused group, then bump a lower */
/* priority group if there's too many. If it's of lower or equal priority, */
/* then don't form the group at all (failure on equal priorities reduces */
/* fickleness). */
form_group(side, goal, x, y, priority)
Side *side;
int goal, x, y, priority;
{
int g;
Plan *plan = side_plan(side);
for (g = 1; g < MAXGROUPS; ++g) {
if (plan->group[g].goal == NOGROUP) break;
}
if (g == MAXGROUPS) {
for (g = 1; g < MAXGROUPS; ++g) {
if (priority > plan->group[g].priority) {
disband_group(side, g);
break;
}
}
}
if (g < MAXGROUPS) {
plan->group[g].goal = goal;
plan->group[g].priority = priority;
plan->group[g].x = x;
plan->group[g].y = y;
plan->group[g].etype = NOTHING;
plan->group[g].area = 1;
plan->group[g].size = 0;
if (Debug) printf("%s form group %d with goal %d -> %d,%d\n",
side->name, g, goal, x, y);
return g;
} else {
return 0;
}
}
/* When group's goal accomplished, release the units for other activities. */
/* Not very efficient to scan all units, but simpler and safer than links. */
disband_group(side, g)
Side *side;
int g;
{
Unit *unit;
Plan *plan = side_plan(side);
if (Debug) printf("%s disband group %d with goal %d -> %d,%d\n",
side->name, g, plan->group[g].goal,
plan->group[g].x, plan->group[g].y);
plan->group[g].goal = NOGROUP;
plan->group[g].size = 0;
for_all_units(unit) {
if (unit->side == side && unit->group == g) {
unit->group = NOGROUP;
unit->goal = NOGOAL;
}
}
}
/* Given a goal and argument, see if a group already exists like that. */
find_group(side, goal, x, y)
Side *side;
int goal, x, y;
{
int g;
for (g = 1; g < MAXGROUPS; ++g) {
if ((side_plan(side)->group[g].goal == goal) &&
(x == -1 || side_plan(side)->group[g].x == x) &&
(y == -1 || side_plan(side)->group[g].y == y))
return g;
}
return 0;
}
/* Decide whether a change of product is desirable. */
change_machine_product(unit)
Unit *unit;
{
int u = unit->type;
if (Freeze) {
return FALSE;
} else if (utypes[u].maker) {
if (producing(unit)) {
if ((unit->built > 5) ||
((utypes[u].make[unit->product] * unit->built) > 50)) {
return TRUE;
}
} else {
return TRUE;
}
}
return FALSE;
}
/* Machine algorithm for deciding what a unit should build. This routine */
/* must return the type of unit decided upon. Variety of production is */
/* important, as is favoring types which can leave the builder other than */
/* on a transport. Capturers of valuable units are also highly preferable. */
machine_product(unit)
Unit *unit;
{
int u = unit->type, type;
int i, j, k, d, x, y, value, bestvalue, besttype;
int adjterr[MAXTTYPES];
mside = unit->side;
for_all_terrain_types(i) adjterr[i] = 0;
for_all_directions(d) {
x = wrap(unit->x + dirx[d]); y = unit->y + diry[d];
adjterr[terrain_at(x, y)]++;
}
besttype = period.firstptype;
bestvalue = 0;
for_all_unit_types(i) {
value = 0;
if (!could_make(u, i)) value = -1000;
if (mobile(i)) {
for_all_terrain_types(j) {
if (could_move(i, j)) value += adjterr[j];
}
if (value <= 0) value = -1000;
}
for_all_unit_types(j) {
if (could_capture(i, j)) value += 2;
if (could_carry(i, j)) {
for_all_unit_types(k) {
if (could_capture(j, k)) value += 1;
}
}
}
if (mside->building[i] == 0) value += period.numutypes / 2;
if (mside->building[i] == 1) value += 1;
if (mside->units[i] == 0) value += period.numutypes / 4;
if (value > bestvalue) {
besttype = i;
bestvalue = value;
}
}
type = besttype;
/* safety check */
if (!could_make(unit->type, type)) type = NOTHING;
if (Debug) printf("%s will now build %s units\n",
unit_handle(NULL, unit),
(type == NOTHING ? "no" : utypes[type].name));
return type;
}
/* Decide on and make a move or set orders for a machine player. */
machine_move(unit)
Unit *unit;
{
munit = unit;
mside = unit->side;
if (Freeze) {
order_sentry(unit, 1);
} else if (humanside(mside)) {
unit->goal = DRIFT;
if (maybe_return_home(unit)) return;
/* need to decide about "short-term" tactics */
search_for_best_move(unit);
} else if (side_plan(mside)->shouldresign && flip_coin()) {
resign_game(mside, NULL);
} else {
if (unit->group == NOGROUP) decide_group(unit);
if (unit->goal == NOGOAL) decide_goal(unit);
if (maybe_return_home(unit)) return;
if (probability(50) && short_term(unit)) return;
search_for_best_move(unit);
}
}
/* Picking the correct units for a group is essential to its success. */
/* We rate the unit for its suitability for each group (which will also */
/* be adjusted by the group's needs). */
decide_group(unit)
Unit *unit;
{
int g, t, suitability, best = 0, bestgroup = 0;
Plan *plan = side_plan(unit->side);
for (g = 1; g < MAXGROUPS; ++g) {
suitability = 0;
switch (plan->group[g].goal) {
case NOGROUP:
break;
case HITTARGET:
suitability += bhw[unit->type][plan->group[g].etype];
suitability += bthw[unit->type][plan->group[g].etype];
suitability = min(suitability, 10000);
suitability /= (distance(unit->x, unit->y,
plan->group[g].x, plan->group[g].y) + 1);
suitability /= (plan->group[g].size / 10 + 1);
/* should be fast units (?) with good odds against etype */
/* also need some way to decide about adding transports */
break;
case CAPTARGET:
suitability += bcw[unit->type][plan->group[g].etype];
suitability += btcw[unit->type][plan->group[g].etype];
suitability = min(suitability, 10000);
suitability /= (distance(unit->x, unit->y,
plan->group[g].x, plan->group[g].y) + 1);
suitability /= (plan->group[g].size / 10 + 1);
/* also need some way to decide about adding transports */
/* and maybe defenders for vulnerable capturers/transports */
/* size must be sufficient to be nearly certain of taking */
break;
case EXPLORE:
suitability = 100;
suitability /= (plan->group[g].size + 1);
break;
case DEFEND:
suitability = random(100);
break;
case OCCUPYHEX:
/* assign a group capable of reaching the hex */
break;
default:
case_panic("group goal", plan->group[g].goal);
break;
}
suitability *= (plan->group[g].priority + 1);
if (suitability > best) {
best = suitability;
bestgroup = g;
}
}
unit->group = bestgroup;
unit->goal = NOGOAL;
plan->group[bestgroup].size++;
if (Debug) printf("%s assigned to group %d\n",
unit_handle(NULL, unit), bestgroup);
}
/* Set up goals for units that need them. */
/* Goals should differ according to unit's role in group... */
decide_goal(unit)
Unit *unit;
{
Plan *plan = side_plan(unit->side);
switch (plan->group[unit->group].goal) {
case NOGOAL:
/* dubious */
unit->goal = DRIFT;
unit->gx = unit->gy = 0;
break;
case HITTARGET:
unit->goal = ASSAULT;
unit->gx = plan->group[unit->group].x;
unit->gy = plan->group[unit->group].y;
break;
case CAPTARGET:
if (could_capture(unit->type, plan->group[unit->group].etype)) {
unit->goal = ASSAULT;
} else if (probability(fullness(unit))) {
unit->goal = APPROACH;
} else {
unit->goal = LOAD;
}
unit->gx = plan->group[unit->group].x;
unit->gy = plan->group[unit->group].y;
break;
case EXPLORE:
unit->goal = APPROACH;
unit->gx = plan->group[unit->group].x;
unit->gy = plan->group[unit->group].y;
break;
case DEFEND:
unit->goal = DRIFT;
unit->gx = plan->group[unit->group].x;
unit->gy = plan->group[unit->group].y;
break;
case OCCUPYHEX:
unit->goal = APPROACH;
unit->gx = plan->group[unit->group].x;
unit->gy = plan->group[unit->group].y;
break;
default:
case_panic("group goal", plan->group[unit->group].goal);
break;
}
if (Debug) printf("%s group %d assigned goal %d -> %d,%d\n",
unit_handle(NULL, unit), unit->group, unit->goal,
unit->gx, unit->gy);
}
/* See if the location has a unit that can take us in for refueling */
/* (where's the check for refueling ability?) */
haven_p(x, y)
int x, y;
{
Unit *unit = unit_at(x, y);
return ((unit != NULL && mside == unit->side && alive(unit) &&
can_carry(unit, munit) && !might_be_captured(unit)));
}
/* See if the location has a unit that can repair us */
shop_p(x, y)
int x, y;
{
Unit *unit = unit_at(x, y);
return (unit != NULL && munit->side == unit->side && alive(unit) &&
can_carry(unit, munit) &&
could_repair(unit->type, munit->type));
}
/* See if we're in a bad way, either on supply or hits, and get to safety */
/* if possible. If not, then move on to other actions. */
/* Can't be 100% though, there might be some problem preventing move */
maybe_return_home(unit)
Unit *unit;
{
int ux = unit->x, uy = unit->y, ox, oy, range;
if (low_supplies(unit) && probability(98)) {
range = range_left(unit);
if (unit->transport) {
order_sentry(unit, 1);
return TRUE;
} else if (!unit->occupant) {
if (Debug) printf("%s should return\n", unit_handle(NULL, unit));
if ((range * range < numunits) ?
(search_area(ux, uy, range, haven_p, &ox, &oy)) :
(find_closest_unit(ux, uy, range, haven_p, &ox, &oy))) {
order_moveto(unit, ox, oy);
unit->orders.flags |= SHORTESTPATH;
unit->orders.flags &=
~(ENEMYWAKE|NEUTRALWAKE|SUPPLYWAKE|ATTACKUNIT);
if (Debug) printf(" will resupply\n");
return TRUE;
}
} else {
return FALSE; /* should be more detailed */
}
}
if (cripple(unit) && probability(98)) {
if (unit->transport) {
order_sentry(unit, 1);
return TRUE;
} else {
if (Debug) printf("%s should repair\n", unit_handle(NULL, unit));
range = range_left(unit);
if ((range * range < numunits) ?
(search_area(ux, uy, range, haven_p, &ox, &oy)) :
(find_closest_unit(ux, uy, range, shop_p, &ox, &oy))) {
order_moveto(unit, ox, oy);
unit->orders.flags &= ~SHORTESTPATH;
unit->orders.flags &=
~(ENEMYWAKE|NEUTRALWAKE|SUPPLYWAKE|ATTACKUNIT);
if (Debug) printf(" will repair\n");
return TRUE;
} else {
return FALSE;
}
}
}
if (out_of_ammo(unit) >= 0 && probability(80)) {
if (unit->transport) {
order_sentry(unit, 1);
return TRUE;
} else {
range = range_left(unit);
if (Debug) printf("%s should reload\n", unit_handle(NULL, unit));
if ((range * range < numunits) ?
(search_area(ux, uy, range, haven_p, &ox, &oy)) :
(find_closest_unit(ux, uy, range, haven_p, &ox, &oy))) {
order_moveto(unit, ox, oy);
unit->orders.flags &= ~SHORTESTPATH;
unit->orders.flags &=
~(ENEMYWAKE|NEUTRALWAKE|SUPPLYWAKE|ATTACKUNIT);
if (Debug) printf(" will reload\n");
return TRUE;
} else {
return FALSE;
}
}
}
return FALSE;
}
/* Return the distance that we can go by shortest path before running out */
/* of important supplies. Will return at least 1, since we can *always* */
/* move one hex to safety. This is a worst-case routine, too complicated */
/* to worry about units getting refreshed by terrain or whatever. */
range_left(unit)
Unit *unit;
{
int u = unit->type, r, least = 12345;
for_all_resource_types(r) {
if (utypes[u].tomove[r] > 0) least = min(least, unit->supply[r]);
if (utypes[u].consume[r] > 0)
least = min(least, unit->supply[r] / utypes[u].consume[r]);
}
return (least == 12345 ? 1 : least);
}
/* Do short-range planning. */
short_term(unit)
Unit *unit;
{
switch (unit->goal) {
case DRIFT:
case LOAD:
case APPROACH:
case ASSAULT:
break;
default:
case_panic("unit goal", munit->goal);
break;
}
return FALSE;
}
/* Given a position nearby the unit, evaluate it with respect to goals, */
/* general characteristics, and so forth. -10000 is very bad, 0 is OK, */
/* 10000 or so is best possible. */
/* Should downrate hexes within reach of enemy retaliation. */
/* Should downrate hexes requiring supply consumption to enter/occupy. */
evaluate_hex(x, y)
int x, y;
{
bool adjhex, ownhex;
int view, etype, dist, worth = 0;
int terr = terrain_at(x, y);
Side *es;
Unit *eunit;
view = side_view(mside, x, y);
dist = distance(munit->x, munit->y, x, y);
adjhex = (dist == 1);
ownhex = (dist == 0);
if (y <= 0 || y >= world.height-1) {
worth = -10000;
} else {
switch (munit->goal) {
case DRIFT:
if (ownhex) {
worth = -1;
} else if (view == UNSEEN) {
worth = random(100) / dist;
} else if (view == EMPTY) {
worth = -100;
if (impassable(munit, x, y)) worth -= 900;
} else {
es = side_n(vside(view));
etype = vtype(view);
if (es == NULL) {
if (could_capture(munit->type, etype)) {
worth = 20000 / dist;
} else {
worth = -10000;
}
} else if (!allied_side(mside, es)) {
worth = 200 + attack_worth(munit, etype);
worth += threat(mside, etype, x, y);
worth /= dist;
} else {
}
}
break;
case LOAD:
if (ownhex || view == UNSEEN || view == EMPTY) {
worth = -1;
} else {
es = side_n(vside(view));
if (mside == es) {
if ((eunit = unit_at(x, y)) != NULL) {
if (eunit->group == munit->group) {
worth = 4000;
worth /= dist;
}
}
} else {
worth = -100;
}
}
break;
case APPROACH:
case ASSAULT:
if (ownhex) {
worth = -100;
} else if (view == UNSEEN) {
} else if (view == EMPTY) {
if (impassable(munit, x, y)) worth -= 900;
} else if (x == munit->gx && y == munit->gy) {
worth = 10000;
} else {
es = side_n(vside(view));
etype = vtype(view);
if (es == NULL) {
if (could_capture(munit->type, etype)) {
worth = 20000 / dist;
} else {
worth = -10000;
}
} else if (!allied_side(mside, es)) {
worth = 200 + attack_worth(munit, etype);
worth += threat(mside, etype, x, y);
worth /= dist;
} else {
es = side_n(vside(view));
if (mside == es) {
if ((eunit = unit_at(x, y)) != NULL) {
if (eunit->group == munit->group &&
eunit->goal == LOAD &&
could_carry(eunit->type, munit->type)) {
worth = 4000;
worth /= dist;
}
}
} else {
worth = -100;
}
}
}
break;
default:
case_panic("unit goal", munit->goal);
break;
}
}
if ((munit->gx > 0 || munit->gy > 0) &&
(distance(x, y, munit->gx, munit->gy) <
distance(munit->x, munit->y, munit->gx, munit->gy))) {
worth += 1000;
}
worth -= 100;
worth += utypes[munit->type].productivity[terr];
aset(localworth, x, y, worth);
}
/* Scan evaluated area looking for best overall hex. */
int bestworth = -10000, bestx, besty;
maximize_worth(x, y)
int x, y;
{
int worth;
worth = aref(localworth, x, y);
if (worth >= 0) {
if (worth > bestworth) {
bestworth = worth; bestx = x; besty = y;
} else if (worth == bestworth && flip_coin()) {
bestworth = worth; bestx = x; besty = y;
}
}
}
/* Search for most favorable odds anywhere in the area, but only for */
/* the remaining moves in this turn. Multi-turn tactics is elsewhere. */
/* Need to be able to direct unit to sit at best hex if not at max move. */
/* Could compare value of hexes to value of goal here and go for goal maybe? */
search_for_best_move(unit)
Unit *unit;
{
int ux = unit->x, uy = unit->y, range = unit->movesleft;
if (!mobile(unit->type)) {
order_sentry(unit, 100);
return;
}
if (Debug) printf("%s ", unit_handle(NULL, unit));
bestworth = -20000;
apply_to_area(ux, uy, range, evaluate_hex);
apply_to_area(ux, uy, range, maximize_worth);
if (bestworth >= 5000) {
if (unit->transport != NULL && mobile(unit->transport->type)) {
if (Debug) printf("sleeping on transport\n");
order_sentry(unit, 5);
} else if ((ux == bestx && uy == besty) || !can_move(unit)) {
if (Debug) printf("staying put\n");
order_sentry(unit, 1);
} else if (probability(90)) {
if (Debug) printf("moving to %d,%d (worth %d)\n",
bestworth, bestx, besty);
order_moveto(unit, bestx, besty);
unit->orders.flags &= ~SHORTESTPATH;
} else {
if (Debug) printf("hanging around\n");
order_sentry(unit, random(5));
}
} else if (unit->goal != DRIFT && probability(90)) {
switch (unit->goal) {
case DRIFT:
break;
case LOAD:
if (unit->occupant != NULL) {
if (Debug) printf("starting off to goal\n");
unit->goal = APPROACH;
order_moveto(unit, unit->gx, unit->gy);
} else {
if (bestworth >= 0) {
if (Debug) printf("loading at %d,%d (worth %d)\n",
bestworth, bestx, besty);
order_moveto(unit, bestx, besty);
unit->orders.flags &= ~SHORTESTPATH;
} else {
if (Debug) printf("moving slowly about\n");
order_movedir(unit, random_dir(), 1);
}
}
break;
case APPROACH:
case ASSAULT:
if (unit->transport != NULL) {
if (unit->transport->group == unit->group) {
if (Debug) printf("riding in transport\n");
order_sentry(unit, 4);
} else if (!can_move(unit)) {
if (Debug) printf("waiting to get off\n");
order_sentry(unit, 2);
} else {
if (Debug) printf("leaving for %d,%d\n",
unit->gx, unit->gy);
order_moveto(unit, unit->gx, unit->gy);
}
} else {
if (Debug) printf("approaching %d,%d\n", unit->gx, unit->gy);
order_moveto(unit, unit->gx, unit->gy);
}
break;
default:
case_panic("unit goal", munit->goal);
break;
}
} else {
if (can_produce(unit) && unit->transport == NULL && probability(90)) {
if (Debug) printf("going to build something\n");
set_product(unit, machine_product(unit));
set_schedule(unit);
order_sentry(unit, unit->schedule+1);
} else if (probability(90)) {
if (Debug) printf("going in random direction\n");
order_movedir(unit, random_dir(), random(3)+1);
} else {
if (Debug) printf("hanging around\n");
order_sentry(unit, random(4)+1);
}
}
}
/* This is a heuristic estimation of the value of one unit type hitting */
/* on another. Should take cost of production into account as well as the */
/* chance and significance of any effect. */
attack_worth(unit, etype)
Unit *unit;
int etype;
{
int utype = unit->type, worth;
worth = bhw[utype][etype];
if (utypes[utype].damage[etype] >= utypes[etype].hp)
worth *= 2;
if (utypes[etype].damage[utype] >= unit->hp)
worth /= (could_capture(utype, etype) ? 1 : 4);
if (could_capture(utype, etype)) worth *= 4;
return worth;
}
/* Support functions. */
/* True if unit is in immediate danger of being captured. */
/* Needs check on capturer transport being seen. */
might_be_captured(unit)
Unit *unit;
{
int d, x, y;
Unit *unit2;
for_all_directions(d) {
x = wrap(unit->x + dirx[d]); y = unit->y + diry[d];
if (((unit2 = unit_at(x, y)) != NULL) &&
(enemy_side(unit->side, unit2->side)) &&
(could_capture(unit2->type, unit->type))) return TRUE;
}
return FALSE;
}
/* Return true if the given unit type at given position is threatened. */
threat(side, u, x0, y0)
Side *side;
int u, x0, y0;
{
int d, x, y, view, thr = 0;
Side *side2;
for_all_directions(d) {
x = wrap(x0 + dirx[d]); y = y0 + diry[d];
view = side_view(side, x, y);
if (view != UNSEEN && view != EMPTY) {
side2 = side_n(vside(view));
if (allied_side(side, side2)) {
if (could_capture(u, vtype(view))) thr += 1000;
if (bhw[u][vtype(view)] > 0) thr += 100;
}
}
}
return thr;
}
/* Test if unit can move out into adjacent hexes. */
can_move(unit)
Unit *unit;
{
int d, x, y;
for_all_directions(d) {
x = wrap(unit->x + dirx[d]); y = limit(unit->y + diry[d]);
if (could_move(unit->type, terrain_at(x, y))) return TRUE;
}
return FALSE;
}
/* Returns the type of missing supplies. */
out_of_ammo(unit)
Unit *unit;
{
int u = unit->type, r;
for_all_resource_types(r) {
if (utypes[u].hitswith[r] > 0 && unit->supply[r] <= 0)
return r;
}
return (-1);
}
/* Returns TRUE if type is capturable somehow. */
capturable(e)
int e;
{
int u;
if (utypes[e].surrender > 0 || utypes[e].siege > 0) return TRUE;
for_all_unit_types(u) if (could_capture(u, e)) return TRUE;
return FALSE;
}
/* True if the given unit is a sort that can build other units. */
can_produce(unit)
Unit *unit;
{
int p;
for_all_unit_types(p) {
if (could_make(unit->type, p)) return TRUE;
}
return FALSE;
}
/* Return percentage of capacity. */
fullness(unit)
Unit *unit;
{
int u = unit->type, o, cap = 0, num = 0, vol = 0;
Unit *occ;
for_all_unit_types(o) cap += utypes[u].capacity[o];
for_all_occupants(unit, occ) {
num++;
vol += utypes[occ->type].volume;
}
if (utypes[u].holdvolume > 0) {
return ((100 * vol) / utypes[u].holdvolume);
} else if (cap > 0) {
return ((100 * num) / cap);
} else {
fprintf(stderr, "Fullness ???\n");
}
}
find_closest_unit(x0, y0, maxdist, pred, rxp, ryp)
int x0, y0, maxdist, (*pred)(), *rxp, *ryp;
{
Unit *unit;
for_all_units(unit) {
if (distance(x0, y0, unit->x, unit->y) <= maxdist) {
if ((*pred)(unit->x, unit->y)) {
*rxp = unit->x; *ryp = unit->y;
return TRUE;
}
}
}
return FALSE;
}