Complete.Org: Mailing Lists: Archives: freeciv-ai: March 2003:
[freeciv-ai] Re: [Freeciv-Dev] Re: (PR#3646) freeciv-ai ai_military_find
Home

[freeciv-ai] Re: [Freeciv-Dev] Re: (PR#3646) freeciv-ai ai_military_find

[Top] [All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index] [Thread Index]
To: ue80@xxxxxxxxxxxxxxxxxxxxx
Subject: [freeciv-ai] Re: [Freeciv-Dev] Re: (PR#3646) freeciv-ai ai_military_findvictim
From: "Gregory Berkolaiko" <Gregory.Berkolaiko@xxxxxxxxxxxx>
Date: Mon, 24 Mar 2003 11:04:36 -0800
Reply-to: rt@xxxxxxxxxxxxxx

On Fri, 7 Mar 2003, ue80@xxxxxxxxxxxxxxxxxxxxx wrote:

[...]

> armour-landed.gz
> Distance to city 2, Moves on grass 3. It's possible to get the city and
> have 1 move left to defend against dipls or something else. AI is
> stopping in front of the city. (Ok its not moving away from the city)

[...]

Attached (if I won't forget) is a proof-of-concept patch which makes 
ai_military_rampage look beyond adjacent tiles.  It was tested on Pille's 
armour-landed.gz savegame (see in the thread) and might crash on other 
savegames for all I know.

But with this savegame it works quite well.  One of the problems is that 
it leaves cities undefended, but this is the fault of the calling code.  I 
deliberately removed all the "shall we stay or shall we go" checks from 
ai_rampage_want (formerly ai_military_findvictim), because I believe if a 
unit is told by authorities to go on a rampage, it should do so 
unreservedly.

However, I am planning to provide finer rampage tuning like different
thresholds for rampage that involves moving (e.g. picking huts) and
rampage which will result in the unit staying put (attacking immediate
neighbours).

In the mean time, I've discovered a couple of bugs in the PF code.

G.

Index: ai/aiunit.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aiunit.c,v
retrieving revision 1.270
diff -u -r1.270 aiunit.c
--- ai/aiunit.c 2003/03/07 05:08:41     1.270
+++ ai/aiunit.c 2003/03/24 18:52:09
@@ -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"
@@ -1001,162 +1004,174 @@
 }
 
 /*************************************************************************
-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:
+This function appraises the location (x, y) for a quick hit-n-run operation.
 
-Returns value of the victim which has been chosen:
+Returns value as follows:
 
-99999    means empty (undefended) city
+99999    means undefended enemy city
 99998    means hut
-2..99997 means value of enemy unit weaker than our unit
+2..99997 is 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
-
-If value <= 0 is returned, (dest_x, dest_y) is set to actual punit's position.
 **************************************************************************/
-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 bellig = unit_belligerence_primitive(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);
-
-  *dest_y = y;
-  *dest_x = x;
- 
-  /* Ferryboats with passengers do not attack. */
-  if (punit->ai.passenger > 0) {
-    return 0;
-  }
- 
-  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;
-  }
-
-  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)
-
-    struct unit *pdef = get_defender(punit, x1, y1);
+  
+  if (pdef) {
     
-    if (pdef) {
-      struct unit *patt = get_attacker(punit, x1, y1);
-
-      if (!can_unit_attack_tile(punit, x1, y1)) {
-        continue;
+    if (!can_unit_attack_tile(punit, x, y)) {
+      return 0;
+    }
+    
+    {
+      /* See description of kill_desire() about this variables. */
+      int bellig = unit_belligerence_primitive(punit);
+      int vuln = unit_vulnerability(punit, pdef);
+      int attack = reinforcements_value(punit, pdef->x, pdef->y) + bellig;
+      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(x, y)) {
+        benefit += unit_value(get_role_unit(F_CITIES, 0));
+        benefit = (benefit * punit->hp) / unit_type(punit)->hp;
       }
-
-      /* 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) + bellig;
-        int benefit = unit_type(pdef)->build_cost;
-        int loss = unit_type(punit)->build_cost;
+      
+      /* If we have non-zero belligerence... */
+      if (attack > 0 && is_my_turn(punit, pdef)) {
+        int desire;
         
-        attack *= attack;
+        /* FIXME? Why we don't use stack_size as victim_count? --pasky */
         
-        /* 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;
-        }
+        desire = kill_desire(benefit, attack, loss, vuln, 1);
         
-        /* If we have non-zero belligerence... */
-        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);
-          }
-        }
+        /* No need to amortize! We're doing it in one-turn horizon. */
+        return desire;
       }
-      
-    } else {
-      struct city *pcity = map_get_city(x1, y1);
+    }
+    
+  } 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_unit_move_to_tile(punit, x1, y1) != 0
-          && COULD_OCCUPY(punit)
-          && !is_ocean(map_get_terrain(punit->x, punit->y))) {
-        SET_BEST(99999);
-        continue;
+          && COULD_OCCUPY(punit)) {
+
+        return 99999;
       }
 
       /* ...or tiny pleasant hut here! */
-      if (map_has_special(x1, y1, S_HUT) && best < 99999
-          && could_unit_move_to_tile(punit, x1, y1) != 0
+      if (map_has_special(x, y, S_HUT)
           && !is_barbarian(unit_owner(punit))
           && punit->ai.ai_role != AIUNIT_ESCORT
-          && punit->ai.charge == BODYGUARD_NONE /* Above line doesn't seem to 
work. :( */
+          /* Above line doesn't seem to work. :( */
+          && punit->ai.charge == BODYGUARD_NONE
           && punit->ai.ai_role != AIUNIT_DEFEND_HOME) {
-        SET_BEST(99998);
-        continue;
+
+        /* TODO: All these bodyguard checks don't belong here! */
+        return 99998;
       }
       
       /* 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;
+      if (is_land_barbarian(pplayer)
+          && get_tile_infrastructure_set(map_get_tile(x, y)) != S_NO_SPECIAL) {
+        return 1;
       }
     }
-#undef SET_BEST
-  } adjc_iterate_end;
+
+  return 0;
+}
 
-  return best;
+/*************************************************************************
+  Look for worthy targets within one-turn horizon.
+*************************************************************************/
+static int find_rampage_target(struct unit *punit, int *x, int *y, 
+                               struct pf_path **path)
+{
+  struct pf_map *map;
+  struct pf_parameter parameter;
+  int max_want = 0;
+ 
+  pft_fill_default_parameter(&parameter);  
+  pft_fill_unit_attack_param(&parameter, punit);
+  
+  map = pf_create_map(&parameter);
+  while (pf_next(map)) {
+    struct pf_position pos;
+    int want;
+    
+    pf_next_get_position(map, &pos);
+    
+    if (pos.total_MC > punit->moves_left) {
+      /* This is too far */
+      break;
+    }
+    
+    want = ai_rampage_want(punit, pos.x, pos.y);
+    if (want > max_want) {
+      max_want = want;
+      *x = pos.x;
+      *y = pos.y;
+    }
+  }
+
+  if (max_want > 0) {
+    *path = pf_get_path(map, *x, *y);
+  }
+
+  pf_destroy_map(map);
+  
+  return max_want;
 }
 
 /*************************************************************************
-  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. 
+  Put a path-executing function here
+*************************************************************************/
+static bool ai_unit_execute_path(struct unit *punit, struct pf_path *path)
+{
+  int i;
+  int sanity = punit->id;
+  
+  handle_unit_activity_request(punit, ACTIVITY_IDLE);
+  pf_print_path(LOG_NORMAL, path);
+
+  for (i = 1; i <= path->length; i++) {
+    struct packet_move_unit pmove;
+    
+    pmove.x = path->positions[i].x;
+    pmove.y = path->positions[i].y;
+    pmove.unid = punit->id;
+    handle_move_unit(unit_owner(punit), &pmove);
+    
+    if (!find_unit_by_id(sanity)) {
+      /* Died... */
+      return FALSE;
+    }
+    
+    if (!same_pos(punit->x, punit->y, pmove.x, pmove.y)) {
+      /* Stopped (or maybe fought) */
+      return TRUE;
+    }
+  }
+
+  return TRUE;
+}
+
+/*************************************************************************
+  Find and kill anything reachable within this turn and worth more than 
+  the given threshold until we have run out of juicy targets or movement. 
   Wraps ai_military_findvictim().
   Returns TRUE if survived the rampage session.
 **************************************************************************/
@@ -1164,18 +1179,23 @@
 {
   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, &x, &y, &path) >= threshold) {
+    if (!ai_unit_execute_path(punit, path)) {
       /* Died */
-      return FALSE;
+      count = -1;
     }
+    pf_destroy_path(path);
+    path = NULL;
   }
-  assert(count >= 0);
-  return TRUE;
+  
+  pf_destroy_path(path);
+  path = NULL;
+  return (count >= 0);
 }
 
 /*************************************************************************
Index: common/aicore/path_finding.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/common/aicore/path_finding.c,v
retrieving revision 1.2
diff -u -r1.2 path_finding.c
--- common/aicore/path_finding.c        2003/03/21 20:49:27     1.2
+++ common/aicore/path_finding.c        2003/03/24 18:52:09
@@ -208,9 +208,12 @@
     bool my_zoc = (tile->city || tile->terrain == T_OCEAN
                   || is_my_zoc(params->owner, x, y));
     bool allied = (is_allied_unit_tile(tile, params->owner) != NULL);
+    bool enemy = (is_enemy_unit_tile(tile, params->owner) != NULL);
 
     /* if my zoc 2 else if allied 1 else 0 */
-    node->zoc_number = (my_zoc ? 2 : (allied ? 1 : 0));
+    /* Essentially, enemy tile is like allied tile, we should be allowed 
+     * to go there (attack), but not to leave, necessarily */
+    node->zoc_number = (my_zoc ? 2 : ((allied || enemy) ? 1 : 0));
   }
 
   /* Evaluate the extra cost of the destination */
@@ -574,6 +577,11 @@
     /* pf_fill_position doesn't set direction */
     path->positions[i].dir_to_next_pos = dir_next;
 
+    if (i == 0) {
+      /* That's it */
+      break;
+    }
+
     dir_next = node->dir_to_here;
 
     /* Step further back */
@@ -651,8 +659,10 @@
 ************************************************************************/
 void pf_destroy_path(struct pf_path *path)
 {
-  free(path->positions);
-  free(path);
+  if (path) {
+    free(path->positions);
+    free(path);
+  }
 }
 
 
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/03/24 18:52:09
@@ -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/03/24 18:52:09
@@ -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

[Prev in Thread] Current Thread [Next in Thread]