[freeciv-ai] Re: (PR#4026) Advanced rampage.
[Top] [All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index] [Thread Index]
Here is an up-to-date version of rampage.
I am running some auto-games now, see if I still get crashes ;)
G.
Index: ai/aiunit.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aiunit.c,v
retrieving revision 1.275
diff -u -r1.275 aiunit.c
--- ai/aiunit.c 2003/05/06 06:02:49 1.275
+++ ai/aiunit.c 2003/05/06 22:16:34
@@ -44,6 +44,9 @@
#include "unithand.h"
#include "unittools.h"
+#include "path_finding.h"
+#include "pf_tools.h"
+
#include "advmilitary.h"
#include "aiair.h"
#include "aicity.h"
@@ -62,10 +65,14 @@
static void ai_manage_barbarian_leader(struct player *pplayer,
struct unit *leader);
+#define RAMPAGE_BETTER_THAN_PILLAGE 2
+#define RAMPAGE_HUT_OR_BETTER 99998
+#define RAMPAGE_ONLY_FREE_CITY 99999
+static bool ai_military_rampage(struct unit *punit, int thresh_adj,
+ int thresh_move);
static void ai_military_findjob(struct player *pplayer,struct unit *punit);
static void ai_military_gohome(struct player *pplayer,struct unit *punit);
static void ai_military_attack(struct player *pplayer,struct unit *punit);
-static bool ai_military_rampage(struct unit *punit, int threshold);
static int unit_move_turns(struct unit *punit, int x, int y);
static bool unit_role_defender(Unit_Type_id type);
@@ -1012,182 +1019,223 @@
}
/*************************************************************************
-This function looks at tiles adjacent to the unit in order to find something
-to kill or explore. It prefers tiles in the following order:
-
-Returns value of the victim which has been chosen:
-
-99999 means empty (undefended) city
-99998 means hut
-2..99997 means value of enemy unit weaker than our unit
-1 means barbarians wanting to pillage
-0 means nothing found or error
--2*MORT*TRADE_WEIGHTING
- means nothing found and punit causing unhappiness
+ This function appraises the location (x, y) for a quick hit-n-run operation.
-If value <= 0 is returned, (dest_x, dest_y) is set to actual punit's position.
+ Returns value as follows:
+ -RAMPAGE_ONLY_FREE_CITY
+ means undefended enemy city
+ -RAMPAGE_HUT_OR_BETTER
+ means hut
+ RAMPAGE_BETTER_THAN_PILLAGE ... RAMPAGE_HUT_OR_BETTER - 1
+ is value of enemy unit weaker than our unit
+ -(RAMPAGE_BETTER_THAN_PILLAGE - 1)
+ means barbarians wanting to pillage
+ 0 means nothing found or error
+ Here the minus indicates that you need to enter the target tile (as
+ opposed to attacking it, which leaves you where you are).
**************************************************************************/
-static int ai_military_findvictim(struct unit *punit, int *dest_x, int *dest_y)
+static int ai_rampage_want(struct unit *punit, int x, int y)
{
struct player *pplayer = unit_owner(punit);
- int attack_power = unit_att_rating_now(punit);
- int x = punit->x, y = punit->y;
- int best = 0;
- int stack_size = unit_list_size(&(map_get_tile(punit->x, punit->y)->units));
+ struct unit *pdef = get_defender(punit, x, y);
CHECK_UNIT(punit);
+
+ if (pdef) {
+
+ if (!can_unit_attack_tile(punit, x, y)) {
+ return 0;
+ }
+
+ {
+ /* See description of kill_desire() about these variables. */
+ int att_rating = unit_att_rating_now(punit);
+ int vuln = unit_vulnerability(punit, pdef);
+ int attack = reinforcements_value(punit, pdef->x, pdef->y) + att_rating;
+ int benefit = stack_cost(pdef);
+ int loss = unit_type(punit)->build_cost;
+
+ attack *= attack;
+
+ /* If the victim is in the city, we increase the benefit and correct
+ * it with our health because there may be more units in the city
+ * stacked, and we won't destroy them all at once, so in the next
+ * turn they may attack us. So we shouldn't send already injured
+ * units to useless suicide. */
+ if (map_get_city(x, y)) {
+ /* A WAG for the city value */
+ benefit += unit_value(get_role_unit(F_CITIES, 0));
+ benefit = (benefit * punit->hp) / unit_type(punit)->hp;
+ }
+
+ /* If we have non-zero attack rating... */
+ if (att_rating > 0 && is_my_turn(punit, pdef)) {
+ /* No need to amortize! We're doing it in one-turn horizon. */
+ return kill_desire(benefit, attack, loss, vuln, 1);
+ }
+ }
+
+ } else {
+ struct city *pcity = map_get_city(x, y);
+
+ /* No defender... */
+
+ /* ...and free foreign city waiting for us. Who would resist! */
+ if (pcity && pplayers_at_war(pplayer, city_owner(pcity))
+ && COULD_OCCUPY(punit)) {
+
+ return -RAMPAGE_ONLY_FREE_CITY;
+ }
+
+ /* ...or tiny pleasant hut here! */
+ if (map_has_special(x, y, S_HUT) && !is_barbarian(pplayer)) {
+
+ return -RAMPAGE_HUT_OR_BETTER;
+ }
+
+ /* If we have nothing to do, we can at least pillage something, hmm? */
+ if (is_land_barbarian(pplayer)
+ && get_tile_infrastructure_set(map_get_tile(x, y)) != S_NO_SPECIAL) {
+ return -(RAMPAGE_BETTER_THAN_PILLAGE - 1);
+ }
+ }
+
+ return 0;
+}
- *dest_y = y;
- *dest_x = x;
+/*************************************************************************
+ Look for worthy targets within a one-turn horizon.
+*************************************************************************/
+static bool find_rampage_target(struct unit *punit,
+ int thresh_adj, int thresh_move,
+ struct pf_path **path)
+{
+ struct pf_map *tgt_map;
+ struct pf_parameter parameter;
+ /* Coordinates of the best target (initialize to silence compiler) */
+ int x = punit->x, y = punit->y;
+ /* Want of the best target */
+ int max_want = 0;
+ struct player *pplayer = unit_owner(punit);
- /* Ferryboats with passengers do not attack. */
- if (punit->ai.passenger > 0) {
- return 0;
- }
+ pft_fill_default_parameter(¶meter);
+ pft_fill_unit_attack_param(¶meter, punit);
+
+ tgt_map = pf_create_map(¶meter);
+ while (pf_next(tgt_map)) {
+ struct pf_position pos;
+ int want;
+ bool move_needed;
+ int thresh;
- if (punit->unhappiness > 0) {
- /* When we're causing unhappiness, we'll set best even lower,
- * so that we will take even targets which we would ignore otherwise
- * (in other words -- we're going to commit suicide). */
- best = - 2 * MORT * TRADE_WEIGHTING;
+ pf_next_get_position(tgt_map, &pos);
+
+ if (pos.total_MC > punit->moves_left) {
+ /* This is too far */
+ break;
+ }
+
+ if (ai_handicap(pplayer, H_TARGETS)
+ && !map_get_known_and_seen(pos.x, pos.y, pplayer)) {
+ /* The target is under fog of war */
+ continue;
+ }
+
+ want = ai_rampage_want(punit, pos.x, pos.y);
+
+ /* Negative want means move needed even though the tiles are adjacent */
+ move_needed = (!is_tiles_adjacent(punit->x, punit->y, pos.x, pos.y)
+ || want < 0);
+ /* Select the relevant threshold */
+ thresh = (move_needed ? thresh_move : thresh_adj);
+ want = (want < 0 ? -want : want);
+
+ if (want > max_want && want > thresh) {
+ /* The new want exceeds both the previous maximum
+ * and the relevant threshold, so it's worth recording */
+ max_want = want;
+ x = pos.x;
+ y = pos.y;
+ }
}
- adjc_iterate(x, y, x1, y1) {
- /* Macro to set the tile with our target as the best (with value new_best)
*/
-#define SET_BEST(new_best) \
- do { best = (new_best); *dest_x = x1; *dest_y = y1; } while (FALSE)
+ if (max_want > 0) {
+ /* We found something */
+ *path = pf_get_path(tgt_map, x, y);
+ assert(*path);
+ }
- struct unit *pdef = get_defender(punit, x1, y1);
-
- if (pdef) {
- struct unit *patt = get_attacker(punit, x1, y1);
+ pf_destroy_map(tgt_map);
+
+ return (max_want > 0);
+}
- if (!can_unit_attack_tile(punit, x1, y1)) {
- continue;
- }
+/*************************************************************************
+ This is a function to execute paths returned by the path-finding engine.
+ It is analogous to goto_route_execute, only much simpler.
- /* If we are in the city, let's deeply consider defending it - however,
- * horsemen in city refused to attack phalanx just outside that was
- * bodyguarding catapult; thus, we get the best attacker on the tile (x1,
- * y1) as well, for the case when there are multiple different units on
- * the tile (x1, y1). Thus we force punit to attack a stack of units if
- * they're endangering punit seriously, even if they aren't that weak. */
- /* FIXME: The get_total_defense_power(pdef, punit) should probably use
- * patt rather than pdef. There also ought to be a better metric for
- * determining this. */
- if (patt
- && map_get_city(x, y)
- && get_total_defense_power(pdef, punit) *
- get_total_defense_power(punit, pdef) >=
- get_total_attack_power(patt, punit) *
- get_total_attack_power(punit, pdef)
- && stack_size < 2
- && get_total_attack_power(patt, punit) > 0) {
- freelog(LOG_DEBUG, "%s's %s defending %s from %s's %s at (%d, %d)",
- pplayer->name, unit_type(punit)->name, map_get_city(x,
y)->name,
- unit_owner(pdef)->name, unit_type(pdef)->name, punit->x,
punit->y);
- } else {
- /* See description of kill_desire() about this variables. */
- int vuln = unit_vulnerability(punit, pdef);
- int attack = reinforcements_value(punit, pdef->x, pdef->y)
- + attack_power;
- int benefit = unit_type(pdef)->build_cost;
- int loss = unit_type(punit)->build_cost;
-
- attack *= attack;
-
- /* If the victim is in the city, we increase the benefit and correct
- * it with our health because there may be more units in the city
- * stacked, and we won't destroy them all at once, so in the next
- * turn they may attack us. So we shouldn't send already injured
- * units to useless suicide. */
- if (map_get_city(x1, y1)) {
- benefit += unit_value(get_role_unit(F_CITIES, 0));
- benefit = (benefit * punit->hp) / unit_type(punit)->hp;
- }
-
- /* If we have non-zero attack rating... */
- if (attack > 0 && is_my_turn(punit, pdef)) {
- int desire;
-
- /* FIXME? Why we don't use stack_size as victim_count? --pasky */
-
- desire = kill_desire(benefit, attack, loss, vuln, 1);
-
- /* No need to amortize! We're doing it in one-turn horizon. */
-
- if (desire > best && ai_fuzzy(pplayer, TRUE)) {
- freelog(LOG_DEBUG, "Better than %d is %d (%s)",
- best, desire, unit_type(pdef)->name);
- SET_BEST(desire);
- } else {
- freelog(LOG_DEBUG, "NOT better than %d is %d (%s)",
- best, desire, unit_type(pdef)->name);
- }
- }
- }
-
- } else {
- struct city *pcity = map_get_city(x1, y1);
-
- /* No defender... */
-
- /* ...and free foreign city waiting for us. Who would resist! */
- if (pcity && pplayers_at_war(pplayer, city_owner(pcity))
- && could_unit_move_to_tile(punit, x1, y1) != 0
- && COULD_OCCUPY(punit)
- && !is_ocean(map_get_terrain(punit->x, punit->y))) {
- SET_BEST(99999);
- continue;
- }
+ Weuse ai_unit_attack which means "move if target unoccupied, attack
+ otherwise" and also brings our bodyguard along.
+*************************************************************************/
+static bool ai_unit_execute_path(struct unit *punit, struct pf_path *path)
+{
+ int i;
+
+ pf_print_path(LOG_NORMAL, path);
- /* ...or tiny pleasant hut here! */
- if (map_has_special(x1, y1, S_HUT) && best < 99999
- && could_unit_move_to_tile(punit, x1, y1) != 0
- && !is_barbarian(unit_owner(punit))
- && punit->ai.ai_role != AIUNIT_ESCORT
- && punit->ai.charge == BODYGUARD_NONE /* Above line doesn't seem to
work. :( */
- && punit->ai.ai_role != AIUNIT_DEFEND_HOME) {
- SET_BEST(99998);
- continue;
- }
-
- /* If we have nothing to do, we can at least pillage something, hmm? */
- if (is_land_barbarian(pplayer) && best == 0
- && get_tile_infrastructure_set(map_get_tile(x1, y1)) != S_NO_SPECIAL
- && could_unit_move_to_tile(punit, x1, y1) != 0) {
- SET_BEST(1);
- continue;
- }
+ /* We start with i = 1 for i = 0 is our present position */
+ for (i = 1; i < path->length; i++) {
+ int x = path->positions[i].x, y = path->positions[i].y;
+
+ if (!ai_unit_attack(punit, x, y)) {
+ /* Died... */
+ return FALSE;
}
-#undef SET_BEST
- } adjc_iterate_end;
+
+ if (!same_pos(punit->x, punit->y, x, y)) {
+ /* Stopped (or maybe fought) */
+ return TRUE;
+ }
+ }
- return best;
+ return TRUE;
}
/*************************************************************************
- Find and kill anything adjacent to us that we don't like with a
- given threshold until we have run out of juicy targets or movement.
- Wraps ai_military_findvictim().
+ Find and kill anything reachable within this turn and worth more than
+ the relevant of the given thresholds until we have run out of juicy
+ targets or movement. The first threshold is for attacking which will
+ leave us where we stand (attacking adjacent units), the second is for
+ attacking distant (but within reach) targets.
+
+ For example, if unit is a bodyguard on duty, it should call
+ ai_military_rampage(punit, 100, RAMPAGE_ONLY_FREE_CITY)
+ meaning "we will move _only_ to pick up a free city but we are happy to
+ attack adjacent squares as long as they are worthy of it".
+
Returns TRUE if survived the rampage session.
**************************************************************************/
-static bool ai_military_rampage(struct unit *punit, int threshold)
+static bool ai_military_rampage(struct unit *punit, int thresh_adj,
+ int thresh_move)
{
- int x, y;
int count = punit->moves_left + 1; /* break any infinite loops */
-
+ struct pf_path *path = NULL;
+
CHECK_UNIT(punit);
- while (punit->moves_left > 0 && count-- > 0
- && ai_military_findvictim(punit, &x, &y) >= threshold) {
- if (!ai_unit_attack(punit, x, y)) {
+ while (count-- > 0 && punit->moves_left > 0
+ && find_rampage_target(punit, thresh_adj, thresh_move, &path)) {
+ if (!ai_unit_execute_path(punit, path)) {
/* Died */
- return FALSE;
+ count = -1;
}
+ pf_destroy_path(path);
+ path = NULL;
}
- assert(count >= 0);
- return TRUE;
+
+ assert(!path);
+
+ return (count >= 0);
}
/*************************************************************************
@@ -1232,8 +1280,11 @@
ai_unit_new_role(punit, AIUNIT_NONE, -1, -1);
}
} else {
- /* I had these guys set to just fortify, which is so dumb. -- Syela */
- (void) ai_military_rampage(punit, 40 * SHIELD_WEIGHTING);
+ /* I had these guys set to just fortify, which is so dumb. -- Syela
+ * Instead we can attack adjacent units and maybe even pick up some free
+ * cities! */
+ (void) ai_military_rampage(punit, 40 * SHIELD_WEIGHTING,
+ RAMPAGE_ONLY_FREE_CITY);
}
}
@@ -1753,7 +1804,9 @@
freelog(LOG_DEBUG, "INHOUSE. GOTO AI_NONE(%d)", punit->id);
ai_unit_new_role(punit, AIUNIT_NONE, -1, -1);
/* aggro defense goes here -- Syela */
- (void) ai_military_rampage(punit, 2); /* 2 is better than pillage */
+ /* Attack just about anything */
+ (void) ai_military_rampage(punit, RAMPAGE_BETTER_THAN_PILLAGE,
+ RAMPAGE_BETTER_THAN_PILLAGE);
} else {
UNIT_LOG(LOG_DEBUG, punit, "GOHOME");
(void) ai_unit_goto(punit, pcity->x, pcity->y);
@@ -2319,7 +2372,7 @@
/*************************************************************************
This does the attack until we have used up all our movement, unless we
- should safeguard a city. First we rampage on adjacent tiles, then we go
+ should safeguard a city. First we rampage nearby, then we go
looking for trouble elsewhere. If there is nothing to kill, sailing units
go home, others explore while barbs go berserk.
**************************************************************************/
@@ -2334,8 +2387,9 @@
/* Main attack loop */
do {
- /* First find easy adjacent enemies; 2 is better than pillage */
- if (!ai_military_rampage(punit, 2)) {
+ /* First find easy nearby enemies, anything better than pillage goes */
+ if (!ai_military_rampage(punit, RAMPAGE_BETTER_THAN_PILLAGE,
+ RAMPAGE_BETTER_THAN_PILLAGE)) {
return; /* we died */
}
@@ -2673,8 +2727,9 @@
if (pcity) {
/* rest in city until the hitpoints are recovered, but attempt
- to protect city from attack */
- if (ai_military_rampage(punit, 2)) {
+ to protect city from attack (and be opportunistic too)*/
+ if (ai_military_rampage(punit, RAMPAGE_BETTER_THAN_PILLAGE,
+ RAMPAGE_ONLY_FREE_CITY)) {
freelog(LOG_DEBUG, "%s's %s(%d) at (%d, %d) recovering hit points.",
pplayer->name, unit_type(punit)->name, punit->id, punit->x,
punit->y);
@@ -2683,8 +2738,9 @@
}
} else {
/* goto to nearest city to recover hit points */
- /* just before, check to see if we can occupy at enemy city undefended */
- if (!ai_military_rampage(punit, 99999)) {
+ /* just before, check to see if we can occupy an undefended enemy city */
+ if (!ai_military_rampage(punit, RAMPAGE_ONLY_FREE_CITY,
+ RAMPAGE_ONLY_FREE_CITY)) {
return; /* oops, we died */
}
Index: common/aicore/pf_tools.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/common/aicore/pf_tools.c,v
retrieving revision 1.1
diff -u -r1.1 pf_tools.c
--- common/aicore/pf_tools.c 2003/03/11 17:59:26 1.1
+++ common/aicore/pf_tools.c 2003/05/06 22:16:34
@@ -55,9 +55,30 @@
return PF_IMPOSSIBLE_MC;
}
+/**********************************************************************
+ Sea attack is the same as overlap (consider bombardment) but we don't
+ want to pass through enemy tiles.
+**********************************************************************/
+static int sea_attack_move(int x, int y, enum direction8 dir,
+ int x1, int y1, struct pf_parameter *param)
+{
+ struct tile *src_tile = map_get_tile(x, y);
+
+ if (src_tile->terrain == T_OCEAN) {
+ if (is_non_allied_unit_tile(src_tile, param->owner)) {
+ return PF_IMPOSSIBLE_MC;
+ }
+ return SINGLE_MOVE;
+ } else if (is_allied_city_tile(map_get_tile(x, y), param->owner)
+ && map_get_terrain(x1, y1) == T_OCEAN) {
+ return SINGLE_MOVE;
+ }
+
+ return PF_IMPOSSIBLE_MC;
+}
+
/************************************************************
LAND_MOVE cost function for a unit
- Must put owner into *data.
************************************************************/
static int normal_move_unit(int x, int y, enum direction8 dir,
int x1, int y1, struct pf_parameter *param)
@@ -81,6 +102,55 @@
return move_cost;
}
+/*******************************************************************
+ LAND_MOVE cost function for a unit, but taking into account
+ possibilities of attacking.
+*******************************************************************/
+static int land_attack_move(int x, int y, enum direction8 dir,
+ int x1, int y1, struct pf_parameter *param)
+{
+ struct tile *src_tile = map_get_tile(x, y);
+ struct tile *tgt_tile = map_get_tile(x1, y1);
+ int move_cost;
+
+ if (tgt_tile->terrain == T_OCEAN) {
+
+ /* Any-to-Sea */
+ if (ground_unit_transporter_capacity(x1, y1, param->owner) > 0) {
+ move_cost = SINGLE_MOVE;
+ } else {
+ move_cost = PF_IMPOSSIBLE_MC;
+ }
+ } else if (src_tile->terrain == T_OCEAN) {
+
+ /* Sea-to-Land. TODO: Marines support */
+ if (!is_non_allied_unit_tile(tgt_tile, param->owner)) {
+ move_cost
+ = get_tile_type(tgt_tile->terrain)->movement_cost * SINGLE_MOVE;
+ } else {
+ move_cost = PF_IMPOSSIBLE_MC;
+ }
+ } else {
+
+ /* Land-to-Land */
+ if (is_non_allied_unit_tile(src_tile, param->owner)) {
+ /* Cannot pass through defended tiles */
+ move_cost = PF_IMPOSSIBLE_MC;
+ } else if(is_non_allied_unit_tile(tgt_tile, param->owner)) {
+
+ /* TODO: Intelligent checking */
+ /* Attack! */
+ move_cost = SINGLE_MOVE;
+ } else {
+ /* Normal move */
+ move_cost = src_tile->move_cost[dir];
+ }
+ }
+
+ return move_cost;
+}
+
+
/************************************************************
A cost function for a land unit, which allows going into
the ocean (with moves costing SINGLE_MOVE). It is
@@ -315,11 +385,13 @@
break;
case SEA_MOVING:
parameter->get_MC = sea_overlap_move;
+ break;
default:
die("Unsupported move_type");
}
parameter->zoc_used = FALSE;
+ parameter->omniscience = TRUE;
if (unit_flag(punit, F_TRIREME)
&& base_trireme_loss_pct(unit_owner(punit)) > 0) {
@@ -327,6 +399,41 @@
} else {
parameter->is_pos_dangerous = NULL;
}
+}
+
+/**********************************************************************
+ Consider attacking and non-attacking possibilities properly
+**********************************************************************/
+void pft_fill_unit_attack_param(struct pf_parameter *parameter,
+ struct unit *punit)
+{
+ parameter->turn_mode = TM_CAPPED;
+ parameter->get_TB = NULL;
+ parameter->get_EC = NULL;
+
+ parameter->start_x = punit->x;
+ parameter->start_y = punit->y;
+ parameter->moves_left_initially = punit->moves_left;
+ parameter->move_rate = unit_move_rate(punit);
+ parameter->owner = unit_owner(punit);
+
+ switch (unit_type(punit)->move_type) {
+ case LAND_MOVING:
+ parameter->get_MC = land_attack_move;
+ break;
+ case SEA_MOVING:
+ parameter->get_MC = sea_attack_move;
+ break;
+ default:
+ die("Unsupported move_type");
+ }
+
+ parameter->zoc_used = (unit_type(punit)->move_type == LAND_MOVING
+ && !unit_flag(punit, F_IGZOC));
+ parameter->omniscience = TRUE;
+
+ /* It is too complicated to work with danger here */
+ parameter->is_pos_dangerous = NULL;
}
/**********************************************************************
Index: common/aicore/pf_tools.h
===================================================================
RCS file: /home/freeciv/CVS/freeciv/common/aicore/pf_tools.h,v
retrieving revision 1.1
diff -u -r1.1 pf_tools.h
--- common/aicore/pf_tools.h 2003/03/11 17:59:26 1.1
+++ common/aicore/pf_tools.h 2003/05/06 22:16:34
@@ -20,6 +20,8 @@
struct unit *punit);
void pft_fill_unit_overlap_param(struct pf_parameter *parameter,
struct unit *punit);
+void pft_fill_unit_attack_param(struct pf_parameter *parameter,
+ struct unit *punit);
/*
* Below iterator is mostly for use by AI, iterates through all positions
[freeciv-ai] Re: (PR#4026) Advanced rampage., Gregory Berkolaiko, 2003/05/07
[freeciv-ai] Re: (PR#4026) Advanced rampage., Per I. Mathisen, 2003/05/10
[freeciv-ai] Re: (PR#4026) Advanced rampage., Gregory Berkolaiko, 2003/05/12
[freeciv-ai] Re: (PR#4026) Advanced rampage., Per I. Mathisen, 2003/05/12
|
|