Complete.Org: Mailing Lists: Archives: freeciv-ai: May 2004:
[freeciv-ai] (PR#8777) Find ferry
Home

[freeciv-ai] (PR#8777) Find ferry

[Top] [All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index] [Thread Index]
To: undisclosed-recipients: ;
Subject: [freeciv-ai] (PR#8777) Find ferry
From: "Gregory Berkolaiko" <Gregory.Berkolaiko@xxxxxxxxxxxxx>
Date: Thu, 20 May 2004 22:20:58 -0700
Reply-to: rt@xxxxxxxxxxx

<URL: http://rt.freeciv.org/Ticket/Display.html?id=8777 >

> [glip - Wed May 19 18:37:19 2004]:
> 
> Here is the implementation of find_ferry function torn out from the new
> settler code.  It is tested using the old settler code and it works, see
> settler4.gz.  Now even settlers inland can find a ferry far away in the
> seas to take them to a new continent.
> 
> New find_ferry should eventually replace all uses of find_boat.
> Changes to server/settler.c are for testing only and can be removed.

An updated code.  Now it won't look for a ferry if there are none to be
found.  How does it know?  aidata does the counting.

I attach:
find_ferry2.diff -- the patch in the form that should go in
find_ferry_test.diff -- with various debug messages switched on and
server/settlers.c wired to work with it
settler4.gz -- a test game

Per, I recommend you to have a look at the performance of Syela's
settler code in this case.  Modulo minor problems like caravel going off
to explore while a city is building the settler -- instead of waiting
for the settler to be finished (this is actually settler code fault), it
performs very well and covers the island with 4 cities, something I am
not sure your/our code does.  Maybe you can tune the parameters, but
after I fix some stuff with ferries building.

G.
? ferry_and_settler.txt
? settle4.gz
? stuck.gz
? ai/aisettler.c
? ai/aisettler.h
? common/aicore/citymap.c
? common/aicore/citymap.h
Index: ai/aidata.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aidata.c,v
retrieving revision 1.24
diff -u -r1.24 aidata.c
--- ai/aidata.c 1 May 2004 03:22:11 -0000       1.24
+++ ai/aidata.c 21 May 2004 04:57:53 -0000
@@ -395,8 +395,9 @@
 }
 
 /**************************************************************************
-  Use this wrapper to correctly update the statistics. Use NULL to
-  unregister any ferry that might be there.
+  Use this wrapper to correctly update the statistics. Use ferry=NULL to
+  request a ferry.  Should be used in conjunction with ai_set_passenger
+  if ferry!=NULL.
 **************************************************************************/
 void ai_set_ferry(struct unit *punit, struct unit *ferry)
 {
@@ -409,7 +410,6 @@
   } else if (ferry) {
     /* Make sure we delete punit from the list of potential passengers */
     ai_clear_ferry(punit);
-    ferry->ai.passenger = punit->id;
     punit->ai.ferryboat = ferry->id;
   }
 }
@@ -458,6 +458,37 @@
 }
 
 /**************************************************************************
+  Returns the number of available boats.  A simple accessor made to perform 
+  debug checks.
+**************************************************************************/
+int ai_available_boats(struct player *pplayer)
+{
+  struct ai_data *ai = ai_data_get(pplayer);
+
+  /* To developer: Switch this checking on when testing some new 
+   * ferry code. */
+#if 1
+  int boats = 0;
+
+  unit_list_iterate(pplayer->units, punit) {
+    struct tile *ptile = map_get_tile(punit->x, punit->y);
+
+    if (is_sailing_unit(punit) && is_ground_units_transport(punit) 
+       && punit->ai.passenger == FERRY_AVAILABLE) {
+      boats++;
+    }
+  } unit_list_iterate_end;
+
+  if (boats != ai->stats.available_boats) {
+    freelog(LOG_ERROR, "Boats miscounted: recorded %d but in reality %d",
+           ai->stats.available_boats, boats);
+  }
+#endif
+
+  return ai->stats.available_boats;
+}
+
+/**************************************************************************
   Deinitialize data
 **************************************************************************/
 void ai_data_done(struct player *pplayer)
Index: ai/aidata.h
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aidata.h,v
retrieving revision 1.11
diff -u -r1.11 aidata.h
--- ai/aidata.h 9 Oct 2003 00:07:33 -0000       1.11
+++ ai/aidata.h 21 May 2004 04:57:54 -0000
@@ -138,6 +138,7 @@
 void ai_set_passenger(struct unit *punit, struct unit *passenger);
 void ai_set_ferry(struct unit *punit, struct unit *ferry);
 void ai_clear_ferry(struct unit *punit);
+int ai_available_boats(struct player *pplayer);
 
 
 #endif
Index: ai/aitools.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aitools.c,v
retrieving revision 1.103
diff -u -r1.103 aitools.c
--- ai/aitools.c        4 May 2004 17:40:25 -0000       1.103
+++ ai/aitools.c        21 May 2004 04:58:00 -0000
@@ -97,8 +97,8 @@
   This is a function to execute paths returned by the path-finding engine.
   It is analogous to goto_route_execute, only much simpler.
 
-  We use ai_unit_attack which means "move if target unoccupied, attack
-  otherwise" and also brings our bodyguard along.
+  Brings our bodyguard along.
+  Returns FALSE only if died.
 *************************************************************************/
 bool ai_unit_execute_path(struct unit *punit, struct pf_path *path)
 {
@@ -107,7 +107,6 @@
   /* 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;
-    bool result;
     int id = punit->id;
 
     /* We use ai_unit_move() for everything but the last step
@@ -115,12 +114,11 @@
      * shows up. Any enemy on the target tile is expected to
      * be our target and any attack there intentional. */
     if (i == path->length - 1) {
-      result = ai_unit_attack(punit, x, y);
+      (void) ai_unit_attack(punit, x, y);
     } else {
-      ai_unit_move(punit, x, y);
-      result = (find_unit_by_id(id) != NULL);
+      (void) ai_unit_move(punit, x, y);
     }
-    if (!result) {
+    if (!find_unit_by_id(id)) {
       /* Died... */
       return FALSE;
     }
@@ -206,7 +204,137 @@
   /* What if we have a bodyguard, but don't need one? */
 }
 
-#define LOGLEVEL_GOTHERE LOG_DEBUG
+/****************************************************************************
+  Combined cost function for a land unit looking for a ferry.  The path 
+  finding first goes over the continent and then into the ocean where we 
+  actually look for ferry.  Thus moves land-to-sea are allowed and moves
+  sea-to-land are not.  A consequence is that we don't get into the cities
+  on other continent, which might station boats.  This defficiency seems to
+  be impossible to fix with the current PF structure, so it has to be
+  accounted for in the actual ferry search function.
+
+  For movements sea-to-sea the cost is collected via the extra cost 
+  call-back.  Doesn't care for enemy/neutral tiles, these should be excluded
+  using a TB call-back.
+****************************************************************************/
+static int combined_land_sea_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 (is_ocean(tgt_tile->terrain)) {
+    /* Any-to-Sea */
+    move_cost = 0;
+  } else if (src_tile->terrain == T_OCEAN) {
+    /* Sea-to-Land */
+    move_cost = PF_IMPOSSIBLE_MC;
+  } else {
+    /* Land-to-Land */
+    move_cost = src_tile->move_cost[dir];
+  }
+
+  return move_cost;
+}
+
+/****************************************************************************
+  EC callback to account for the cost of sea moves by a ferry hurrying to 
+  pick our unit up.
+****************************************************************************/
+static int sea_move(int x, int y, enum known_type known,
+                    struct pf_parameter *param)
+{
+  if (is_ocean(map_get_tile(x, y)->terrain)) {
+    /* Approximately TURN_FACTOR / average ferry move rate 
+     * we can pass a better guess of the move rate through param->data
+     * but we don't know which boat we will find out there */
+    return SINGLE_MOVE * PF_TURN_FACTOR / 12;
+  } else {
+    return 0;
+  }
+}
+
+#define LOGLEVEL_FINDFERRY LOG_NORMAL
+/****************************************************************************
+  Proper and real PF function for finding a boat.  If you don't require
+  the path to the ferry, pass path=NULL.
+
+  WARNING: Due to the nature of this function and PF (see the comment of 
+  combined_land_sea_move), the path won't lead onto the boat itself.
+
+  FIXME: Actually check the capacity.
+****************************************************************************/
+int find_ferry(struct unit *punit, int cap, struct pf_path **path)
+{
+  int best_turns = FC_INFINITY;
+  int best_id = 0;
+  struct pf_parameter param;
+  struct pf_map *search_map;
+
+
+  UNIT_LOG(LOGLEVEL_FINDFERRY, punit, "asked find_ferry for a boat");
+
+  if (ai_available_boats(unit_owner(punit)) <= 0 
+      && punit->ai.ferryboat <= 0) {
+    /* No boats to be found (the second check is to ensure that we are not 
+     * the ones keeping the last boat busy) */
+    return 0;
+  }
+
+  pft_fill_unit_parameter(&param, punit);
+  param.turn_mode = TM_WORST_TIME;
+  param.get_TB = no_fights_or_unknown;
+  param.get_EC = sea_move;
+  param.get_MC = combined_land_sea_move;
+
+  search_map = pf_create_map(&param);
+
+  pf_iterator(search_map, pos) {
+    int radius = (is_ocean(map_get_tile(pos.x, pos.y)->terrain) ? 1 : 0);
+
+    if (pos.turn + pos.total_EC/PF_TURN_FACTOR > best_turns) {
+      /* Won't find anything better */
+      /* FIXME: This condition is somewhat dodgy */
+      break;
+    }
+    
+    square_iterate(pos.x, pos.y, radius, x, y) {
+      struct tile *ptile = map_get_tile(x, y);
+      
+      unit_list_iterate(ptile->units, aunit) {
+        if (is_ground_units_transport(aunit)
+            && (aunit->ai.passenger == FERRY_AVAILABLE
+                || aunit->ai.passenger == punit->id)) {
+          /* Turns for the unit to get to rendezvous pnt */
+          int u_turns = pos.turn;
+          /* Turns for the boat to get to the rendezvous pnt */
+          int f_turns = ((pos.total_EC / PF_TURN_FACTOR * 16 
+                          - aunit->moves_left) 
+                         / unit_type(aunit)->move_rate);
+          int turns = MAX(u_turns, f_turns);
+          
+          if (turns < best_turns) {
+            UNIT_LOG(LOGLEVEL_FINDFERRY, punit, 
+                     "Found a potential boat %s[%d](%d,%d)",
+                     unit_type(aunit)->name, aunit->id, aunit->x, aunit->y);
+           if (path) {
+             *path = pf_next_get_path(search_map);
+           }
+            best_turns = turns;
+            best_id = aunit->id;
+          }
+        }
+      } unit_list_iterate_end;
+    } square_iterate_end;
+  } pf_iterator_end;
+  pf_destroy_map(search_map);
+
+  return best_id;
+}
+
+#define LOGLEVEL_GOTHERE LOG_NORMAL
 /****************************************************************************
   This is ferry-enabled goto.  Should not normally be used for non-ferried 
   units (i.e. planes or ships), use ai_unit_goto instead.
@@ -220,6 +348,7 @@
                 int dest_x, int dest_y)
 {
   struct unit *bodyguard = find_unit_by_id(punit->ai.bodyguard);
+  struct pf_path *path_to_ferry = NULL;
 
   CHECK_UNIT(punit);
 
@@ -242,8 +371,7 @@
              dest_x, dest_y);
 
     if (boatid <= 0) {
-      int bx, by;
-      boatid = find_boat(pplayer, &bx, &by, 2);
+      boatid = find_ferry(punit, 2, &path_to_ferry);
     } 
     ferryboat = find_unit_by_id(boatid);
 
@@ -259,21 +387,29 @@
     }
 
     ai_set_ferry(punit, ferryboat);
-    if (!same_pos(punit->x, punit->y, ferryboat->x, ferryboat->y)
-        && (!is_at_coast(punit->x, punit->y) 
-            || is_tiles_adjacent(punit->x, punit->y, 
-                                 ferryboat->x, ferryboat->y))) {
+    ai_set_passenger(ferryboat, punit);
+
+    if (!is_tiles_adjacent(punit->x, punit->y, ferryboat->x, ferryboat->y)
+       && !same_pos(punit->x, punit->y, ferryboat->x, ferryboat->y)
+       && !is_at_coast(punit->x, punit->y)) {
       /* Go to the boat only if it cannot reach us or it's parked 
        * next to us.  Otherwise just wait (boats are normally faster) */
-      /* FIXME: agree on a rendez-vous point */
-      /* FIXME: this can lose bodyguard */
+      /* TODO: agree on a rendez-vous point */
       UNIT_LOG(LOGLEVEL_GOTHERE, punit, "going to boat[%d](%d,%d).", boatid,
                ferryboat->x, ferryboat->y);
-      if (!ai_unit_goto(punit, ferryboat->x, ferryboat->y)) { 
+      /* The path can be amphibious so we will stop at the coast.  
+       * It might not lead _onto_ the boat. */
+      if (!ai_unit_execute_path(punit, path_to_ferry)) { 
         /* Died. */
+       pf_destroy_path(path_to_ferry);
         return FALSE;
       }
     }
+    pf_destroy_path(path_to_ferry);
+
+    if (is_tiles_adjacent(punit->x, punit->y, ferryboat->x, ferryboat->y)) {
+      (void) ai_unit_move(punit, ferryboat->x, ferryboat->y);
+    }
     
     if (!same_pos(punit->x, punit->y, ferryboat->x, ferryboat->y)) {
       /* Didn't get to the boat */
Index: ai/aitools.h
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aitools.h,v
retrieving revision 1.41
diff -u -r1.41 aitools.h
--- ai/aitools.h        9 Jan 2004 16:59:49 -0000       1.41
+++ ai/aitools.h        21 May 2004 04:58:00 -0000
@@ -78,5 +78,7 @@
 bool ai_wants_no_science(struct player *pplayer);
 
 bool is_player_dangerous(struct player *pplayer, struct player *aplayer);
+int find_ferry(struct unit *punit, int cap, struct pf_path **path);
+
 
 #endif  /* FC__AITOOLS_H */
Index: ai/aiunit.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aiunit.c,v
retrieving revision 1.315
diff -u -r1.315 aiunit.c
--- ai/aiunit.c 19 May 2004 14:40:15 -0000      1.315
+++ ai/aiunit.c 21 May 2004 04:58:16 -0000
@@ -2415,8 +2415,6 @@
 static void ai_manage_ferryboat(struct player *pplayer, struct unit *punit)
 {
   struct city *pcity;
-  int oldbossid = -1;  /* Loop prevention. If boss doesn't want to move,
-                        * neither do we. */
 
   CHECK_UNIT(punit);
 
@@ -2438,17 +2436,20 @@
     /* Do we have the passenger-in-charge on board? */
     struct tile *ptile = map_get_tile(punit->x, punit->y);
 
-    if (punit->ai.passenger > 0 
-        && !unit_list_find(&ptile->units, punit->ai.passenger)) {
-      UNIT_LOG(LOGLEVEL_FERRY, punit, 
-              "lost passenger-in-charge[%d], resetting",
-               punit->ai.passenger);
-      ai_set_passenger(punit, NULL);
-    } else if (oldbossid > 0) {
-      /* Need to look for a new boss */
-      UNIT_LOG(LOGLEVEL_FERRY, punit, "taking control back from [%d]", 
-              oldbossid);
-      ai_set_passenger(punit, NULL);
+    if (punit->ai.passenger > 0) {
+      struct unit *psngr = find_unit_by_id(punit->ai.passenger);
+      
+      /* If the passenger-in-charge is adjacent, we should wait for it to 
+       * board.  We will pass control to it later.
+       * FIXME: A possible side-effect: a boat will linger near a passenger 
+       * which already landed. */
+      if (!psngr 
+         || real_map_distance(punit->x, punit->y, psngr->x, psngr->y) > 1) {
+       UNIT_LOG(LOGLEVEL_FERRY, punit, 
+                "lost passenger-in-charge[%d], resetting",
+                punit->ai.passenger);
+       punit->ai.passenger = 0;
+      }
     }
 
     if (punit->ai.passenger <= 0) {
@@ -2470,11 +2471,6 @@
         }
       } unit_list_iterate_end;
       
-      if (candidate && candidate->id == oldbossid) {
-       /* The boss decided to stay put on the ferry. We aren't moving. */
-       return;
-      }
-
       if (candidate) {
         UNIT_LOG(LOGLEVEL_FERRY, punit, 
                  "appointed %s[%d] our passenger-in-charge",
@@ -2487,11 +2483,11 @@
     }
 
     if (punit->ai.passenger > 0) {
+      int bossid = punit->ai.passenger;    /* Loop prevention */
       struct unit *boss = find_unit_by_id(punit->ai.passenger);
       int id = punit->id;                  /* To check if survived */
 
       assert(boss != NULL);
-      oldbossid = punit->ai.passenger;
 
       if (unit_flag(boss, F_SETTLERS) || unit_flag(boss, F_CITIES)) {
         /* Temporary hack: settlers all go in the end, forcing them 
@@ -2506,6 +2502,11 @@
       if (!find_unit_by_id(id) || punit->moves_left <= 0) {
         return;
       }
+      if (find_unit_by_id(bossid) 
+         && same_pos(punit->x, punit->y, boss->x, boss->y)) {
+       /* The boss decided to stay put on the ferry. We aren't moving. */
+       return;
+      }
     } else {
       /* Cannot select a passenger-in-charge */
       break;
Index: server/settlers.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/server/settlers.c,v
retrieving revision 1.181
diff -u -r1.181 settlers.c
--- server/settlers.c   19 May 2004 14:40:15 -0000      1.181
+++ server/settlers.c   21 May 2004 04:58:35 -0000
@@ -928,11 +928,11 @@
   int food_upkeep        = unit_food_upkeep(punit);
   int food_cost          = unit_foodbox_cost(punit);
 
-  int boatid, bx = 0, by = 0;  /* as returned by find_boat */
+  int boatid, bx = punit->x, by = punit->y;    /* as returned by find_boat */
   enemy_mask my_enemies = enemies[pplayer->player_no]; /* optimalization */
 
   if (pplayer->ai.control)
-    boatid = find_boat(pplayer, &bx, &by, 1); /* might need 2 for bodyguard */
+    boatid = find_ferry(punit, 2, NULL); /* might need 2 for bodyguard */
   else
     boatid = 0;
   *ferryboat = unit_list_find(&(map_get_tile(punit->x, punit->y)->units), 
boatid);
@@ -969,9 +969,7 @@
       } else if (!goto_is_sane(punit, x, y, TRUE) ||
                 WARMAP_COST(x, y) > THRESHOLD * mv_rate) {
        /* for Rome->Carthage */
-       if (!is_ocean_near_tile(x, y)) {
-         mv_cost = 9999;
-       } else if (boatid != 0) {
+       if (boatid != 0) {
          if (punit->id == 0 && mycity->id == boatid) {
            w_virtual = TRUE;
          }
@@ -1028,7 +1026,7 @@
       }
 #endif
 
-      if (map_get_continent(x, y) != ucont && !nav_known && near >= 8) {
+      if (map_get_continent(x, y) != ucont && !nav_known && near >= 10) {
 #ifdef REALLY_DEBUG_THIS
        freelog(LOG_DEBUG,
                "%s (%d, %d) rejected city at (%d, %d) to %d, newv = %d, moves 
= %d" \
Index: ai/aidata.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aidata.c,v
retrieving revision 1.24
diff -u -r1.24 aidata.c
--- ai/aidata.c 1 May 2004 03:22:11 -0000       1.24
+++ ai/aidata.c 21 May 2004 05:00:53 -0000
@@ -395,8 +395,9 @@
 }
 
 /**************************************************************************
-  Use this wrapper to correctly update the statistics. Use NULL to
-  unregister any ferry that might be there.
+  Use this wrapper to correctly update the statistics. Use ferry=NULL to
+  request a ferry.  Should be used in conjunction with ai_set_passenger
+  if ferry!=NULL.
 **************************************************************************/
 void ai_set_ferry(struct unit *punit, struct unit *ferry)
 {
@@ -409,7 +410,6 @@
   } else if (ferry) {
     /* Make sure we delete punit from the list of potential passengers */
     ai_clear_ferry(punit);
-    ferry->ai.passenger = punit->id;
     punit->ai.ferryboat = ferry->id;
   }
 }
@@ -458,6 +458,37 @@
 }
 
 /**************************************************************************
+  Returns the number of available boats.  A simple accessor made to perform 
+  debug checks.
+**************************************************************************/
+int ai_available_boats(struct player *pplayer)
+{
+  struct ai_data *ai = ai_data_get(pplayer);
+
+  /* To developer: Switch this checking on when testing some new 
+   * ferry code. */
+#if 0
+  int boats = 0;
+
+  unit_list_iterate(pplayer->units, punit) {
+    struct tile *ptile = map_get_tile(punit->x, punit->y);
+
+    if (is_sailing_unit(punit) && is_ground_units_transport(punit) 
+       && punit->ai.passenger == FERRY_AVAILABLE) {
+      boats++;
+    }
+  } unit_list_iterate_end;
+
+  if (boats != ai->stats.available_boats) {
+    freelog(LOG_ERROR, "Boats miscounted: recorded %d but in reality %d",
+           ai->stats.available_boats, boats);
+  }
+#endif
+
+  return ai->stats.available_boats;
+}
+
+/**************************************************************************
   Deinitialize data
 **************************************************************************/
 void ai_data_done(struct player *pplayer)
Index: ai/aidata.h
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aidata.h,v
retrieving revision 1.11
diff -u -r1.11 aidata.h
--- ai/aidata.h 9 Oct 2003 00:07:33 -0000       1.11
+++ ai/aidata.h 21 May 2004 05:00:53 -0000
@@ -138,6 +138,7 @@
 void ai_set_passenger(struct unit *punit, struct unit *passenger);
 void ai_set_ferry(struct unit *punit, struct unit *ferry);
 void ai_clear_ferry(struct unit *punit);
+int ai_available_boats(struct player *pplayer);
 
 
 #endif
Index: ai/aitools.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aitools.c,v
retrieving revision 1.103
diff -u -r1.103 aitools.c
--- ai/aitools.c        4 May 2004 17:40:25 -0000       1.103
+++ ai/aitools.c        21 May 2004 05:00:59 -0000
@@ -97,8 +97,8 @@
   This is a function to execute paths returned by the path-finding engine.
   It is analogous to goto_route_execute, only much simpler.
 
-  We use ai_unit_attack which means "move if target unoccupied, attack
-  otherwise" and also brings our bodyguard along.
+  Brings our bodyguard along.
+  Returns FALSE only if died.
 *************************************************************************/
 bool ai_unit_execute_path(struct unit *punit, struct pf_path *path)
 {
@@ -107,7 +107,6 @@
   /* 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;
-    bool result;
     int id = punit->id;
 
     /* We use ai_unit_move() for everything but the last step
@@ -115,12 +114,11 @@
      * shows up. Any enemy on the target tile is expected to
      * be our target and any attack there intentional. */
     if (i == path->length - 1) {
-      result = ai_unit_attack(punit, x, y);
+      (void) ai_unit_attack(punit, x, y);
     } else {
-      ai_unit_move(punit, x, y);
-      result = (find_unit_by_id(id) != NULL);
+      (void) ai_unit_move(punit, x, y);
     }
-    if (!result) {
+    if (!find_unit_by_id(id)) {
       /* Died... */
       return FALSE;
     }
@@ -206,6 +204,136 @@
   /* What if we have a bodyguard, but don't need one? */
 }
 
+/****************************************************************************
+  Combined cost function for a land unit looking for a ferry.  The path 
+  finding first goes over the continent and then into the ocean where we 
+  actually look for ferry.  Thus moves land-to-sea are allowed and moves
+  sea-to-land are not.  A consequence is that we don't get into the cities
+  on other continent, which might station boats.  This defficiency seems to
+  be impossible to fix with the current PF structure, so it has to be
+  accounted for in the actual ferry search function.
+
+  For movements sea-to-sea the cost is collected via the extra cost 
+  call-back.  Doesn't care for enemy/neutral tiles, these should be excluded
+  using a TB call-back.
+****************************************************************************/
+static int combined_land_sea_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 (is_ocean(tgt_tile->terrain)) {
+    /* Any-to-Sea */
+    move_cost = 0;
+  } else if (src_tile->terrain == T_OCEAN) {
+    /* Sea-to-Land */
+    move_cost = PF_IMPOSSIBLE_MC;
+  } else {
+    /* Land-to-Land */
+    move_cost = src_tile->move_cost[dir];
+  }
+
+  return move_cost;
+}
+
+/****************************************************************************
+  EC callback to account for the cost of sea moves by a ferry hurrying to 
+  pick our unit up.
+****************************************************************************/
+static int sea_move(int x, int y, enum known_type known,
+                    struct pf_parameter *param)
+{
+  if (is_ocean(map_get_tile(x, y)->terrain)) {
+    /* Approximately TURN_FACTOR / average ferry move rate 
+     * we can pass a better guess of the move rate through param->data
+     * but we don't know which boat we will find out there */
+    return SINGLE_MOVE * PF_TURN_FACTOR / 12;
+  } else {
+    return 0;
+  }
+}
+
+#define LOGLEVEL_FINDFERRY LOG_DEBUG
+/****************************************************************************
+  Proper and real PF function for finding a boat.  If you don't require
+  the path to the ferry, pass path=NULL.
+
+  WARNING: Due to the nature of this function and PF (see the comment of 
+  combined_land_sea_move), the path won't lead onto the boat itself.
+
+  FIXME: Actually check the capacity.
+****************************************************************************/
+int find_ferry(struct unit *punit, int cap, struct pf_path **path)
+{
+  int best_turns = FC_INFINITY;
+  int best_id = 0;
+  struct pf_parameter param;
+  struct pf_map *search_map;
+
+
+  UNIT_LOG(LOGLEVEL_FINDFERRY, punit, "asked find_ferry for a boat");
+
+  if (ai_available_boats(unit_owner(punit)) <= 0 
+      && punit->ai.ferryboat <= 0) {
+    /* No boats to be found (the second check is to ensure that we are not 
+     * the ones keeping the last boat busy) */
+    return 0;
+  }
+
+  pft_fill_unit_parameter(&param, punit);
+  param.turn_mode = TM_WORST_TIME;
+  param.get_TB = no_fights_or_unknown;
+  param.get_EC = sea_move;
+  param.get_MC = combined_land_sea_move;
+
+  search_map = pf_create_map(&param);
+
+  pf_iterator(search_map, pos) {
+    int radius = (is_ocean(map_get_tile(pos.x, pos.y)->terrain) ? 1 : 0);
+
+    if (pos.turn + pos.total_EC/PF_TURN_FACTOR > best_turns) {
+      /* Won't find anything better */
+      /* FIXME: This condition is somewhat dodgy */
+      break;
+    }
+    
+    square_iterate(pos.x, pos.y, radius, x, y) {
+      struct tile *ptile = map_get_tile(x, y);
+      
+      unit_list_iterate(ptile->units, aunit) {
+        if (is_ground_units_transport(aunit)
+            && (aunit->ai.passenger == FERRY_AVAILABLE
+                || aunit->ai.passenger == punit->id)) {
+          /* Turns for the unit to get to rendezvous pnt */
+          int u_turns = pos.turn;
+          /* Turns for the boat to get to the rendezvous pnt */
+          int f_turns = ((pos.total_EC / PF_TURN_FACTOR * 16 
+                          - aunit->moves_left) 
+                         / unit_type(aunit)->move_rate);
+          int turns = MAX(u_turns, f_turns);
+          
+          if (turns < best_turns) {
+            UNIT_LOG(LOGLEVEL_FINDFERRY, punit, 
+                     "Found a potential boat %s[%d](%d,%d)",
+                     unit_type(aunit)->name, aunit->id, aunit->x, aunit->y);
+           if (path) {
+             *path = pf_next_get_path(search_map);
+           }
+            best_turns = turns;
+            best_id = aunit->id;
+          }
+        }
+      } unit_list_iterate_end;
+    } square_iterate_end;
+  } pf_iterator_end;
+  pf_destroy_map(search_map);
+
+  return best_id;
+}
+
 #define LOGLEVEL_GOTHERE LOG_DEBUG
 /****************************************************************************
   This is ferry-enabled goto.  Should not normally be used for non-ferried 
@@ -220,6 +348,7 @@
                 int dest_x, int dest_y)
 {
   struct unit *bodyguard = find_unit_by_id(punit->ai.bodyguard);
+  struct pf_path *path_to_ferry = NULL;
 
   CHECK_UNIT(punit);
 
@@ -242,8 +371,7 @@
              dest_x, dest_y);
 
     if (boatid <= 0) {
-      int bx, by;
-      boatid = find_boat(pplayer, &bx, &by, 2);
+      boatid = find_ferry(punit, 2, &path_to_ferry);
     } 
     ferryboat = find_unit_by_id(boatid);
 
@@ -259,21 +387,29 @@
     }
 
     ai_set_ferry(punit, ferryboat);
-    if (!same_pos(punit->x, punit->y, ferryboat->x, ferryboat->y)
-        && (!is_at_coast(punit->x, punit->y) 
-            || is_tiles_adjacent(punit->x, punit->y, 
-                                 ferryboat->x, ferryboat->y))) {
+    ai_set_passenger(ferryboat, punit);
+
+    if (!is_tiles_adjacent(punit->x, punit->y, ferryboat->x, ferryboat->y)
+       && !same_pos(punit->x, punit->y, ferryboat->x, ferryboat->y)
+       && !is_at_coast(punit->x, punit->y)) {
       /* Go to the boat only if it cannot reach us or it's parked 
        * next to us.  Otherwise just wait (boats are normally faster) */
-      /* FIXME: agree on a rendez-vous point */
-      /* FIXME: this can lose bodyguard */
+      /* TODO: agree on a rendez-vous point */
       UNIT_LOG(LOGLEVEL_GOTHERE, punit, "going to boat[%d](%d,%d).", boatid,
                ferryboat->x, ferryboat->y);
-      if (!ai_unit_goto(punit, ferryboat->x, ferryboat->y)) { 
+      /* The path can be amphibious so we will stop at the coast.  
+       * It might not lead _onto_ the boat. */
+      if (!ai_unit_execute_path(punit, path_to_ferry)) { 
         /* Died. */
+       pf_destroy_path(path_to_ferry);
         return FALSE;
       }
     }
+    pf_destroy_path(path_to_ferry);
+
+    if (is_tiles_adjacent(punit->x, punit->y, ferryboat->x, ferryboat->y)) {
+      (void) ai_unit_move(punit, ferryboat->x, ferryboat->y);
+    }
     
     if (!same_pos(punit->x, punit->y, ferryboat->x, ferryboat->y)) {
       /* Didn't get to the boat */
Index: ai/aitools.h
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aitools.h,v
retrieving revision 1.41
diff -u -r1.41 aitools.h
--- ai/aitools.h        9 Jan 2004 16:59:49 -0000       1.41
+++ ai/aitools.h        21 May 2004 05:01:00 -0000
@@ -78,5 +78,7 @@
 bool ai_wants_no_science(struct player *pplayer);
 
 bool is_player_dangerous(struct player *pplayer, struct player *aplayer);
+int find_ferry(struct unit *punit, int cap, struct pf_path **path);
+
 
 #endif  /* FC__AITOOLS_H */
Index: ai/aiunit.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/ai/aiunit.c,v
retrieving revision 1.315
diff -u -r1.315 aiunit.c
--- ai/aiunit.c 19 May 2004 14:40:15 -0000      1.315
+++ ai/aiunit.c 21 May 2004 05:01:15 -0000
@@ -2415,8 +2415,6 @@
 static void ai_manage_ferryboat(struct player *pplayer, struct unit *punit)
 {
   struct city *pcity;
-  int oldbossid = -1;  /* Loop prevention. If boss doesn't want to move,
-                        * neither do we. */
 
   CHECK_UNIT(punit);
 
@@ -2438,17 +2436,20 @@
     /* Do we have the passenger-in-charge on board? */
     struct tile *ptile = map_get_tile(punit->x, punit->y);
 
-    if (punit->ai.passenger > 0 
-        && !unit_list_find(&ptile->units, punit->ai.passenger)) {
-      UNIT_LOG(LOGLEVEL_FERRY, punit, 
-              "lost passenger-in-charge[%d], resetting",
-               punit->ai.passenger);
-      ai_set_passenger(punit, NULL);
-    } else if (oldbossid > 0) {
-      /* Need to look for a new boss */
-      UNIT_LOG(LOGLEVEL_FERRY, punit, "taking control back from [%d]", 
-              oldbossid);
-      ai_set_passenger(punit, NULL);
+    if (punit->ai.passenger > 0) {
+      struct unit *psngr = find_unit_by_id(punit->ai.passenger);
+      
+      /* If the passenger-in-charge is adjacent, we should wait for it to 
+       * board.  We will pass control to it later.
+       * FIXME: A possible side-effect: a boat will linger near a passenger 
+       * which already landed. */
+      if (!psngr 
+         || real_map_distance(punit->x, punit->y, psngr->x, psngr->y) > 1) {
+       UNIT_LOG(LOGLEVEL_FERRY, punit, 
+                "lost passenger-in-charge[%d], resetting",
+                punit->ai.passenger);
+       punit->ai.passenger = 0;
+      }
     }
 
     if (punit->ai.passenger <= 0) {
@@ -2470,11 +2471,6 @@
         }
       } unit_list_iterate_end;
       
-      if (candidate && candidate->id == oldbossid) {
-       /* The boss decided to stay put on the ferry. We aren't moving. */
-       return;
-      }
-
       if (candidate) {
         UNIT_LOG(LOGLEVEL_FERRY, punit, 
                  "appointed %s[%d] our passenger-in-charge",
@@ -2487,11 +2483,11 @@
     }
 
     if (punit->ai.passenger > 0) {
+      int bossid = punit->ai.passenger;    /* Loop prevention */
       struct unit *boss = find_unit_by_id(punit->ai.passenger);
       int id = punit->id;                  /* To check if survived */
 
       assert(boss != NULL);
-      oldbossid = punit->ai.passenger;
 
       if (unit_flag(boss, F_SETTLERS) || unit_flag(boss, F_CITIES)) {
         /* Temporary hack: settlers all go in the end, forcing them 
@@ -2506,6 +2502,11 @@
       if (!find_unit_by_id(id) || punit->moves_left <= 0) {
         return;
       }
+      if (find_unit_by_id(bossid) 
+         && same_pos(punit->x, punit->y, boss->x, boss->y)) {
+       /* The boss decided to stay put on the ferry. We aren't moving. */
+       return;
+      }
     } else {
       /* Cannot select a passenger-in-charge */
       break;

Attachment: settle4.gz
Description: GNU Zip compressed data


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