DataMuseum.dk

Presents historical artifacts from the history of:

DKUUG/EUUG Conference tapes

This is an automatic "excavation" of a thematic subset of
artifacts from Datamuseum.dk's BitArchive.

See our Wiki for more about DKUUG/EUUG Conference tapes

Excavated with: AutoArchaeologist - Free & Open Source Software.


top - metrics - download
Index: T m

⟦a81301340⟧ TextFile

    Length: 32778 (0x800a)
    Types: TextFile
    Names: »mplay.c«

Derivation

└─⟦b20c6495f⟧ Bits:30007238 EUUGD18: Wien-båndet, efterår 1987
    └─⟦this⟧ »EUUGD18/X/Xconq/mplay.c« 

TextFile

/* 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;
}