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