Complete.Org: Mailing Lists: Archives: freeciv-dev: October 2004:
[Freeciv-Dev] (PR#10511) infinite loop in creating starting positions
Home

[Freeciv-Dev] (PR#10511) infinite loop in creating starting positions

[Top] [All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index] [Thread Index]
To: undisclosed-recipients: ;
Subject: [Freeciv-Dev] (PR#10511) infinite loop in creating starting positions
From: "Jason Short" <jdorje@xxxxxxxxxxxxxxxxxxxxx>
Date: Wed, 13 Oct 2004 11:49:43 -0700
Reply-to: rt@xxxxxxxxxxx

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

The infinite loop problem has been reported more often.  If you have a 
high aifill (30) with a small map size (1) and low landmass (15), there 
isn't enough room to place starting positions.

This patch improves that.  I'd be rather surprised if starting position 
placement ever fails after this.  Of course you may end up with a game 
that's not playable.  But at least you'll have learned your lesson, and 
won't blame the game.  It should be applied to both branches.

Changes:

- When decreasing "dist", restart the placement from scratch.  This is 
needed for fairness (the current method isn't fair), particularly when 
we start dropping restrictions.  This change necessitates a 
data->starters value.

- When decreasing "dist", it may be decreased to or below 0.  When this 
happens the minimum distance doesn't actually drop any more, but instead 
we start dropping restrictions.  Note there is a special case added to 
prevent two starters from being on the same tile (otherwise with dist<=0 
this would be possible).

* First we allow starting positions to be placed on huts.  This is easy 
enough; we just remove the huts afterwards.  Unfortunately huts are 
pretty rare so it doesn't help in most games.

* Then we allow starting positions to be placed on any terrain. 
Normally only grassland and plains are eligible; when the restriction is 
dropped any non-ocean terrain is eligible.  This may include arctic, 
except that polar continents generally aren't eligible if you have 
separatepoles (see below).  In every case I've tested, dropping this 
restriction allows placement to complete.

* Then we allow starting positions to be on any continent.  Normally the 
number of starters per continent is set at the beginning, but when this 
restriction is dropped players can be put on any continent.  Of course 
this allows all land tiles to be eligible starters, and there are at 
least 150 land tiles (landmass 15% with size 1000 tiles), so it should 
never fail.  But the game will be unplayable because players will be all 
over the poles.

For each dropped restriction an error message is sent.  If none of the 
restriction-droppings helps we'll still end up with an error.  However I 
changed this so it doesn't dump core (this is a user error not a code 
error) but just does an exit().

Note several translatable strings are changed by this patch.

I know marcelo had some code to improve starting positions.  But those 
patches are 10x larger than this and probably shouldn't be committed to 
the stable branch.  This patch addresses only this one very serious problem.

jason

? newtiles
Index: server/generator/startpos.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/server/generator/startpos.c,v
retrieving revision 1.3
diff -u -r1.3 startpos.c
--- server/generator/startpos.c 29 Sep 2004 02:24:24 -0000      1.3
+++ server/generator/startpos.c 13 Oct 2004 18:30:38 -0000
@@ -20,6 +20,7 @@
 #include "map.h"
 
 #include "maphand.h"
+#include "plrhand.h"
 
 #include "mapgen_topology.h"
 #include "startpos.h"
@@ -174,7 +175,17 @@
 
 struct start_filter_data {
   int count; /* Number of existing start positions. */
-  int dist; /* Minimum distance between starting positions. */
+
+  /* Minimum distance between starting positions.  If 0 or negative, then
+   * we start dropping restrictions on the starting positions. */
+#define NO_HUT_CHECK 0
+#define NO_TERRAIN_CHECK -1
+#define NO_CONTINENT_CHECK -2
+#define STARTPOS_ERROR -3
+  int dist;
+
+  /* Number of starting positions on each continent. */
+  int *starters;
 };
 
 /**************************************************************************
@@ -194,22 +205,24 @@
   const struct start_filter_data *data = dataptr;
   int i;
   Terrain_type_id t = map_get_terrain(ptile);
+  int continent = map_get_continent(ptile);
 
   if (is_ocean(map_get_terrain(ptile))) {
     return FALSE;
   }
 
-  if (islands[(int)map_get_continent(ptile)].starters == 0) {
+  if (data->dist > NO_CONTINENT_CHECK
+      && data->starters[continent] >= islands[continent].starters) {
     return FALSE;
   }
 
   /* Only start on certain terrain types. */
-  if (!terrain_has_flag(t, TER_STARTER)) {
+  if (data->dist > NO_TERRAIN_CHECK && !terrain_has_flag(t, TER_STARTER)) {
     return FALSE;
   }
   
   /* Don't start on a hut. */
-  if (map_has_special(ptile, S_HUT)) {
+  if (data->dist > NO_HUT_CHECK && map_has_special(ptile, S_HUT)) {
     return FALSE;
   }
   
@@ -219,6 +232,11 @@
   for (i = 0; i < data->count; i++) {
     struct tile *tile1 = map.start_positions[i].tile;
 
+    if (tile1 == ptile) {
+      /* Never allow two starting positions on the same tile. */
+      return FALSE;
+    }
+
     if (map_get_continent(ptile) == map_get_continent(tile1)
        && real_map_distance(ptile, tile1) < data->dist) {
       return FALSE;
@@ -237,6 +255,7 @@
   struct tile *ptile;
   int k, sum;
   struct start_filter_data data;
+  int starters[map.num_continents];
   
   if (!islands) {
     /* Isle data is already setup for generators 2, 3, and 4. */
@@ -255,12 +274,15 @@
   }
   assert(game.nplayers <= data.count + sum);
 
+  data.starters = starters;
+  memset(starters, 0, sizeof(starters));
+
   map.start_positions = fc_realloc(map.start_positions,
                                   game.nplayers
                                   * sizeof(*map.start_positions));
   while (data.count < game.nplayers) {
     if ((ptile = rand_map_pos_filtered(&data, is_valid_start_pos))) {
-      islands[(int)map_get_continent(ptile)].starters--;
+      data.starters[(int)map_get_continent(ptile)]++;
       map.start_positions[data.count].tile = ptile;
       map.start_positions[data.count].nation = NO_NATION_SELECTED;
       freelog(LOG_DEBUG, "Adding %d,%d as starting position %d.",
@@ -268,17 +290,55 @@
       data.count++;
 
     } else {
-      
       data.dist--;
-      if (data.dist == 0) {
-       die(_("The server appears to have gotten into an infinite loop "
-             "in the allocation of starting positions, and will abort.\n"
-             "Maybe the numbers of players/ia is too much for this map.\n"
-             "Please report this bug at %s."), WEBSITE_URL);
+
+      /* Reset everything so we start from scratch. */
+      data.count = 0;
+      memset(starters, 0, sizeof(starters));
+
+      if (data.dist == NO_HUT_CHECK) {
+       const char *msg = _("There are too many players in the game, so "
+                           "players may be placed on tiles with huts.");
+
+       notify_player_ex(NULL, NULL, E_NOEVENT, "%s", msg);
+       freelog(LOG_ERROR, "%s", msg);
+      }
+
+      if (data.dist == NO_CONTINENT_CHECK) {
+       const char *msg = _("There are too many players in the game, so "
+                           "players may be placed on any continent "
+                           "(including poles).");
+
+       notify_player_ex(NULL, NULL, E_NOEVENT, "%s", msg);
+       freelog(LOG_ERROR, "%s", msg);
+      }
+
+      if (data.dist == NO_TERRAIN_CHECK) {
+       const char *msg = _("There are too many players in the game, so "
+                           "players may be placed on any terrain.");
+
+       notify_player_ex(NULL, NULL, E_NOEVENT, "%s", msg);
+       freelog(LOG_ERROR, "%s", msg);
+      }
+
+      if (data.dist == STARTPOS_ERROR) {
+       const char *msg = _("The game cannot place enough starting "
+                           "positions because the number of players/aifill "
+                           "is too high.");
+
+       notify_player_ex(NULL, NULL, E_NOEVENT, "%s", msg);
+       freelog(LOG_FATAL, "%s", msg);
+       /* TODO: should just return to pregame. */
+       exit(EXIT_FAILURE);
       }
     }
   }
   map.num_start_positions = game.nplayers;
+  for (k = 0; k < game.nplayers; k++) {
+    /* In rare cases starting positions may be placed on huts.  See
+     * NO_HUT_CHECK above. */
+    map_clear_special(map.start_positions[k].tile, S_HUT);
+  }
 
   free(islands);
   islands = NULL;

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