<URL: http://bugs.freeciv.org/Ticket/Display.html?id=40073 >
There's a problem with reproducing reports for 2.2 savegames. Over the past 9 months, I've spent considerable time improving the reproducibility of 2.1 (PR#39365, PR#39572, etc.) Searching for myrand() differences between 2.1 and 2.2, found only 1 more variable, added by PR#12682. It is used as both an interval *and* a "weight" multiplier for calculating AI wants. Renamed it to ai.building_wait, and save/restore it. (Yes, that's punny.) Renamed the related AI "recalc" variables to have the same prefix_* as the corresponding *_want counterparts (matching the names in the savegames). Moved the pseudo-random equation from tilespec into rand, making it easier to find, trace, and use elsewhere. Other trivial cleanup.
Index: utility/rand.c =================================================================== --- utility/rand.c (revision 14377) +++ utility/rand.c (working copy) @@ -78,8 +78,8 @@ directly representable in type RANDOM_TYPE, so we do instead: divisor = MAX_UINT32/size *************************************************************************/ -RANDOM_TYPE myrand_debug(RANDOM_TYPE size, const char *called_as, int line, - const char *file) +RANDOM_TYPE myrand_debug(RANDOM_TYPE size, const char *called_as, + int line, const char *file) { RANDOM_TYPE new_rand, divisor, max; int bailout = 0; @@ -110,8 +110,8 @@ rand_state.v[rand_state.x] = new_rand; if (++bailout > 10000) { - freelog(LOG_ERROR, "%s(%lu) = %lu bailout at line %d of %s", - called_as, (unsigned long)size, (unsigned long)new_rand, line, file); + freelog(LOG_ERROR, "%s(%lu) = %lu bailout at %s:%d", + called_as, (unsigned long)size, (unsigned long)new_rand, file, line); new_rand = 0; break; } @@ -124,8 +124,8 @@ new_rand = 0; } - freelog(LOG_RAND, "%s(%lu) = %lu at line %d of %s", - called_as, (unsigned long)size, (unsigned long)new_rand, line, file); + freelog(LOG_RAND, "%s(%lu) = %lu at %s:%d", + called_as, (unsigned long)size, (unsigned long)new_rand, file, line); return new_rand; } @@ -248,3 +248,31 @@ /* restore state: */ set_myrand_state(saved_state); } + +/************************************************************************* + Local pseudo-random function for repeatedly reaching the same result, + instead of myrand(). Primarily needed for tiles. + + Use an invariant equation for seed. + Result is 0 to (size - 1). +*************************************************************************/ +RANDOM_TYPE myrandomly_debug(RANDOM_TYPE seed, RANDOM_TYPE size, + const char *called_as, int line, const char *file) +{ + RANDOM_TYPE result; + +#define LARGE_PRIME (10007) +#define SMALL_PRIME (1009) + + /* Check for overflow and underflow */ + assert((int)(seed * LARGE_PRIME) > 0); + assert(size < SMALL_PRIME); + assert(size > 0); + result = ((seed * LARGE_PRIME) % SMALL_PRIME) % size; + + freelog(LOG_RAND, "%s(%lu,%lu) = %lu at %s:%d", + called_as, (unsigned long)seed, (unsigned long)size, + (unsigned long)result, file, line); + + return result; +} Index: utility/rand.h =================================================================== --- utility/rand.h (revision 14377) +++ utility/rand.h (working copy) @@ -26,8 +26,8 @@ bool is_init; /* initially 0 for static storage */ } RANDOM_STATE; -#define myrand(vsize) myrand_debug((vsize), "myrand", \ - __LINE__, __FILE__) +#define myrand(_size) \ + myrand_debug((_size), "myrand", __LINE__, __FILE__) RANDOM_TYPE myrand_debug(RANDOM_TYPE size, const char *called_as, int line, const char *file); @@ -40,4 +40,12 @@ void test_random1(int n); +/*===*/ + +#define myrandomly(_seed, _size) \ + myrandomly_debug((_seed), (_size), "myrandomly", __LINE__, __FILE__) + +RANDOM_TYPE myrandomly_debug(RANDOM_TYPE seed, RANDOM_TYPE size, + const char *called_as, int line, const char *file); + #endif /* FC__RAND_H */ Index: server/stdinhand.c =================================================================== --- server/stdinhand.c (revision 14377) +++ server/stdinhand.c (working copy) @@ -2357,7 +2357,6 @@ } else { pcity->debug = TRUE; CITY_LOG(LOG_TEST, pcity, "debugged"); - pcity->ai.next_recalc = 0; /* force recalc of city next turn */ } } else if (ntokens > 0 && strcmp(arg[0], "units") == 0) { int x, y; Index: server/savegame.c =================================================================== --- server/savegame.c (revision 14377) +++ server/savegame.c (working copy) @@ -2533,21 +2533,30 @@ worklist_load(file, &pcity->worklist, "player%d.c%d", plrno, i); /* FIXME: remove this when the urgency is properly recalculated. */ - pcity->ai.urgency = secfile_lookup_int_default(file, 0, - "player%d.c%d.ai.urgency", plrno, i); + pcity->ai.urgency = + secfile_lookup_int_default(file, 0, "player%d.c%d.ai.urgency", + plrno, i); /* avoid myrand recalculations on subsequent reload. */ - pcity->ai.next_recalc = secfile_lookup_int_default(file, 0, - "player%d.c%d.ai.building_turn", plrno, i); + pcity->ai.building_turn = + secfile_lookup_int_default(file, 0, "player%d.c%d.ai.building_turn", + plrno, i); + pcity->ai.building_wait = + secfile_lookup_int_default(file, BUILDING_WAIT_MINIMUM, + "player%d.c%d.ai.building_wait", + plrno, i); /* avoid myrand and expensive recalculations on subsequent reload. */ - pcity->ai.next_founder_want_recalc = secfile_lookup_int_default(file, 0, - "player%d.c%d.ai.founder_turn", plrno, i); - pcity->ai.founder_want = secfile_lookup_int_default(file, 0, - "player%d.c%d.ai.founder_want", plrno, i); - pcity->ai.founder_boat = secfile_lookup_bool_default(file, - (pcity->ai.founder_want < 0), - "player%d.c%d.ai.founder_boat", plrno, i); + pcity->ai.founder_turn = + secfile_lookup_int_default(file, 0, "player%d.c%d.ai.founder_turn", + plrno, i); + pcity->ai.founder_want = + secfile_lookup_int_default(file, 0, "player%d.c%d.ai.founder_want", + plrno, i); + pcity->ai.founder_boat = + secfile_lookup_bool_default(file, (pcity->ai.founder_want < 0), + "player%d.c%d.ai.founder_boat", + plrno, i); /* After all the set_worker_city() are done. */ tile_set_city(pcity->tile, pcity); @@ -3287,11 +3296,13 @@ "player%d.c%d.ai.urgency", plrno, i); /* avoid myrand recalculations on subsequent reload. */ - secfile_insert_int(file, pcity->ai.next_recalc, + secfile_insert_int(file, pcity->ai.building_turn, "player%d.c%d.ai.building_turn", plrno, i); + secfile_insert_int(file, pcity->ai.building_wait, + "player%d.c%d.ai.building_wait", plrno, i); /* avoid myrand and expensive recalculations on subsequent reload. */ - secfile_insert_int(file, pcity->ai.next_founder_want_recalc, + secfile_insert_int(file, pcity->ai.founder_turn, "player%d.c%d.ai.founder_turn", plrno, i); secfile_insert_int(file, pcity->ai.founder_want, "player%d.c%d.ai.founder_want", plrno, i); Index: common/city.c =================================================================== --- common/city.c (revision 14377) +++ common/city.c (working copy) @@ -2438,8 +2438,7 @@ pcity->owner = pplayer; pcity->tile = ptile; sz_strlcpy(pcity->name, name); -#if 0 - /* some zero variables not compiled, but left for searching */ +#ifdef ZERO_VARIABLES_FOR_SEARCHING memset(pcity->feel, 0, sizeof(pcity->feel)); memset(pcity->specialists, 0, sizeof(pcity->specialists)); memset(pcity->tile_output, 0, sizeof(pcity->tile_output)); @@ -2509,6 +2508,7 @@ pcity->turn_last_built = game.info.turn; pcity->changed_from = pcity->production; +#ifdef ZERO_VARIABLES_FOR_SEARCHING pcity->before_change_shields = 0; pcity->disbanded_shields = 0; pcity->caravan_shields = 0; @@ -2521,13 +2521,21 @@ pcity->server.needs_arrange = FALSE; pcity->server.vision = NULL; /* No vision. */ + pcity->ai.building_turn = 0; +#endif + pcity->ai.building_wait = BUILDING_WAIT_MINIMUM; +#ifdef ZERO_VARIABLES_FOR_SEARCHING + memset(pcity->ai.building_want, 0, sizeof(pcity->ai.building_want)); + + /* pcity->ai.choice; placeholder for searching */ pcity->ai.founder_boat = FALSE; + pcity->ai.founder_turn = 0; pcity->ai.founder_want = 0; /* calculating this is really expensive */ - pcity->ai.next_founder_want_recalc = 0; /* turns to recalc found_want */ + pcity->ai.settler_want = 0; +#endif pcity->ai.trade_want = 1; /* we always want some */ -#if 0 - /* some zero variables not compiled, but left for searching */ - memset(pcity->ai.building_want, 0, sizeof(pcity->ai.building_want)); + +#ifdef ZERO_VARIABLES_FOR_SEARCHING pcity->ai.danger = 0; pcity->ai.urgency = 0; pcity->ai.grave_danger = 0; @@ -2536,8 +2544,6 @@ pcity->ai.invasion = 0; pcity->ai.bcost = 0; pcity->ai.attack = 0; - pcity->ai.recalc_interval = 1; - pcity->ai.next_recalc = 0; memset(pcity->surplus, 0, O_COUNT * sizeof(*pcity->surplus)); memset(pcity->waste, 0, O_COUNT * sizeof(*pcity->waste)); Index: common/city.h =================================================================== --- common/city.h (revision 14377) +++ common/city.h (working copy) @@ -186,6 +186,10 @@ }; struct ai_city { + int building_turn; /* only recalculate every Nth turn */ + int building_wait; /* for weighting values */ +#define BUILDING_WAIT_MINIMUM (1) + /* building desirabilities - easiest to handle them here -- Syela */ /* The units of building_want are output * (shields/gold/luxuries) multiplied by a priority @@ -193,6 +197,8 @@ */ int building_want[B_LAST]; + struct ai_choice choice; /* to spend gold in the right place only */ + unsigned int danger; /* danger to be compared to assess_defense */ bool diplomat_threat; /* enemy diplomat or spy is near the city */ bool has_diplomat; /* this city has diplomat or spy defender */ @@ -201,8 +207,6 @@ unsigned int grave_danger; /* danger, should show positive feedback */ int wallvalue; /* how much it helps for defenders to be ground units */ - int trade_want; /* saves a zillion calculations */ - struct ai_choice choice; /* to spend gold in the right place only */ int downtown; /* distance from neighbours, for locating wonders wisely */ int distance_to_wonder_city; /* wondercity will set this for us, @@ -217,17 +221,17 @@ /* These values are for builder (F_SETTLERS) and founder (F_CITIES) units. * Negative values indicate that the city needs a boat first; * -value is the degree of want in that case. */ - int settler_want, founder_want; - int next_founder_want_recalc; /* do not recalc founder_want every turn */ - bool founder_boat; /* if the city founder will need a boat */ + bool founder_boat; /* city founder will need a boat */ + int founder_turn; /* only recalculate every Nth turn */ + int founder_want; + int settler_want; + int trade_want; /* saves a zillion calculations */ + int invasion; /* who's coming to kill us, for attack co-ordination */ int attack, bcost; /* This is also for invasion - total power and value of * all units coming to kill us. */ int worth; /* Cache city worth here, sum of all weighted incomes */ - /* Only recalc every Nth turn: */ - int recalc_interval; /* Use for weighting values calculated every Nth turn */ - int next_recalc; }; enum citizen_category { Index: ai/aicity.c =================================================================== --- ai/aicity.c (revision 14377) +++ ai/aicity.c (working copy) @@ -276,7 +276,7 @@ /* The conversion factor was determined by experiment, * and might need adjustment. */ - const int tech_want = building_want * pcity->ai.recalc_interval * 14 / 8; + const int tech_want = building_want * pcity->ai.building_wait * 14 / 8; #if 0 /* This logging is relatively expensive, * so activate it only while necessary. */ @@ -1118,15 +1118,15 @@ improvement_iterate(pimprove) { pcity->ai.building_want[improvement_index(pimprove)] = 1; } improvement_iterate_end; - } else if (pcity->ai.next_recalc <= game.info.turn) { + } else if (pcity->ai.building_turn <= game.info.turn) { /* Do a scheduled recalculation this turn */ improvement_iterate(pimprove) { pcity->ai.building_want[improvement_index(pimprove)] = 0; } improvement_iterate_end; } else if (should_force_recalc(pcity)) { /* Do an emergency recalculation this turn. */ - pcity->ai.recalc_interval = pcity->ai.next_recalc - game.info.turn; - pcity->ai.next_recalc = game.info.turn; + pcity->ai.building_wait = pcity->ai.building_turn - game.info.turn; + pcity->ai.building_turn = game.info.turn; improvement_iterate(pimprove) { pcity->ai.building_want[improvement_index(pimprove)] = 0; @@ -1150,7 +1150,7 @@ /* Don't consider impossible or redundant buildings */ pcity->ai.building_want[improvement_index(pimprove)] = 0; } else if (pplayer->ai.control - && pcity->ai.next_recalc <= game.info.turn) { + && pcity->ai.building_turn <= game.info.turn) { /* Building wants vary relatively slowly, so not worthwhile * recalculating them every turn. * We DO want to calculate (tech) wants because of buildings @@ -1275,11 +1275,11 @@ /* Reset recalc counter */ city_list_iterate(pplayer->cities, pcity) { - if (pcity->ai.next_recalc <= game.info.turn) { + if (pcity->ai.building_turn <= game.info.turn) { /* This will spread recalcs out so that no one turn end is * much longer than others */ - pcity->ai.recalc_interval = myrand(RECALC_SPEED) + RECALC_SPEED; - pcity->ai.next_recalc = game.info.turn + pcity->ai.recalc_interval; + pcity->ai.building_wait = myrand(RECALC_SPEED) + RECALC_SPEED; + pcity->ai.building_turn = game.info.turn + pcity->ai.building_wait; } } city_list_iterate_end; } @@ -1701,12 +1701,15 @@ TIMING_LOG(AIT_CITY_TERRAIN, TIMER_STOP); TIMING_LOG(AIT_CITY_SETTLERS, TIMER_START); - if (pcity->ai.next_founder_want_recalc <= game.info.turn) { + if (pcity->ai.founder_turn <= game.info.turn) { /* Will record its findings in pcity->founder_want */ contemplate_new_city(pcity); /* Avoid recalculating all the time.. */ - pcity->ai.next_founder_want_recalc = + pcity->ai.founder_turn = game.info.turn + myrand(RECALC_SPEED) + RECALC_SPEED; + } else if (pcity->debug) { + /* recalculate every turn */ + contemplate_new_city(pcity); } TIMING_LOG(AIT_CITY_SETTLERS, TIMER_STOP); ASSERT_CHOICE(pcity->ai.choice); Index: client/tilespec.c =================================================================== --- client/tilespec.c (revision 14377) +++ client/tilespec.c (working copy) @@ -38,6 +38,7 @@ #include "mem.h" #include "nation.h" #include "player.h" +#include "rand.h" #include "registry.h" #include "shared.h" #include "specialist.h" @@ -3787,13 +3788,8 @@ if (count > 0) { /* Pseudo-random reproducable algorithm to pick a sprite. */ - const int LARGE_PRIME = 10007; - const int SMALL_PRIME = 1009; + count = myrandomly(tile_index(ptile), count); - assert(count < SMALL_PRIME); - assert((int)(LARGE_PRIME * MAP_INDEX_SIZE) > 0); - count = ((tile_index(ptile) * LARGE_PRIME) % SMALL_PRIME) % count; - if (dlp->is_tall) { ox += FULL_TILE_X_OFFSET; oy += FULL_TILE_Y_OFFSET;
_______________________________________________ Freeciv-dev mailing list Freeciv-dev@gna.org https://mail.gna.org/listinfo/freeciv-dev