/* Implementation of the "mplayer" AI in Xconq. Copyright (C) 1987-1989, 1991-2000 Stanley T. Shebs. Xconq is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. See the file COPYING. */ #include "conq.h" #include "kpublic.h" #include "ai.h" extern void register_mplayer(AI_ops *ops); static Theater *tmptheater; static Side *anewside; /* Local function declarations. */ static void mplayer_init(Side *side); static void mplayer_init_turn(Side *side); static void mplayer_create_strategy(Side *side); static void reset_strategy(Side *side); static void analyze_the_game(Side *side); static void determine_subgoals(Side *side); static void review_theaters(Side *side); static void create_initial_theaters(Side *side); static Theater *create_theater(Side *side); static void remove_theater(Side *side, Theater *theater); static void move_theater_cell(int x, int y); static void remove_small_theaters(Side *side); static void compute_theater_bounds(Side *side); static void review_goals(Side *side); static void mplayer_review_units(Side *side); static void update_side_strategy(Side *side); static void decide_theater_needs(Side *side, Theater *theater); static void estimate_strengths(Side *side); static void decide_resignation(Side *side); static void add_goal(Side *side, Goal *goal); static Goal *has_goal(Side *side, GoalType goaltype); static Goal *has_unsatisfied_goal(Side *side, GoalType goaltype); static void mplayer_decide_plan(Side *side, Unit *unit); static int mplayer_adjust_plan(Side *side, Unit *unit); static int need_explorers(Side *side); static void mplayer_react_to_task_result(Side *side, Unit *unit, Task *task, TaskOutcome rslt); static void change_to_adjacent_theater(Side *side, Unit *unit); static void mplayer_react_to_new_side(Side *side, Side *side2); static void mplayer_finish_movement(Side *side); static Unit *search_for_available_transport(Unit *unit, int purpose); static void mplayer_rethink_plan(Unit *unit); static char *mplayer_at_desig(Side *side, int x, int y); static int mplayer_theater_at(Side *side, int x, int y); static int mplayer_read_strengths(Side *side); static Obj *mplayer_save_state(Side *side); #if 0 static void mplayer_react_to_unit_loss(Side *side, Unit *unit); #endif void register_mplayer(AI_ops *ops) { ops->name = "mplayer"; ops->help = "general AI that can play any game"; ops->to_init = mplayer_init; ops->to_init_turn = mplayer_init_turn; ops->to_decide_plan = mplayer_decide_plan; ops->to_react_to_task_result = mplayer_react_to_task_result; ops->to_react_to_new_side = mplayer_react_to_new_side; ops->to_adjust_plan = mplayer_adjust_plan; ops->to_finish_movement = mplayer_finish_movement; ops->to_save_state = mplayer_save_state; ops->region_at = mplayer_theater_at; ops->at_desig = mplayer_at_desig; }; static void mplayer_init(Side *side) { Unit *unit; if (game_class == gc_none) { game_class = find_game_class(); } /* Delete any old strategy object in case we just switched AI type. */ if (side->ai != NULL) { free(side->ai); side->ai = NULL; } /* Then always create a new strategy from scratch. */ mplayer_create_strategy(side); /* If the side has no units at the moment, it doesn't really need to plan. */ if (!side_has_units(side)) return; /* Compute an initial estimation of units on each side. */ /* (Needed for save/restore consistency, otherwise not critical to do here.) */ estimate_strengths(side); /* Study the scorekeepers and such, decide how to play the game. */ analyze_the_game(side); /* Reset plans of any units that were not doing anything. */ for_all_side_units(side, unit) { if (in_play(unit) && ai_controlled(unit)) { net_force_replan(side, unit, TRUE); } } } /* At the beginning of each turn, make plans and review the situation. */ static void mplayer_init_turn(Side *side) { int u, u2; /* Cases where we no longer need to run. */ if (!side->ingame) return; /* A side without units hasn't got anything to do but wait. */ /* (should account for possible units on controlled sides) */ if (!side_has_units(side)) return; /* Mplayers in a hacked game will not play, unless they're being debugged. */ if (compromised && !DebugM) return; update_all_progress_displays("ai turn init start", side->id); DMprintf("%s mplayer init turn\n", side_desig(side)); /* Make sure a strategy object exists. */ if (ai(side) == NULL) mplayer_create_strategy(side); /* Look over the game design we're playing with. */ analyze_the_game(side); if (ai(side)->report_not_understood) { notify_all("%s AI doesn't understand scoring in this game!", short_side_title(side)); ai(side)->report_not_understood = FALSE; } /* code specific to the "time" game */ if (game_class == gc_time) { for_all_unit_types(u) { if (ai(side)->develop_status[u] == RS_DEVELOP_ASSIGNED) { u2 = ai(side)->develop_on[u]; if (!needs_develop (side, u2)) { /* develop done, start upgrading */ DMprintf("%s has completed develop on %s\n", side_desig(side), u_type_name(u2)); ai(side)->develop_status[u] = RS_UPGRADE_NEEDED; } } } } /* If this game is one that can be won, as opposed to just dinking around, figure how to win it. */ if (ai(side)->trytowin) { /* Check out the current goal tree first. */ review_goals(side); /* Goal analysis might have triggered resignation. */ if (!side->ingame) goto done; /* Check out all the theaters. */ review_theaters(side); /* Check out all of our units. */ mplayer_review_units(side); /* (should be integrated better) */ mplayer_finish_movement(side); /* Decide on the new current plan. */ update_side_strategy(side); /* Propagate this to individual unit plans. */ update_unit_plans(side); } else { update_unit_plans_randomly(side); } done: update_all_progress_displays("", side->id); DMprintf("%s mplayer init turn done\n", side_desig(side)); } /* Create and install an entirely new strategy object for the side. */ void mplayer_create_strategy(Side *side) { Strategy *strategy = (Strategy *) xmalloc(sizeof(Strategy)); /* Put the specific structure into a generic slot. */ side->ai = (struct a_ai *) strategy; /* Allocate a table of pointers to theaters, for access via small numbers rather than full pointers. */ strategy->theatertable = (Theater **) xmalloc(127 * sizeof(Theater *)); /* Allocate a layer of indexes into the theater table. */ strategy->areatheaters = malloc_area_layer(char); /* Allocate random things. */ /* Arrays for unit types. */ strategy->actualmix = (short *) xmalloc(numutypes * sizeof(short)); strategy->expectedmix = (short *) xmalloc(numutypes * sizeof(short)); strategy->idealmix = (short *) xmalloc(numutypes * sizeof(short)); strategy->develop_status = (short *) xmalloc(numutypes * sizeof(short)); strategy->develop_on = (short *) xmalloc(numutypes * sizeof(short)); /* Arrays for terrain types. */ strategy->terrainguess = (short *) xmalloc(numttypes * sizeof(short)); strategy->writable_state = lispnil; /* Set everything to correct initial values. */ reset_strategy(side); } /* Put all the right initial values into the strategy, but don't allocate anything. */ static void reset_strategy(Side *side) { int u, u2, t, dir; Strategy *strategy = (Strategy *) side->ai; /* Remember when we did this. */ strategy->creationdate = g_turn(); /* Null out various stuff. */ strategy->numgoals = 0; strategy->theaters = NULL; /* Actually we start with no theaters, but it's convenient to leave entry 0 in the theater table pointing to NULL. */ strategy->numtheaters = 1; /* Clear pointers to special-purpose theaters. */ strategy->homefront = NULL; for_all_directions(dir) { strategy->perimeters[dir] = NULL; strategy->midranges[dir] = NULL; strategy->remotes[dir] = NULL; } strategy->explorersneeded = 0; /* Reset the summation of our exploration needs. */ for_all_unit_types(u) { strategy->actualmix[u] = 0; strategy->expectedmix[u] = 0; strategy->idealmix[u] = 0; strategy->develop_status[u] = 0; strategy->develop_on[u] = 0; /* code specific to the "time" game */ if (game_class == gc_time) { for_all_unit_types(u2) { if (needs_develop (side, u2) && can_develop_on(u, u2)) { strategy->develop_status[u] = RS_DEVELOP_NEEDED; strategy->develop_on[u] = u2; DMprintf("%s can develop on %s (to level %d)\n", u_type_name(u), u_type_name(u2), u_tech_to_build(u2)); } } } } for_all_terrain_types(t) { strategy->terrainguess[t] = 0; } strategy->analyzegame = TRUE; /* Analyze the game and decide our basic goals. */ analyze_the_game(side); } /* Look over the game design and decide what we're supposed to be doing, if anything at all. This just sets up toplevel goals based on the game design, does not evaluate goals or any such. */ static void analyze_the_game(Side *side) { int maybedraw, i; Goal *goal; if (ai(side)->analyzegame) { if (should_try_to_win(side)) { ai(side)->trytowin = TRUE; /* This is our whole purpose in the game. */ goal = create_goal(GOAL_WON_GAME, side, TRUE); add_goal(side, goal); /* Now figure what exactly we have to do in order to win. */ determine_subgoals(side); /* Machine will want to keep playing as long as it thinks it has a chance to win. */ maybedraw = FALSE; } else { ai(side)->trytowin = FALSE; /* Since the side is not trying to win anything, it will be pretty laidback about whether to keep the game going. */ maybedraw = TRUE; } /* Be trusting about game saves, at least for now. (The problem is that a human player could escape fate by saving the game and then either editing the saved game or just throwing it away.) */ if (TRUE != side->willingtosave /* (should) and decision delegated to AI */) net_set_willing_to_save(side, TRUE); if (maybedraw != side->willingtodraw /* (should) and decision delegated to AI */) try_to_draw(side, maybedraw, "mplayer"); ai(side)->analyzegame = FALSE; /* Summarize our analysis of this game. */ DMprintf("%s will try to %s this game\n", side_desig(side), ai(side)->trytowin ? "win" : "have fun in"); for (i = 0; i < ai(side)->numgoals; ++i) { goal = ai(side)->goals[i]; DMprintf("%s has %s\n", side_desig(side), goal_desig(goal)); } } } static void determine_subgoals(Side *side) { int numvicgoals, understood; Unit *unit; Side *side2; Scorekeeper *sk; Goal *goal; understood = TRUE; /* Look at each scorekeeper and decide on appropriate goals. */ for_all_scorekeepers(sk) { /* (should test who scorekeeper applies to) */ if (match_keyword(sk->body, K_LAST_SIDE_WINS) || match_keyword(sk->body, K_LAST_ALLIANCE_WINS)) { /* We want to "kick butt" - *everybody* else's butt. */ for_all_sides(side2) { if (!trusted_side(side, side2) && side2->ingame) { /* Our goals include preventing other sides from accomplishing theirs. */ goal = create_goal(GOAL_WON_GAME, side2, FALSE); add_goal(side, goal); /* (should add "search-and-destroy" as corollaries) */ } } /* Add goals to protect our own units. */ numvicgoals = 0; for_all_side_units(side, unit) { if (point_value(unit) > 0 /* (should be "n most valuable") */ && in_play(unit) && numvicgoals < 10) { goal = create_goal(GOAL_VICINITY_HELD, side, TRUE); goal->args[0] = unit->x; goal->args[1] = unit->y; goal->args[2] = goal->args[3] = 2; add_goal(side, goal); ++numvicgoals; } } } else if (sk->initial != -10001) { /* This is a numerical scorekeeper whose value we want to maximize. */ if (consp(sk->body) && match_keyword(car(sk->body), K_SET)) { if (consp(cadr(sk->body)) && match_keyword(car(cadr(sk->body)), K_SUM)) { understood = FALSE; } else { understood = FALSE; } } else { understood = FALSE; } } else { understood = FALSE; } } if (!understood) { /* Can't notify anybody yet, no windows up, so record */ ai(side)->report_not_understood = TRUE; DMprintf("%s AI doesn't understand scoring in this game!", short_side_title(side)); } /* We might develop a sudden interest in exploration. */ /* (but should only be if information is really important to winning) */ if (!side->see_all) { if (!g_terrain_seen()) { add_goal(side, create_goal(GOAL_WORLD_KNOWN, side, TRUE)); } /* It will be important to keep track of other sides' units as much as possible. */ for_all_sides(side2) { if (side != side2) { goal = create_goal(GOAL_POSITIONS_KNOWN, side, TRUE); goal->args[0] = (long) side2; add_goal(side, goal); } } /* Also add the general goal of knowing where indeps are. */ goal = create_goal(GOAL_POSITIONS_KNOWN, side, TRUE); goal->args[0] = (long) NULL; add_goal(side, goal); } } /* Do a combination of analyzing existing theaters and creating new ones. */ static void review_theaters(Side *side) { int x, y, u, s, pop, totnumunits; int firstcontact = FALSE; int homefound = FALSE; Unit *unit; UnitView *uview; Side *firstcontactside, *homefoundside, *otherside, *side2; Theater *theater; /* Create some theaters if none exist. */ if (ai(side)->theaters == NULL) { create_initial_theaters(side); compute_theater_bounds(side); } for_all_theaters(side, theater) { theater->allied_units = 0; theater->makers = 0; theater->unexplored = 0; theater->border = FALSE; theater->allied_bases = 0; for_all_unit_types(u) { theater->numassigned[u] = 0; theater->numneeded[u] = 0; theater->numenemies[u] = 0; theater->numsuspected[u] = theater->numsuspectedmax[u] = 0; theater->numtotransport[u] = 0; } if (people_sides_defined()) { for (s = 0; s <= numsides; ++s) theater->people[s] = 0; } theater->units_lost /= 2; } /* Now look at all the units that we can. */ for_all_side_units(side, unit) { if (in_play(unit)) { theater = unit_theater(unit); if (theater != NULL) { ++(theater->allied_units); ++(theater->numassigned[unit->type]); if (isbase(unit)) ++(theater->allied_bases); if (unit->plan && unit->plan->waitingfortransport) ++(theater->numtotransport[unit->type]); } } } /* (should also analyze allies etc) */ /* Now look at the whole world. */ for_all_interior_cells(x, y) { theater = theater_at(side, x, y); if (theater != NULL) { if (side->see_all) { /* We get to look at the real units. */ for_all_stack(x, y, unit) { /* what about occupants? */ if (in_play(unit) && !trusted_side(side, unit->side) && (!indep(unit) || u_point_value(unit->type) > 0)) { if (enemy_side(side, unit->side)) ++(theater->numenemies[unit->type]); if (ai(side)->contacted[unit->side->id] == 0) { ai(side)->contacted[unit->side->id] = 1; if (!indep(unit)) { firstcontact = TRUE; firstcontactside = unit->side; } } if (ai(side)->homefound[unit->side->id] == 0 && !mobile(unit->type)) { ai(side)->homefound[unit->side->id] = 1; if (!indep(unit)) { homefound = TRUE; homefoundside = unit->side; } } } } if (people_sides_defined()) { pop = people_side_at(x, y); if (pop != NOBODY) { ++(theater->people[pop]); if (ai(side)->homefound[pop] == 0) { ai(side)->homefound[pop] = 1; if (pop != 0) { homefound = TRUE; homefoundside = side_n(pop); } } } } } else { /* We must satisfy ourselves with unit views. */ if (terrain_view(side, x, y) == UNSEEN) { ++(theater->unexplored); } else { for_all_view_stack(side, x, y, uview) { side2 = view_side(uview); if (enemy_side(side, side2)) { /* Note that we assume indeps are enemies here. */ u = view_type(uview); if (u_point_value(u) > 0) { ++(theater->numsuspected[u]); ++(theater->numsuspectedmax[u]); } } } if (people_sides_defined()) { pop = people_side_at(x, y); if (pop != NOBODY) { ++(theater->people[pop]); } } } } } } for_all_theaters(side, theater) { theater->x = (theater->xmin + theater->xmax) / 2; theater->y = (theater->ymin + theater->ymax) / 2; theater->enemystrengthmin = theater->enemystrengthmax = 0; for_all_unit_types(u) { theater->enemystrengthmin += theater->numenemies[u] + theater->numsuspected[u]; } theater->enemystrengthmax = theater->enemystrengthmin; } if (firstcontact || homefound) { for_all_side_units(side, unit) { if (ai_controlled(unit)) { net_force_replan(side, unit, FALSE); set_unit_theater(unit, NULL); update_unit_display(side, unit, TRUE); } } } for_all_theaters(side, theater) { DMprintf("%s theater \"%s\" at %d,%d from %d,%d to %d,%d (size %d)\n", side_desig(side), theater->name, theater->x, theater->y, theater->xmin, theater->ymin, theater->xmax, theater->ymax, theater->size); /* Summarize what we know about the theater. */ DMprintf("%s theater \"%s\"", side_desig(side), theater->name); if (!side->see_all && theater->unexplored > 0) { DMprintf(" unexplored %d", theater->unexplored); } DMprintf(" enemy %d", theater->enemystrengthmin); if (theater->enemystrengthmin != theater->enemystrengthmax) { DMprintf("-%d", theater->enemystrengthmax); } for_all_unit_types(u) { if (theater->numenemies[u] + theater->numsuspected[u] > 0) { DMprintf(" %3s %d", u_type_name(u), theater->numenemies[u]); if (theater->numsuspected[u] > 0) { DMprintf("+%d", theater->numsuspected[u]); } } } if (people_sides_defined()) { DMprintf(" people"); for (s = 0; s <= numsides; ++s) { if (theater->people[s] > 0) { DMprintf(" s%d %d", s, theater->people[s]); } } } DMprintf("\n"); totnumunits = 0; for_all_unit_types(u) { totnumunits += (theater->numassigned[u] + theater->numneeded[u] + theater->numtotransport[u]); } if (totnumunits > 0) { /* Summarize the status of our own units in this theater. */ DMprintf("%s theater \"%s\" has ", side_desig(side), theater->name); for_all_unit_types(u) { if (theater->numassigned[u] + theater->numneeded[u] + theater->numtotransport[u] > 0) { DMprintf(" %d %3s", theater->numassigned[u], u_type_name(u)); if (theater->numneeded[u] > 0) { DMprintf(" (of %d needed)", theater->numneeded[u]); } if (theater->numtotransport[u] > 0) { DMprintf(" (%d awaiting transport)", theater->numtotransport[u]); } } } DMprintf("\n"); } } /* Also summarize contacts. */ for_all_sides(otherside) { if (otherside != side) { if (ai(side)->contacted[otherside->id]) { DMprintf("%s contacted s%d", side_desig(side), otherside->id); if (ai(side)->homefound[otherside->id]) { DMprintf(", home found"); } DMprintf("\n"); } } } } /* Set up the initial set of theaters. */ static void create_initial_theaters(Side *side) { int x, y, dir, dist, i, j; int xmin, ymin, xmax, ymax; int homeradius, perimradius, midradius, xxx; int numthx, numthy, thwid, thhgt; Unit *unit; Theater *homefront, *enemyarea, *theater; Theater *gridtheaters[8][8]; Strategy *strategy = ai(side); for (i = 0; i < 8; ++i) { for (j = 0; j < 8; ++j) { gridtheaters[i][j] = NULL; } } /* Compute bbox of initial (should also do enemy?) units. */ xmin = area.width; ymin = area.height; xmax = ymax = 0; for_all_side_units(side, unit) { if (alive(unit) /* and other preconditions? */) { if (unit->x < xmin) xmin = unit->x; if (unit->y < ymin) ymin = unit->y; if (unit->x > xmax) xmax = unit->x; if (unit->y > ymax) ymax = unit->y; } } /* Most games start with each side's units grouped closely together. If this is not the case, do something else. */ if (xmax - xmin > area.width / 4 && ymax - ymin > area.height / 4) { /* (should do some sort of clustering of units) */ if (0 /*people_sides_defined()*/) { homefront = create_theater(side); homefront->name = "Home Front"; enemyarea = create_theater(side); enemyarea->name = "Enemy Area"; for_all_interior_cells(x, y) { if (people_side_at(x, y) == side->id) { set_theater_at(side, x, y, homefront); } else { set_theater_at(side, x, y, enemyarea); } } } else { /* Divide the world up along a grid. */ numthx = (area.width > 60 ? (area.width > 120 ? 7 : 5) : 3); numthy = (area.height > 60 ? (area.height > 120 ? 7 : 5) : 3); thwid = max(8, area.width / numthx); thhgt = max(8, area.height / numthy); for_all_interior_cells(x, y) { i = x / thwid; j = y / thhgt; if (gridtheaters[i][j] == NULL) { theater = create_theater(side); sprintf(spbuf, "Grid %d,%d", i, j); theater->name = copy_string(spbuf); theater->x = x; theater->y = y; gridtheaters[i][j] = theater; } else { theater = gridtheaters[i][j]; } set_theater_at(side, x, y, theater); } } return; } else { /* Always create a first theater that covers the starting area. */ homefront = create_theater(side); homefront->name = "Home Front"; /* Calculate startxy if not already available. */ if (side->startx < 0 && side->starty < 0) calc_start_xy(side); homefront->x = side->startx; homefront->y = side->starty; strategy->homefront = homefront; homeradius = max(5, g_radius_min()); perimradius = max(homeradius + 5, g_separation_min() - homeradius); midradius = max(perimradius + 10, g_separation_min() * 2); xxx = max((side->startx - perimradius), (area.width - side->startx - perimradius)); xxx /= 2; midradius = min(midradius, perimradius + xxx); for_all_interior_cells(x, y) { dist = distance(x, y, side->startx, side->starty); if (people_sides_defined() && people_side_at(x, y) == side->id && dist < (perimradius - 3)) { set_theater_at(side, x, y, homefront); } else { if (dist < homeradius) { set_theater_at(side, x, y, homefront); } else { dir = approx_dir(x - side->startx, y - side->starty); if (dist < perimradius) { if (strategy->perimeters[dir] == NULL) { theater = create_theater(side); sprintf(spbuf, "Perimeter %s", dirnames[dir]); theater->name = copy_string(spbuf); theater->x = x; theater->y = y; strategy->perimeters[dir] = theater; } else { theater = strategy->perimeters[dir]; } } else if (dist < midradius) { if (strategy->midranges[dir] == NULL) { theater = create_theater(side); sprintf(spbuf, "Midrange %s", dirnames[dir]); theater->name = copy_string(spbuf); theater->x = x; theater->y = y; strategy->midranges[dir] = theater; } else { theater = strategy->midranges[dir]; } } else { if (strategy->remotes[dir] == NULL) { theater = create_theater(side); sprintf(spbuf, "Remote %s", dirnames[dir]); theater->name = copy_string(spbuf); theater->x = x; theater->y = y; strategy->remotes[dir] = theater; } else { theater = strategy->remotes[dir]; } } set_theater_at(side, x, y, theater); } } } } remove_small_theaters(side); /* Assign all units to the theater they're currently in. */ /* (how do reinforcements get handled? mplayer should get hold of perhaps) */ for_all_side_units(side, unit) { if (in_play(unit) /* and other preconditions? */) { set_unit_theater(unit, theater_at(side, unit->x, unit->y)); } } } /* Create a single theater object and link it into the list of theaters. */ /* (should be able to re-use theaters in already in theater table) */ static Theater * create_theater(Side *side) { Theater *theater = (Theater *) xmalloc(sizeof(Theater)); if (ai(side)->numtheaters > MAXTHEATERS) return NULL; theater->id = (ai(side)->numtheaters)++; theater->name = "?"; theater->maingoal = NULL; theater->people = (int *) xmalloc ((numsides + 1) * sizeof(int)); /* (should alloc other array slots too) */ /* Connect theater into a linked list. */ theater->next = ai(side)->theaters; ai(side)->theaters = theater; /* Install it into the theater table also. */ ai(side)->theatertable[theater->id] = theater; return theater; } /* Clear all references to the theater and remove it from the list. Note that the theater size must already be zero. */ static void remove_theater(Side *side, Theater *theater) { int dir; Theater *prev; if (ai(side)->homefront == theater) ai(side)->homefront = NULL; for_all_directions(dir) { if (ai(side)->perimeters[dir] == theater) ai(side)->perimeters[dir] = NULL; if (ai(side)->midranges[dir] == theater) ai(side)->midranges[dir] = NULL; if (ai(side)->remotes[dir] == theater) ai(side)->remotes[dir] = NULL; } if (ai(side)->theaters == theater) ai(side)->theaters = theater->next; else { prev = NULL; for_all_theaters(side, prev) { if (prev->next == theater) { prev->next = theater->next; break; } } /* If prev still null, badness */ } --(ai(side)->numtheaters); } static void move_theater_cell(int x, int y) { int dir, x1, y1; Theater *theater2; if (theater_at(tmpside, x, y) == tmptheater) { for_all_directions(dir) { if (interior_point_in_dir(x, y, dir, &x1, &y1)) { theater2 = theater_at(tmpside, x1, y1); if (theater2 != NULL && theater2 != tmptheater) { set_theater_at(tmpside, x, y, theater2); ++(theater2->size); /* (should recompute bbox too) */ --(tmptheater->size); } } } } } static void remove_small_theaters(Side *side) { int domore; Theater *theater; compute_theater_bounds(side); domore = TRUE; while (domore) { domore = FALSE; for_all_theaters(side, theater) { if (between(1, theater->size, 5)) { tmpside = side; tmptheater = theater; apply_to_area(theater->x, theater->y, 6, move_theater_cell); if (theater->size == 0) { remove_theater(side, theater); /* Have to start over now. */ domore = TRUE; break; } } } } /* Redo, many random changes to bounds. */ compute_theater_bounds(side); } /* Compute the size and bounding box of each theater. This should be run each time theaters change in size or shape. */ static void compute_theater_bounds(Side *side) { int x, y; Theater *theater; for_all_theaters(side, theater) { theater->size = 0; theater->xmin = theater->ymin = -1; theater->xmax = theater->ymax = -1; } for_all_interior_cells(x, y) { theater = theater_at(side, x, y); if (theater != NULL) { ++(theater->size); /* Compute bounding box of theater if not already done. */ if (theater->xmin < 0 || x < theater->xmin) theater->xmin = x; if (theater->ymin < 0 || y < theater->ymin) theater->ymin = y; if (theater->xmax < 0 || x > theater->xmax) theater->xmax = x; if (theater->ymax < 0 || y > theater->ymax) theater->ymax = y; } } } /* Examine the goals to see what has been accomplished and what still needs to be done. */ static void review_goals(Side *side) { int i; Scorekeeper *sk; Goal *goal; Side *side2; Strategy *strategy = ai(side); /* First check on our friends and enemies. */ for_all_sides(side2) { /* If they're not trusting us, we don't want to trust them. */ /* (should be able to update this immediately after other side changes trust) */ if (!trusted_side(side2, side) && trusted_side(side, side2)) net_set_trust(side, side2, FALSE); } for (i = 0; i < strategy->numgoals; ++i) { goal = strategy->goals[i]; DMprintf("%s has %s\n", side_desig(side), goal_desig(goal)); } /* Should look at certainty of each goal and decide whether to keep or drop it, and mention in debug output also. */ /* Also think about resigning. */ if (g_ai_may_resign() && keeping_score()) { for_all_scorekeepers(sk) { if (symbolp(sk->body) && (match_keyword(sk->body, K_LAST_SIDE_WINS) || match_keyword(sk->body, K_LAST_ALLIANCE_WINS))) { decide_resignation(side); } } } } static void estimate_strengths(Side *side) { int u, sn1, x, y; Side *side1, *side2; Strategy *strategy = ai(side); Unit *unit; UnitView *uview; for_all_sides(side1) { sn1 = side1->id; for_all_unit_types(u) { strategy->strengths[sn1][u] = 0; } /* this lets us count even semi-trusted allies' units accurately... */ if (side1 == side || allied_side(side, side1)) { for_all_side_units(side1, unit) { /* Note that we count off-area units, since they are reinforcements usually. */ if (alive(unit) && completed(unit)) { ++(strategy->strengths[sn1][unit->type]); } } } } if (side->see_all || (!strategy->initial_strengths_computed && !mplayer_read_strengths(side))) { /* If we can see everything, we can add up units accurately. We also allow initial strengths to be known accurately; this prevents the AI from resigning just because an important unit appears for the first time, even though all players know that it was one of the starting units. */ for_all_cells(x, y) { for_all_stack(x, y, unit) { side2 = unit->side; if (side2 != NULL && !(side2 == side || allied_side(side, side2))) { if (completed(unit)) { ++(strategy->strengths[side2->id][unit->type]); } } } } } else { /* Look at the current view to get enemy strength. */ /* This is too easily faked, and doesn't know about hiding units... */ /* Should also discount old data. */ for_all_cells(x, y) { for_all_view_stack(side, x, y, uview) { side2 = view_side(uview); /* Count only units on other sides. */ if (!(side2 == side || allied_side(side, side2))) ++(strategy->strengths[side2->id][view_type(uview)]); } } } /* Estimate point values. */ /* (should try to account for individual units with special point values) */ for_all_sides(side1) { sn1 = side1->id; strategy->points[sn1] = 0; for_all_unit_types(u) { strategy->points[sn1] += strategy->strengths[sn1][u] * u_point_value(u); } } /* Estimate how many of each type in allied group. */ for_all_sides(side1) { sn1 = side1->id; for_all_unit_types(u) { strategy->alstrengths[sn1][u] = strategy->strengths[sn1][u]; for_all_sides(side2) { if (side1 != side2 && allied_side(side1, side2)) { strategy->alstrengths[sn1][u] += strategy->strengths[side2->id][u]; } } } strategy->alpoints[sn1] = strategy->points[sn1]; for_all_sides(side2) { if (side1 != side2 && allied_side(side1, side2)) { strategy->alpoints[sn1] += strategy->points[side2->id]; } } } /* The first time we estimate strength, record it specially. Later we will use this to compute which sides are getting stronger/weaker as time goes on. */ if (!strategy->initial_strengths_computed) { if (!mplayer_read_strengths(side)) { for_all_sides(side1) { sn1 = side1->id; strategy->points0[sn1] = strategy->points[sn1]; strategy->alpoints0[sn1] = strategy->alpoints[sn1]; for_all_unit_types(u) { strategy->strengths0[sn1][u] = strategy->strengths[sn1][u]; strategy->alstrengths0[sn1][u] = strategy->alstrengths[sn1][u]; } } } ai_save_state(side); strategy->initial_strengths_computed = TRUE; } /* If we're calling strength estimation because a new side has come into existence, use the current strengths as the new side's initial strengths. */ if (anewside != NULL) { sn1 = anewside->id; strategy->points0[sn1] = strategy->points[sn1]; strategy->alpoints0[sn1] = strategy->alpoints[sn1]; for_all_unit_types(u) { strategy->strengths0[sn1][u] = strategy->strengths[sn1][u]; strategy->alstrengths0[sn1][u] = strategy->alstrengths[sn1][u]; } /* Have to redo the saveable state also; force this by blasting any existing recordable state (it should all be re-creatable from mplayer's internal state). */ strategy->writable_state = lispnil; ai_save_state(side); } /* Dump out a detailed listing of our estimates. */ if (DebugM) { for_all_sides(side1) { sn1 = side1->id; DMprintf("%s ", side_desig(side)); DMprintf("est init streng of %s: ", side_desig(side1)); for_all_unit_types(u) { DMprintf(" %d", strategy->strengths0[sn1][u]); } DMprintf(" (%d points)\n", strategy->points0[sn1]); DMprintf("%s ", side_desig(side)); DMprintf("est curr streng of %s: ", side_desig(side1)); for_all_unit_types(u) { DMprintf(" %d", strategy->strengths[sn1][u]); } DMprintf(" (%d points)\n", strategy->points[sn1]); DMprintf("%s ", side_desig(side)); DMprintf("est init allied of %s: ", side_desig(side1)); for_all_unit_types(u) { DMprintf(" %d", strategy->alstrengths0[sn1][u]); } DMprintf(" (%d points)\n", strategy->alpoints0[sn1]); DMprintf("%s ", side_desig(side)); DMprintf("est curr allied of %s: ", side_desig(side1)); for_all_unit_types(u) { DMprintf(" %d", strategy->alstrengths[sn1][u]); } DMprintf(" (%d points)\n", strategy->alpoints[sn1]); } } } /* Sometimes there is no point in going on, but be careful not to be too pessimistic. Right now we only give up if no hope at all. Currently this is only used if there is a last-side-wins scorekeeper; it would need to be modified considerably to be useful with scorekeepers in general. */ static void decide_resignation(Side *side) { int sn, sn1, ratio, ratio0, chance = 0, chance1; Side *side1; Strategy *strategy = ai(side); /* Disable resignation during the first 10 turns. This fixes problem with mplayer resigning at the start of the game if it has one settler (1 point) and some other side already built a city (10 points). */ if (g_turn() < 10) return; sn = side->id; estimate_strengths(side); /* If our estimate of our own points is zero, then we're about to lose the game anyway, so just return and avoid screwing up ratio calcs below. */ if (strategy->alpoints[sn] <= 0) return; for_all_sides(side1) { if (side != side1 && side1->ingame && !allied_side(side, side1)) { sn1 = side1->id; /* Note that ratio calculations always scale up by 100, so that we can do finer comparisons without needing floating point. */ ratio = (strategy->alpoints[sn1] * 100) / strategy->alpoints[sn]; /* If the AI can see all enemy units everywhere, it resigns much too easily. (Iceland should not quit just because there are lots of Chineese troops in China) (should ultimately test for proximity to enemy)*/ if (side->see_all) { ratio /= 5; /* code specific to the "time" game */ /* the mplayer can severely underestimates its own strength */ } else if (game_class == gc_time) { ratio /= 3; } if (strategy->alpoints0[sn] > 0) { ratio0 = (strategy->alpoints0[sn1] * 100) / strategy->alpoints0[sn]; /* If we estimated 0 points for some side's initial strength, then our estimate is bad; assume parity. */ if (ratio0 <= 0) ratio0 = 100; /* This formula basically calls for no resignation if ratio is no more than twice what it was initially, 50% chance if ratio is four times what it was (if we started out even, then we're outnumbered 4 to 1), and interpolates for ratios in between. */ chance1 = (((ratio * 100) / ratio0) - 200) / 5; chance1 = max(chance1, 0); } else { /* work by absolute ratios */ if (ratio > 400) { chance1 = ratio / 10; } } /* The overall chance is determined by the most threatening side or alliance. */ chance = max(chance, chance1); } } /* Never go all the way to 100%; perhaps the lopsided ratio is just a temporary setback. */ chance = min(chance, 90); /* Whether or not we actually resign, we may be willing to go for a draw if other players want to. */ /* (Note that toggling this flag is not exactly poker-faced behavior, but I doubt human players will be able to derive much advantage, since they'll already have a pretty good idea if the AI is in trouble or not.) */ try_to_draw(side, (chance > 0), "mplayer"); /* Maybe resign. */ if (chance > 0) { if (probability(chance)) { give_up(side, "mplayer"); } } } /* Go through all our units (and allied ones?). */ static void mplayer_review_units(Side *side) { int u, u2, cp, cpmin, any; int numoffensive[MAXUTYPES], numdefensive[MAXUTYPES]; Unit *unit, *occ, *unit2; Plan *plan; Theater *oldtheater, *theater; /* This code is specific to the "time" game. */ if (game_class == gc_time) { for_all_unit_types(u) { u2 = ai(side)->develop_on[u]; if (ai(side)->develop_status[u] == RS_DEVELOP_ASSIGNED) { /* is anyone developing? */ unit2 = NULL; for_all_side_units(side, unit) { if (unit->type==u && in_play(unit) && unit->plan && unit->plan->aicontrol) { if (unit->plan->tasks && unit->plan->tasks->type == TASK_DEVELOP && unit->plan->tasks->args[0] == u2) unit2 = unit; } } if (unit2 != NULL) { DMprintf("%s is developing for %s on %s (level %d/%d)\n", unit_desig(unit2), side_desig(side), u_type_name(u2), side->tech[u2], u_tech_to_build(u2)); } else { DMprintf("no %s is developing for %s on %s!\n", u_type_name(u), side_desig(side), u_type_name(u2)); ai(side)->develop_status[u] = RS_DEVELOP_NEEDED; } } if (ai(side)->develop_status[u] == RS_DEVELOP_NEEDED && needs_develop (side, u2)) { /* pick for develop a unit not building; if all are building, choose the one which started last */ unit2 = NULL; cpmin = 9999; any = 0; for_all_side_units(side, unit) { if (unit->type == u && in_play(unit) && unit->plan && unit->plan->aicontrol) { any = 1; cp = 0; occ = NULL; if ((unit->plan->tasks != NULL && unit->plan->tasks->type == TASK_BUILD)) occ = find_unit_to_complete(unit, unit->plan->tasks); if (occ != NULL) { cp = occ->cp - uu_creation_cp(u,occ->type); if (uu_cp_per_build(u,u2) > 0) cp /= uu_cp_per_build(u,u2); } if (cp < cpmin) { unit2 = unit; cpmin = cp; } } } if (unit2 == NULL) { if (any) DMprintf("no %s is available to develop for %s on %s!\n", u_type_name(u), side_desig(side), u_type_name(u2)); } else { if (assign_to_develop_on(side, unit2, u2)) { ai(side)->develop_status[u] = RS_DEVELOP_ASSIGNED; } } } } } for_all_unit_types(u) { numoffensive[u] = numdefensive[u] = 0; } for_all_side_units(side, unit) { if (in_play(unit) && ai_controlled(unit)) { /* Count plan types. */ switch (unit->plan->type) { case PLAN_OFFENSIVE: case PLAN_EXPLORATORY: ++numoffensive[unit->type]; break; case PLAN_DEFENSIVE: ++numdefensive[unit->type]; break; default: break; } } } for_all_side_units(side, unit) { if (in_play(unit) && ai_controlled(unit)) { /* code specific to the "time" game */ if (game_class == gc_time) { u = unit->type; u2 = ai(side)->develop_on[u]; /* should we upgrade? */ if (ai(side)->develop_status[u] == RS_UPGRADE_NEEDED) { cp = 0; occ = NULL; if ((unit->plan->tasks != NULL && unit->plan->tasks->type == TASK_BUILD)) occ = find_unit_to_complete(unit, unit->plan->tasks); if (occ != NULL) { cp = occ->cp - uu_creation_cp(u,occ->type); if (uu_cp_per_build(u,u2)>0) cp /= uu_cp_per_build(u,u2); } if (occ != NULL && occ->type==u2) { /* already upgrading */ DMprintf("%s is upgrading to %s (%d/%d cp)\n", unit_desig(unit), u_type_name(u2), occ->cp, u_cp(occ->type)); } else if (cp >= u_cp(u2)/4) { /* rule-of-thumb... */ /* complete unit under construction */ DMprintf("%s will complete %s (now %d/%d cp) before upgrading to %s\n", unit_desig(unit), u_type_name(occ->type), occ->cp, u_cp(occ->type), u_type_name(u2)); } else { /* start upgrading */ if (occ != NULL && !fullsized(occ)) { DMprintf("%s will drop work on %s (%d/%d cp) and immediately start upgrading to %s\n", unit_desig(unit), u_type_name(occ->type), occ->cp, u_cp(occ->type), u_type_name(u2)); } else { DMprintf("%s will start upgrading to %s\n", unit_desig(unit), u_type_name(u2)); } net_set_unit_plan_type(side, unit, PLAN_IMPROVING); net_clear_task_agenda(side, unit); net_set_build_task(unit, u2, 1, 0, 0); } } } plan = unit->plan; oldtheater = unit_theater(unit); /* Goal might have become satisfied. */ if (plan->maingoal) { if (goal_truth(side, plan->maingoal) == 100) { DMprintf("%s %s satisfied, removing\n", unit_desig(unit), goal_desig(plan->maingoal)); net_force_replan(side, unit, FALSE); set_unit_theater(unit, NULL); } } /* Theater might have become explored enough (90% known). */ if (plan->type == PLAN_EXPLORATORY && (theater = unit_theater(unit)) != NULL && theater->unexplored < theater->size / 10) { DMprintf("%s theater %s is mostly known\n", unit_desig(unit), theater->name); net_force_replan(side, unit, FALSE); set_unit_theater(unit, NULL); } /* Don't let defense-only units pile up. */ if (plan->type == PLAN_DEFENSIVE && mobile(unit->type) && (numoffensive[unit->type] / 3) < numdefensive[unit->type] /* However, don't mess with units that have specific defensive goals. */ && (plan->maingoal == NULL || (plan->maingoal->type != GOAL_UNIT_OCCUPIED && plan->maingoal->type != GOAL_CELL_OCCUPIED)) && flip_coin()) { DMprintf("%s one of too many on defense (%d off, %d def), replanning\n", unit_desig(unit), numoffensive[unit->type], numdefensive[unit->type]); net_force_replan(side, unit, FALSE); } theater = unit_theater(unit); DMprintf("%s currently assigned to %s", unit_desig(unit), (theater ? theater->name : "no theater")); if (oldtheater != theater) { DMprintf(" (was %s)", (oldtheater ? oldtheater->name : "no theater")); } DMprintf("\n"); } } } /* Look at our current overall strategy and hack it as needed. */ static void update_side_strategy(Side *side) { Theater *theater; DMprintf("%s updating strategy\n", side_desig(side)); /* Add something to add/update theaters as things open up. (?) */ for_all_theaters(side, theater) { decide_theater_needs(side, theater); } } /* Figure out how many units to request for each area. */ static void decide_theater_needs(Side *side, Theater *theater) { if (theater->unexplored > 0) { /* Exploration is less important when 90% of a theater is known. */ if (theater->unexplored > (theater->size / 10)) { ++(ai(side)->explorersneeded); } /* Should look for good exploration units. */ theater->importance = 50; /* should depend on context */ /* theater->reinforce = EXPLORE_AREA; */ #if 0 } else if (0 /* few enemies? */) { if (theater->allied_makers == 0 && theater->makers > 0 && theater->nearby) { theater->reinforce = GUARD_BORDER_TOWN + 2 * theater->makers; } else if (theater->makers > 0) { theater->reinforce = (theater->border ? GUARD_BORDER_TOWN : GUARD_TOWN) + 2 * theater->allied_makers; } else if (theater->allied_bases > 0) { theater->reinforce = (theater->border ? GUARD_BORDER: GUARD_BASE); } else if (theater->border) { theater->reinforce = NO_UNITS; } else { theater->reinforce = NO_UNITS; } } else { if (theater->allied_makers > 0) { theater->reinforce = DEFEND_TOWN + 5 * theater->makers; } else if (theater->allied_bases > 0) { theater->reinforce = DEFEND_BASE + theater->allied_bases; } else { theater->reinforce = 0 /* DEFEND_AREA */; } #endif } } /* Push a new goal onto the side's list of goals. */ /* (this should only add goals that are not already present) */ static void add_goal(Side *side, Goal *goal) { if (ai(side)->numgoals < MAXGOALS) { ai(side)->goals[(ai(side)->numgoals)++] = goal; DMprintf("%s added %s\n", side_desig(side), goal_desig(goal)); } else { DMprintf("%s has no room for %s\n", side_desig(side), goal_desig(goal)); } } /* Return any goal of the given type. */ static Goal * has_goal(Side *side, GoalType goaltype) { int i; Goal *goal; for (i = 0; i < ai(side)->numgoals; ++i) { goal = ai(side)->goals[i]; if (goal != NULL && goal->type == goaltype) { return goal; } } return NULL; } /* Return an unfulfilled goal of the given type. */ static Goal * has_unsatisfied_goal(Side *side, GoalType goaltype) { int i; Goal *goal; for (i = 0; i < ai(side)->numgoals; ++i) { goal = ai(side)->goals[i]; if (goal != NULL && goal->type == goaltype && goal_truth(side, goal) < 100) { return goal; } } return NULL; } /* This is for when a unit needs a plan and asks its side for one. */ static void mplayer_decide_plan(Side *side, Unit *unit) { int u = unit->type; Plan *plan = unit->plan; Goal *goal; /* code specific to the "time" game */ /* don't mess up with units developing or upgrading */ if (game_class == gc_time) { if (ai(side)->develop_status[u] == RS_UPGRADE_NEEDED && plan->tasks != NULL && plan->tasks->type == TASK_BUILD) return; if (ai(side)->develop_status[u] == RS_DEVELOP_ASSIGNED && plan->tasks != NULL && plan->tasks->type == TASK_DEVELOP) return; } /* If we're not trying to win at this game, then make the unit act randomly. */ if (!ai(side)->trytowin) { net_set_unit_plan_type(side, unit, PLAN_RANDOM); net_clear_task_agenda(side, unit); return; } switch (plan->type) { case PLAN_PASSIVE: case PLAN_NONE: if (mobile(u)) { if (u_colonizer_worth(u) > 0) { assign_to_colonize(side, unit); return; } /* Maybe assign to exploration. */ if (has_goal(side, GOAL_WORLD_KNOWN)) { if (need_explorers(side) && u_explorer_worth(u) > 0 && flip_coin()) { /* also limit to a total percentage, in case exploration needs are very high */ assign_to_exploration(side, unit); return; } } /* Maybe assign to collecting materials. */ if ((goal = has_unsatisfied_goal(side, GOAL_HAS_MATERIAL_TYPE))) { if (need_this_type_to_collect(side, u, goal->args[0])) { assign_to_collection(side, unit, goal->args[0]); return; } } /* Assign most units to offense, save some for defense. */ if (u_offensive_worth(u) > 0 && probability(75)) { assign_to_offense(side, unit); } else if (u_defensive_worth(u) > 0) { assign_to_defense(side, unit); /* In the unlikely case that mobile units can build anything. */ } else if (can_build_attackers(side, u)) { assign_to_offense_support(side, unit); } else if (can_build_defenders(side, u)) { assign_to_defense_support(side, unit); } else { } } else { /* Unit doesn't move. */ if (can_build_colonizers(side, u) /* (should fine-tune this test) */ && probability(60)) { assign_to_colonization_support(side, unit); } else if (has_unsatisfied_goal(side, GOAL_VICINITY_HELD) && can_build_attackers(side, u)) { assign_to_offense_support(side, unit); } else if (has_goal(side, GOAL_WORLD_KNOWN) && need_explorers(side) && can_build_explorers(side, u)) { assign_to_explorer_construction(side, unit); } else if (can_build_attackers(side, u)) { assign_to_offense_support(side, unit); } else if (can_build_defenders(side, u)) { assign_to_defense_support(side, unit); } else if (u_defensive_worth(u) > 0) { assign_to_defense(side, unit); } } break; case PLAN_OFFENSIVE: /* leave plan alone */ break; case PLAN_COLONIZING: /* leave plan alone */ break; case PLAN_IMPROVING: /* leave plan alone */ break; case PLAN_EXPLORATORY: /* leave plan alone */ break; case PLAN_DEFENSIVE: /* leave plan alone */ break; default: break; } } static int mplayer_adjust_plan(Side *side, Unit *unit) { int u3, m; Task *task; /* Non-mobile units may be able to execute on a plan by construction; here we let the mplayer decide which type to construct. */ if ((unit->plan->type == PLAN_OFFENSIVE || unit->plan->type == PLAN_COLONIZING || unit->plan->type == PLAN_IMPROVING || unit->plan->type == PLAN_EXPLORATORY) && !mobile(unit->type) && unit->plan->aicontrol && !unit->plan->asleep && unit->plan->tasks == NULL ) { u3 = preferred_build_type(side, unit, unit->plan->type); if (is_unit_type(u3)) { task = unit->plan->tasks; if (task == NULL || task->type != TASK_BUILD) { DMprintf("%s directed to build %s\n", unit_desig(unit), u_type_name(u3)); net_set_build_task(unit, u3, 2, 0, 0); for_all_material_types(m) { int consump = um_consumption_on_creation(u3, m); if (consump > 0 && side_has_treasury(side, m) && um_takes_from_treasury(unit->type, m)) { Goal *goal; goal = create_goal(GOAL_HAS_MATERIAL_TYPE, side, TRUE); goal->args[0] = m; goal->args[1] = 3 * consump; add_goal(side, goal); } } } else { DMprintf("%s already building, leaving alone\n", unit_desig(unit)); } /* Only do one at a time, wait for next go-around for next unit. */ /* (why?) */ return FALSE; } } /* If a collecting unit achieves its goal, cancel its plan. */ if (unit->plan->type == PLAN_IMPROVING && mobile(unit->type) && unit->plan->aicontrol && !unit->plan->asleep && unit->plan->tasks == NULL ) { net_force_replan(side, unit, FALSE); } if (unit->plan->waitingfortasks && unit->plan->aicontrol ) { net_force_replan(side, unit, FALSE); } if (!unit->plan->reserve && g_units_may_go_into_reserve() && unit->plan->execs_this_turn > 10 * max(1, u_acp(unit->type))) { net_set_unit_reserve(side, unit, TRUE, FALSE); } /* Look at more units. */ return TRUE; } static int need_explorers(Side *side) { int s, numcontacted = 0, numfound = 0; for (s = 1; s <= numsides; ++s) { if (s == side->id) continue; if (ai(side)->contacted[s]) ++numcontacted; if (ai(side)->homefound[s]) ++numfound; } if (numcontacted == 0) { /* If we've haven't found anybody, always explore. */ return TRUE; } else if (numfound == 0) { /* If we've made contact but haven't found their home base, we still need to explore, but not so much. */ return probability(50); } else if (numfound < numsides - 1) { /* If we haven't found everybody's home base, we still need to have a few units continuing to explore. */ return probability(10); } else { /* If everybody has been found, then we likely have more pressing concerns; don't do more exploration. */ return FALSE; } } /* This is a hook that runs after each task is executed. */ static void mplayer_react_to_task_result(Side *side, Unit *unit, Task *task, TaskOutcome rslt) { int dx, dy, x1, y1, fact; Unit *occ; Theater *theater; /* React to an apparent blockage. */ if (rslt == TASK_FAILED && task != NULL && task->type == TASK_MOVE_TO && task->retrynum > 2) { if (desired_direction_impassable(unit, task->args[0], task->args[1])) { if (could_be_ferried(unit, task->args[0], task->args[1])) { if (unit->plan->type == PLAN_EXPLORATORY && flip_coin()) { DMprintf("%s blocked while exploring, ", unit_desig(unit)); if (flip_coin()) { DMprintf("changing theaters\n"); change_to_adjacent_theater(side, unit); } else { DMprintf("changing goal within theater\n"); /* Clear the existing goal and create a new one. */ assign_explorer_to_theater(side, unit, unit_theater(unit)); } return; } else if (flip_coin()) { DMprintf("%s blocked, will wait for transport\n", unit_desig(unit)); theater = theater_at(side, unit->x, unit->y); if (theater != NULL) { ++(theater->numtotransport[unit->type]); } net_set_unit_reserve(side, unit, TRUE, FALSE); net_set_unit_waiting_for_transport(side, unit, TRUE); return; } } else { if (unit->occupant) { DMprintf("%s blocked while transporting, will sit briefly\n", unit_desig(unit)); net_set_unit_reserve(side, unit, TRUE, FALSE); for_all_occupants(unit, occ) { net_wake_unit(side, occ, FALSE); } return; } /* Another option is to transfer to another theater. This is especially useful when exploring. */ if (unit->plan->type == PLAN_EXPLORATORY && flip_coin()) { DMprintf("%s blocked while exploring, changing theaters\n", unit_desig(unit)); change_to_adjacent_theater(side, unit); return; } } /* Try moving sideways. */ if (probability(80)) { dx = task->args[0] - unit->x; dy = task->args[1] - unit->y; fact = (flip_coin() ? 50 : -50); x1 = unit->x - ((fact * dy) / 100); y1 = unit->y + ((fact * dx) / 100); if (inside_area(x1, y1)) net_push_move_to_task(unit, x1, y1, 1); } return; } else if (blocked_by_enemy(unit, task->args[0], task->args[1], TRUE)) { /* (should decide if allowable risk to passengers) */ DMprintf("%s blocked by enemy\n", unit_desig(unit)); if (!attack_blockage(side, unit, task->args[0], task->args[1], TRUE)) { if (blocked_by_enemy(unit, task->args[0], task->args[1], FALSE)) { attack_blockage(side, unit, task->args[0], task->args[1], FALSE); } else { /* (should move sideways?) */ } } } else { /* what to do about other failures? */ } return; } /* React to inability to resupply by trying to build a base. */ if (rslt == TASK_FAILED && task != NULL && task->type == TASK_RESUPPLY && task->retrynum > 2) { net_set_unit_reserve(side, unit, FALSE, FALSE); build_depot_for_self(side, unit); } } /* Reassign the given unit to another theater, usually because it is unable to fulfill its goal in its current theater. */ static void change_to_adjacent_theater(Side *side, Unit *unit) { int dir; Theater *theater, *newtheater = NULL; theater = unit_theater(unit); if (theater != NULL) { for_all_directions(dir) { /* If we have a radial pattern of theaters, randomly choose between inward/outward or right/left. */ if (theater == ai(side)->perimeters[dir]) { if (probability(20) && ai(side)->midranges[dir] != NULL) newtheater = ai(side)->midranges[dir]; else newtheater = ai(side)->perimeters[flip_coin() ? left_dir(dir) : right_dir(dir)]; break; } if (theater == ai(side)->midranges[dir]) { if (probability(20) && ai(side)->perimeters[dir] != NULL) newtheater = ai(side)->perimeters[dir]; else if (probability(20) && ai(side)->remotes[dir] != NULL) newtheater = ai(side)->remotes[dir]; else newtheater = ai(side)->midranges[flip_coin() ? left_dir(dir) : right_dir(dir)]; break; } if (theater == ai(side)->remotes[dir]) { if (probability(20) && ai(side)->midranges[dir] != NULL) newtheater = ai(side)->midranges[dir]; else newtheater = ai(side)->remotes[flip_coin() ? left_dir(dir) : right_dir(dir)]; break; } } /* (should add grid case also?) */ if (newtheater != NULL) { assign_explorer_to_theater(side, unit, newtheater); } } } /* This function is called whenever a new side appears in the game. It mainly needs to make that any allocated data is resized appropriately. */ static void mplayer_react_to_new_side(Side *side, Side *side2) { /* (Assumes we call this right after adding each new side) */ int oldnumsides = numsides - 1; int *newpeople, s; Theater *theater; for_all_theaters(side, theater) { /* Grow any people count arrays if present. */ if (theater->people != NULL) { newpeople = (int *) xmalloc ((numsides + 1) * sizeof(int)); for (s = 0; s <= oldnumsides; ++s) newpeople[s] = theater->people[s]; free(theater->people); theater->people = newpeople; } } anewside = side2; estimate_strengths(side); anewside = NULL; } /* At the end of a turn, re-evaluate the plans of some units in case the situation changed. */ static void mplayer_finish_movement(Side *side) { int u, scan; Unit *unit; Theater *theater; scan = FALSE; for_all_theaters(side, theater) { for_all_unit_types(u) { if (theater->numtotransport[u] > 0) { scan = TRUE; break; } } if (scan) break; } if (scan) { /* Find a unit needing transport. */ for_all_side_units(side, unit) { if (is_active(unit) && ai_controlled(unit) && unit->plan && unit->plan->waitingfortransport) { search_for_available_transport(unit, 0); /* Whether or not the search succeeded, stop waiting to see if we get transportation. */ net_set_unit_waiting_for_transport(side, unit, FALSE); } } } for_all_side_units(side, unit) { if (is_active(unit) && ai_controlled(unit)) { mplayer_rethink_plan(unit); } } } /* Given a unit and a reason to be needing transport, look around for something suitable. We need a transport that has available space and is not doing something else more important. */ /* (should also choose transports that can reach the unit's destination or at least close by) */ static Unit * search_for_available_transport(Unit *unit, int purpose) { int dist, closestdist = area.maxdim; Unit *transport, *closesttransport = NULL; Theater *theater = unit_theater(unit); /* (more efficient to search adjacent cells first?) */ for_all_side_units(unit->side, transport) { if (is_active(transport) && mobile(transport->type) && could_carry(transport->type, unit->type) && can_carry(transport, unit) && transport->act != NULL /* not quite correct, but to fix bug */ && (purpose == 1 ? accelerator(transport->type, unit->type) : TRUE) ) { /* Don't grab at units being moved manually. */ if (!ai_controlled(transport)) continue; /* Maybe this one is already coming to get somebody. */ if (transport->plan && transport->plan->tasks != NULL && transport->plan->tasks->type == TASK_PICKUP) { if (transport->plan->tasks->args[0] == unit->id) return transport; /* Picking up somebody else - don't hijack. */ continue; } if (transport->plan && transport->plan->tasks != NULL && transport->plan->tasks->type == TASK_MOVE_TO && transport->plan->tasks->next != NULL && transport->plan->tasks->next->type == TASK_PICKUP) { if (transport->plan->tasks->next->args[0] == unit->id) return transport; /* Picking up somebody else - don't hijack. */ continue; } dist = distance(unit->x, unit->y, transport->x, transport->y); if (dist < closestdist || (dist == closestdist && flip_coin())) { closesttransport = transport; closestdist = dist; } /* If transport already adjacent, no need to keep looking. */ if (closestdist <= 1) break; } } if (closesttransport != NULL && closesttransport->plan != NULL) { net_clear_task_agenda(unit->side, unit); /* (could inherit unit's goal, but not needed) */ if (unit->plan) net_set_unit_plan_type(unit->side, closesttransport, unit->plan->type); net_set_unit_main_goal(unit->side, closesttransport, NULL); net_push_pickup_task(closesttransport, unit); net_push_move_to_task(closesttransport, unit->x, unit->y, 1); net_push_occupy_task(unit, closesttransport); /* No longer count this unit as needing transport. */ if (theater != NULL) { --(theater->numtotransport[unit->type]); set_unit_theater(closesttransport, theater); } DMprintf("%s will be picked up by closest transport %s\n", unit_desig(unit), unit_desig(closesttransport)); return closesttransport; } return NULL; } /* For units with plans and that are under AI control, consider changing the current plan/tasks. */ static void mplayer_rethink_plan(Unit *unit) { int dist, x1, y1; Task *toptask = unit->plan->tasks, *nexttask = NULL; Plan *plan = unit->plan; Unit *transport; if (toptask) nexttask = toptask->next; /* If we have a long ways to go, see if there is a transport available that can get us there faster. */ if (toptask != NULL && (toptask->type == TASK_HIT_UNIT || (toptask->type == TASK_MOVE_TO && nexttask != NULL && nexttask->type == TASK_HIT_UNIT)) && !plan->reserve && !plan->asleep && !plan->waitingfortransport && (unit->transport == NULL || !mobile(unit->transport->type)) && ((dist = distance(unit->x, unit->y, toptask->args[0], toptask->args[1])) >= 4 * u_acp(unit->type)) && accelerable(unit->type) ) { DMprintf("%s looking for transport to accelerate with;\n", unit_desig(unit)); transport = search_for_available_transport(unit, 1); if (transport != NULL) { net_push_sentry_task(unit, max(1, dist / max(1, u_acp(transport->type)))); if (g_units_may_go_into_reserve()) net_set_unit_reserve(unit->side, unit, TRUE, FALSE); net_set_unit_waiting_for_transport(unit->side, unit, FALSE); } else { DMprintf(" found nothing\n"); } } if (unit->plan->type == PLAN_OFFENSIVE && toptask != NULL && toptask->type == TASK_MOVE_TO && distance(unit->x, unit->y, toptask->args[0], toptask->args[1]) >= min(2, u_acp(unit->type)) && enemy_close_by(unit->side, unit, 1 /* 2 would be better? */, &x1, &y1) ) { net_push_hit_unit_task(unit, x1, y1, NONUTYPE, -1); DMprintf("%s sees enemy close by, will attack it\n", unit_desig(unit)); } /* (should also notice fire opportunities) */ /* If we see somebody that could be captured and help us explore, set up to produce capturers. */ if (!mobile(unit->type) && (unit->plan->type == PLAN_EXPLORATORY || unit->plan->type == PLAN_OFFENSIVE) ) { int range = 4, rslt, x, y; DMprintf("%s searching for useful capture within %d in order to choose build; found ", unit_desig(unit), range); tmpunit = unit; rslt = search_around(unit->x, unit->y, range, useful_captureable_here, &x, &y, 1); if (rslt && is_unit_type(tmputype)) { DMprintf("%s at %d,%d", u_type_name(tmputype), x, y); if (toptask != NULL && toptask->type == TASK_BUILD && uu_capture(toptask->args[0], tmputype) ) { /* Already doing the right thing. */ DMprintf(" - already building %s", u_type_name(toptask->args[0])); } else { /* (should find best type that can capture quickly, schedule to build it) */ DMprintf(" - duhhh, what now?"); } } else { DMprintf("nothing"); } DMprintf("\n"); } } #if 0 static void mplayer_react_to_unit_loss(side, unit) Side *side; Unit *unit; { int x = unit->x, y = unit->y; Theater *th; if (!inside_area(x, y)) { x = unit->prevx; y = unit->prevy; } if (!inside_area(x, y)) return; /* Count the unit as having been lost in a particular theater. */ if (ai(side) && (th = theater_at(side, x, y)) != NULL) { ++(th->units_lost); } } #endif /* This is used by interfaces to display the name of the theater in use at a given point. */ static char * mplayer_at_desig(Side *side, int x, int y) { Theater *theater; if (ai(side) == NULL) return ""; theater = theater_at(side, x, y); return (theater ? theater->name : ""); } /* This is used by interfaces to display boundaries between theaters, by comparing the numbers returned. */ static int mplayer_theater_at(Side *side, int x, int y) { Theater *theater; if (ai(side) == NULL) return 0; theater = theater_at(side, x, y); return (theater ? theater->id : 0); } /* Collect initial strength information stored in the mplayer's private saved data. */ static int mplayer_read_strengths(Side *side) { int sn1, u, found = FALSE; char *propname; Obj *props, *bdg, *rest, *sidebdg, *urest; Side *side1; Strategy *strategy = ai(side); props = find_at_key(side->aidata, "mplayer"); for (; props != lispnil; props = cdr(props)) { bdg = car(props); propname = c_string(car(bdg)); if (strcmp(propname, "strengths0") == 0) { found = TRUE; rest = cdr(bdg); for_all_sides(side1) { sn1 = side1->id; sidebdg = car(rest); urest = cadr(sidebdg); for_all_unit_types(u) { strategy->strengths0[sn1][u] = c_number(car(urest)); urest = cdr(urest); } rest = cdr(rest); } } else if (strcmp(propname, "alstrengths0") == 0) { found = TRUE; rest = cdr(bdg); for_all_sides(side1) { sn1 = side1->id; sidebdg = car(rest); urest = cadr(sidebdg); for_all_unit_types(u) { strategy->alstrengths0[sn1][u] = c_number(car(urest)); urest = cdr(urest); } rest = cdr(rest); } } else if (strcmp(propname, "points0") == 0) { found = TRUE; rest = cdr(bdg); for_all_sides(side1) { sn1 = side1->id; strategy->points0[sn1] = c_number(car(rest)); rest = cdr(rest); } } else if (strcmp(propname, "alpoints0") == 0) { found = TRUE; rest = cdr(bdg); for_all_sides(side1) { sn1 = side1->id; strategy->alpoints0[sn1] = c_number(car(rest)); rest = cdr(rest); } } else { } } return found; } /* Write out any state that the mplayer must preserve. We don't actually write; instead we build a Lisp object and pass that back to the writing routines. */ static Obj * mplayer_save_state(Side *side) { int sn1, u; Obj *rslt, *vallist, *uvallist; Side *side1; Strategy *strategy = ai(side); rslt = lispnil; /* Just return last result if it's already been computed. */ if (strategy->writable_state != lispnil || xmalloc_warnings) return strategy->writable_state; /* We're pushing bindings onto a list, so do in reverse of desired order. */ vallist = lispnil; for_all_sides(side1) { sn1 = side1->id; uvallist = lispnil; for_all_unit_types(u) { uvallist = cons(new_number(strategy->alstrengths0[sn1][u]), uvallist); } uvallist = reverse(uvallist); push_binding(&vallist, new_number(sn1), uvallist); } vallist = reverse(vallist); push_cdr_binding(&rslt, intern_symbol("alstrengths0"), vallist); vallist = lispnil; for_all_sides(side1) { sn1 = side1->id; uvallist = lispnil; for_all_unit_types(u) { uvallist = cons(new_number(strategy->strengths0[sn1][u]), uvallist); } uvallist = reverse(uvallist); push_binding(&vallist, new_number(sn1), uvallist); } vallist = reverse(vallist); push_cdr_binding(&rslt, intern_symbol("strengths0"), vallist); vallist = lispnil; for_all_sides(side1) { sn1 = side1->id; vallist = cons(new_number(strategy->alpoints0[sn1]), vallist); } vallist = reverse(vallist); push_cdr_binding(&rslt, intern_symbol("alpoints0"), vallist); vallist = lispnil; for_all_sides(side1) { sn1 = side1->id; vallist = cons(new_number(strategy->points0[sn1]), vallist); } vallist = reverse(vallist); push_cdr_binding(&rslt, intern_symbol("points0"), vallist); strategy->writable_state = rslt; return rslt; }