diff -X freeciv/diff_ignore -Nur freeciv_adm/ai/Makefile.am freeciv/ai/Makefile.am --- freeciv_adm/ai/Makefile.am Wed Oct 1 13:38:38 2003 +++ freeciv/ai/Makefile.am Sun Nov 30 00:10:15 2003 @@ -25,6 +25,8 @@ aihand.h \ ailog.c \ ailog.h \ + aisettler.c \ + aisettler.h \ aitech.c \ aitech.h \ aitools.c \ diff -X freeciv/diff_ignore -Nur freeciv_adm/ai/aidata.c freeciv/ai/aidata.c --- freeciv_adm/ai/aidata.c Sat Oct 25 22:48:59 2003 +++ freeciv/ai/aidata.c Sun Nov 30 00:10:15 2003 @@ -223,6 +223,8 @@ } } } unit_list_iterate_end; + ai->stats.mean_avail_boats = ai->stats.available_boats + - ai->stats.passengers; /*** Diplomacy ***/ @@ -294,7 +296,7 @@ ai->science_priority = 1; } else { ai->luxury_priority = 1; - ai->science_priority = TRADE_WEIGHTING; + ai->science_priority = TRADE_WEIGHTING + 10; } ai->gold_priority = TRADE_WEIGHTING; ai->happy_priority = 1; @@ -302,6 +304,9 @@ ai->angry_priority = TRADE_WEIGHTING * 3; /* grave danger */ ai->pollution_priority = POLLUTION_WEIGHTING; + ai->growth_priority = 15; /* WAG */ + + /* Goals */ ai_best_government(pplayer); } @@ -341,6 +346,26 @@ struct ai_data *ai = &aidata[pplayer->player_no]; int i; + /* The values below are WAGs that have been optimized to the + * default rules. They are not changed during the game. */ + + /* Perfection gives us an idea of how long to search for optimal + * solutions, instead of going for quick and dirty solutions that + * waste valuable time. Decrease for maps where good city placements + * are hard to find. Lower means more perfection. */ + ai->perfection = 3; + /* How much to deemphasise the potential for city growth for city + * placements. Decrease this value if large cities are important + * or planned. Increase if running strict smallpox. */ + ai->growth_potential_deemphasis = 8; + /* Percentage bonus to city locations near an ocean. */ + ai->naval_emphasis = 20; + /* Modifier for defense bonus that is applied to city location want. + * Increase this to lower emphasis on defensive positions, increase + * it if you want more emphasis on heavily defended cities. */ + ai->defense_emphasis = 4000; + + /* Government variables */ ai->govt_reeval = 0; ai->government_want = fc_calloc(game.government_count + 1, sizeof(int)); @@ -425,6 +450,17 @@ } /************************************************************************** + +**************************************************************************/ +bool ai_player_has_enough_boats(struct player *pplayer, int enough) +{ + struct ai_data *ai = &aidata[pplayer->player_no]; + + return (ai->stats.mean_avail_boats >= enough); +} + + +/************************************************************************** Deinitialize data **************************************************************************/ void ai_data_done(struct player *pplayer) diff -X freeciv/diff_ignore -Nur freeciv_adm/ai/aidata.h freeciv/ai/aidata.h --- freeciv_adm/ai/aidata.h Sat Oct 25 22:48:59 2003 +++ freeciv/ai/aidata.h Sun Nov 30 00:10:15 2003 @@ -88,12 +88,16 @@ /* This struct is used for statistical unit building, eg to ensure * that we don't build too few or too many units of a given type. */ + /* FIXME: Once the ferry system has stabilized, review the different + * ferry-related fields, some of them might be useless */ struct { int *workers; /* cities to workers on continent*/ int *cities; /* number of cities on continent */ int passengers; /* number of passengers waiting for boats */ - int boats; - int available_boats; + int boats; /* total number of boats */ + int available_boats; /* number of boats not booked or used */ + int mean_avail_boats; /* excess number of boats somehow averaged + * over several turns */ int average_production; bv_id diplomat_reservations; } stats; @@ -110,6 +114,11 @@ int unhappy_priority; int angry_priority; int pollution_priority; + int growth_priority; + int perfection; + int growth_potential_deemphasis; + int naval_emphasis; + int defense_emphasis; /* Government data */ int *government_want; @@ -138,6 +147,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); +bool ai_player_has_enough_boats(struct player *pplayer, int enough); #endif diff -X freeciv/diff_ignore -Nur freeciv_adm/ai/aisettler.c freeciv/ai/aisettler.c --- freeciv_adm/ai/aisettler.c Thu Jan 1 01:00:00 1970 +++ freeciv/ai/aisettler.c Tue Dec 2 00:15:01 2003 @@ -0,0 +1,554 @@ +/********************************************************************** + Freeciv - Copyright (C) 2003 - The Freeciv Team + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include "city.h" +#include "game.h" +#include "government.h" +#include "map.h" +#include "mem.h" +#include "log.h" +#include "packets.h" +#include "path_finding.h" +#include "pf_tools.h" +#include "player.h" +#include "support.h" +#include "timing.h" + +#include "citytools.h" +#include "gotohand.h" +#include "maphand.h" +#include "settlers.h" +#include "unittools.h" + +#include "aicity.h" +#include "aidata.h" +#include "ailog.h" +#include "aitools.h" +#include "aiunit.h" +#include "citymap.h" + +#include "aisettler.h" + +/* COMMENTS */ +/* + This code tries hard to do the right thing, including looking + into the future (wrt to government), and also doing this in a + modpack friendly manner. However, there are some pieces missing. + * A tighter integration into the city management code would + give more optimal city placements, since existing cities could + move their workers around to give a new city better placement. + Occasionally you will see cities being placed sub-optimally + because the best city center tile is taken when another tile + could have been worked instead by the city that took it. +*/ + +static struct { + int sum; + char food; + char trade; + char shield; +} cachemap[MAP_MAX_WIDTH][MAP_MAX_HEIGHT]; + +/************************************************************************** + Fill cityresult struct with useful info about the city spot. It must + contain valid x, y coordinates and total should be zero. + + We assume whatever best government we are aiming for. + + We always return valid other_x and other_y if total > 0. +**************************************************************************/ +static void cityresult_fill(struct player *pplayer, + struct ai_data *ai, + struct cityresult *result) +{ + struct city *pcity = map_get_city(result->x, result->y); + struct tile *ptile = NULL; + int sum = 0; + bool virtual_city = FALSE; + int curr_govt = pplayer->government; + + pplayer->government = ai->goal.govt.idx; + + result->best_other = 0; + result->other_x = -1; + result->other_y = -1; + result->o_x = -1; + result->o_y = -1; + result->remaining = 0; + + if (!pcity) { + pcity = create_city_virtual(pplayer, result->x, result->y, "Virtuaville"); + virtual_city = TRUE; + } + + city_map_checked_iterate(result->x, result->y, i, j, map_x, map_y) { + int reserved = citymap_read(map_x, map_y); + bool city_center = is_city_center(i, j); + + ptile = map_get_tile(map_x, map_y); + + if (reserved < 0 + || (ai_handicap(pplayer, H_MAP) + && !map_is_known(map_x, map_y, pplayer))) { + /* Tile is reserved or we can't see it */ + sum = 0; + } else if (cachemap[map_x][map_y].sum <= 0 || city_center) { + /* We cannot read city center from cache */ + + /* Food */ + result->tile[i][j].food = base_city_get_food_tile(i, j, pcity, FALSE); + + /* Shields */ + result->tile[i][j].shield =base_city_get_shields_tile(i, j, pcity, FALSE); + + /* Trade */ + result->tile[i][j].trade = base_city_get_trade_tile(i, j, pcity, FALSE); + + sum = result->tile[i][j].food * ai->food_priority + + result->tile[i][j].trade * ai->science_priority + + result->tile[i][j].shield * ai->shield_priority; + if (!city_center) { + /* Avoid crowdedness, except for city center. */ + sum -= reserved * ai->growth_priority; + } + /* Balance perfection */ + sum *= ai->perfection / 2; + if (result->tile[i][j].food >= 2) { + sum *= 2; /* we need this to grow */ + } + + if (!city_center) { + cachemap[map_x][map_y].sum = sum; + cachemap[map_x][map_y].trade = result->tile[i][j].trade; + cachemap[map_x][map_y].shield = result->tile[i][j].shield; + cachemap[map_x][map_y].food = result->tile[i][j].food; + } + } else { + sum = cachemap[map_x][map_y].sum; + result->tile[i][j].shield = cachemap[map_x][map_y].shield; + result->tile[i][j].trade = cachemap[map_x][map_y].trade; + result->tile[i][j].food = cachemap[map_x][map_y].food; + } + + /* Calculate city center and best other than city center */ + if (city_center) { + result->city_center = sum; + } else if (sum > result->best_other) { + /* First add other other to remaining */ + result->remaining += result->best_other + / ai->growth_potential_deemphasis; + /* Then make new best other */ + result->best_other = sum; + result->other_x = map_x; + result->other_y = map_y; + result->o_x = i; + result->o_y = j; + } else { + /* Save total remaining calculation, divided by crowdedness + * of the area and the emphasis placed on space for growth. */ + result->remaining += sum / ai->growth_potential_deemphasis; + } + } city_map_checked_iterate_end; + + if (virtual_city) { + /* Baseline is a size one city (city center + best extra tile). */ + result->total = result->city_center + result->best_other; + } else if (result->best_other != -1) { + /* Baseline is best extra tile only. This is why making new cities + * is so darn good. */ + result->total = result->best_other; + } else { + /* There is no available tile in this city. All is worked. */ + result->total = 0; + return; + } + + if (virtual_city) { + /* Corruption and waste of a size one city deducted. Notice that we + * don't do this if 'fulltradesize' is changed, since then we'd + * never make cities. */ + if (game.fulltradesize == 1) { + result->total -= ai->science_priority + * city_corruption(pcity, + result->tile[result->o_x][result->o_y].trade + + result->tile[2][2].trade); + } + result->total -= ai->shield_priority + * city_waste(pcity, + result->tile[result->o_x][result->o_y].shield + + result->tile[2][2].shield); + } else { + /* Deduct difference in corruption and waste for real cities. Note that it + * is possible (with notradesize) that we _gain_ value here. */ + pcity->size++; + result->total -= ai->science_priority + * (city_corruption(pcity, + result->tile[result->o_x][result->o_y].trade) + - pcity->corruption); + result->total -= ai->shield_priority + * (city_waste(pcity, + result->tile[result->o_x][result->o_y].shield) + - pcity->shield_waste); + pcity->size--; + } + result->total = MAX(0, result->total); + + pplayer->government = curr_govt; + if (virtual_city) { + remove_city_virtual(pcity); + } + + assert(result->best_other != -1 && result->other_x != -1 + && result->other_y != -1); + assert(result->city_center >= 0); + assert(result->remaining >= 0); +} + +/************************************************************************** + Calculates the desire for founding a new city at (x, y). The citymap + ensures that we do not build cities too close to each other. If we + return result->total == 0, then no place was found. +**************************************************************************/ +static void city_desirability(struct player *pplayer, struct ai_data *ai, + struct unit *punit, int x, int y, + struct cityresult *result) +{ + bool ocean_adjacent = is_terrain_near_tile(x, y, T_OCEAN); + struct city *pcity = map_get_city(x, y); + int defense_bonus; + + assert(punit && ai && pplayer && result); + + result->x = x; + result->y = y; + result->total = 0; + + if (!city_can_be_built_here(x, y, punit) + || (ai_handicap(pplayer, H_MAP) + && !map_is_known(x, y, pplayer))) { + return; + } + + /* Check if another settler has taken a spot within mindist */ + square_iterate(x, y, game.rgame.min_dist_bw_cities-1, x1, y1) { + if (citymap_is_reserved(x1, y1)) { + return; + } + } square_iterate_end; + + if (enemies_at(punit, x, y)) { + return; + } + + if (pcity && pcity->size >= game.add_to_size_limit) { + return; + } + + if (!pcity && citymap_is_reserved(x, y)) { + return; /* reserved, go away */ + } + + cityresult_fill(pplayer, ai, result); /* Burn CPU, burn! */ + if (result->total == 0) { + /* Failed to find a good spot */ + return; + } + + /* If (x, y) is an existing city, consider immigration */ + if (pcity && pcity->owner == pplayer->player_no) { + return; + } + + /*** Alright: Now consider building a new city ***/ + + /* Avoid starvation: We must have enough food to grow. */ + if (result->tile[2][2].food + + result->tile[result->o_x][result->o_y].food < 3) { + result->total = 0; + return; + } + + /* Avoid resource starvation. */ + if (result->tile[2][2].shield + + result->tile[result->o_x][result->o_y].shield == 0) { + result->total = 0; + return; + } + + /* Defense modification (as tie breaker mostly) */ + defense_bonus = get_tile_type(map_get_terrain(x, y))->defense_bonus; + if (map_has_special(x, y, S_RIVER)) { + defense_bonus += + (defense_bonus * terrain_control.river_defense_bonus) / 100; + } + result->total += (result->total * defense_bonus) / ai->defense_emphasis; + + /* Adjust for ocean adjacency, which is nice */ + if (ocean_adjacent) { + result->total += (result->total * ai->naval_emphasis) / 100; + } + + /* Add remaining points, which is our potential */ + result->total += result->remaining; + + assert(result->total >= 0); + + return; +} + +/************************************************************************** + Prime settler engine. +**************************************************************************/ +void ai_settler_init(struct player *pplayer) +{ + memset(&cachemap, 0, sizeof(cachemap)); +} + +/**************************************************************************** + Combined cost function for a land unit on a ferry, taking into account + possibilities of attacking on land. For movements sea-to-sea the cost is + collected via the extra cost call-back. +****************************************************************************/ +static int combined_land_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 */ + if (is_ocean(src_tile->terrain) + || same_pos(x, y, param->start_x, param->start_y)) { + move_cost = 0; + } else { + move_cost = PF_IMPOSSIBLE_MC; + } + } else if (src_tile->terrain == T_OCEAN) { + + /* Sea-to-Land. */ + if (!is_non_allied_unit_tile(tgt_tile, param->owner) + && !is_non_allied_city_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(tgt_tile, param->owner)) { + move_cost = src_tile->move_cost[dir]; + } else { + /* We don't attack */ + move_cost = PF_IMPOSSIBLE_MC; + } + } + + return move_cost; +} + +/**************************************************************************** + EC callback to account for the cost of sea moves by a unit carried by a + ferry. To use we need to put the ferry move_rate into the data field of + param. +****************************************************************************/ +static int sea_move(int x, int y, enum known_type known, + struct pf_parameter *param) +{ + int moverate = *(int *)(param->data); + + if (is_ocean(map_get_tile(x, y)->terrain)) { + return SINGLE_MOVE * PF_TURN_FACTOR / moverate; + } else { + return 0; + } +} + +/************************************************************************** + Find nearest and best city placement. Transparently checks if we should + add ourselves to an existing city (TODO). +**************************************************************************/ +void find_best_city_placement(struct unit *punit, struct cityresult *best) +{ + struct player *pplayer = unit_owner(punit); + struct ai_data *ai = ai_data_get(pplayer); + struct cityresult result; + int best_turn = 0; /* Which turn we found the best fit */ + const int enough = 250; /* WAG */ + struct pf_parameter param; + struct pf_map *map; + /* The nearest port to us */ + struct city *port = NULL; + /* TODO: Consider building a new boat */ + bool do_overseas = ai_player_has_enough_boats(pplayer, 1); + + assert(pplayer && pplayer->ai.control); + + /* Remove if we ever add a generic freelog anywhere */ + logdebug_suppress_warning; + + if (do_overseas && is_at_coast(punit->x, punit->y)) { + port = map_get_city(punit->x, punit->y); + } + + best->x = -1; + best->y = -1; + best->total = 0; + + /* Uh-huh, we're on board a ship... + if (map_get_tile(punit->x, punit->y)->terrain == T_OCEAN) { + UNIT_LOG(LOG_ERROR, punit, "We're trying to find a place to make a " + "city while on board a ship..."); + return; + } */ + + /* Phase 1: Consider building cities on our continent */ + + pft_fill_default_parameter(¶m); + pft_fill_unit_parameter(¶m, punit); + map = pf_create_map(¶m); + + pf_iterator(map, pos) { + int turns; + struct tile *ptile = map_get_tile(pos.x, pos.y); + + if (is_ocean(ptile->terrain)) { + continue; /* This can happen if there is a ferry near shore. */ + } + if (ptile->continent != map_get_tile(punit->x, punit->y)->continent) { + /* We have an accidential land bridge. Ignore it. It will in all + * likelihood go away next turn, or even in a few nanoseconds. */ + continue; + } + + if (do_overseas && !port && is_at_coast(pos.x, pos.y)) { + port = map_get_city(pos.x, pos.y); + } + + /* Calculate worth */ + city_desirability(pplayer, ai, punit, pos.x, pos.y, &result); + + /* Check if actually found something */ + if (result.total == 0) { + continue; + } + + /* This algorithm punishes long treks */ + turns = pos.total_MC / unit_type(punit)->move_rate; + result.total = amortize(result.total, ai->perfection * turns); + + /* Reduce want by settler cost. Easier than amortize, but still + * weeds out very small wants. ie we create a threshold here. */ + result.total -= unit_type(punit)->build_cost; + + /* Find best spot */ + if (result.total > best->total) { + *best = result; + best_turn = turns; + } + + /* Can we terminate early? We have a 'good enough' spot, and + * we don't block the establishment of a better city just one + * further step away. */ + if (best->total > enough + /* What is that supposed to mean?? -Glip */ + && turns > unit_type(punit)->move_rate /* sic */ + && best_turn < turns /*+ game.rgame.min_dist_bw_cities*/) { + break; + } + } pf_iterator_end; + + pf_destroy_map(map); + + /* Phase 2: Consider travelling to another continent */ + + if (do_overseas && port) { + Unit_Type_id boattype + = best_role_unit_for_player(pplayer, L_FERRYBOAT); + static int ferry_rate; /* FIXME: do we need static? */ + + if (boattype == U_LAST) { + /* Sea travel not possible yet */ + return; + } + + pft_fill_default_parameter(¶m); + pft_fill_unit_parameter(¶m, punit); + param.turn_mode = TM_WORST_TIME; + param.start_x = port->x; + param.start_y = port->y; + param.get_TB = no_fights_or_unknown; + param.get_EC = sea_move; + param.get_MC = combined_land_move; + ferry_rate = get_unit_type(boattype)->move_rate; + param.data = &ferry_rate; + + map = pf_create_map(¶m); + + /* TODO: Make it a function */ + pf_iterator(map, pos) { + int turns; + struct tile *ptile = map_get_tile(pos.x, pos.y); + + if (is_ocean(ptile->terrain)) { + /* Don't consider cities on sea. */ + continue; + } + + /* Calculate worth */ + city_desirability(pplayer, ai, punit, pos.x, pos.y, &result); + + /* Check if actually found something */ + if (result.total == 0) { + continue; + } + + /* This algorithm punishes long treks */ + turns = pos.turn + pos.total_EC / PF_TURN_FACTOR; + result.total = amortize(result.total, ai->perfection * turns); + + /* Reduce want by settler cost. Easier than amortize, but still + * weeds out very small wants. ie we create a threshold here. */ + result.total -= unit_type(punit)->build_cost; + + /* Find best spot */ + if (result.total > best->total) { + *best = result; + best_turn = turns; + } + + /* Can we terminate early? We have a 'good enough' spot, and + * we don't block the establishment of a better city just one + * further step away. */ + if (best->total > enough + /* What is that supposed to mean?? -Glip */ + && turns > unit_type(punit)->move_rate /* sic */ + && best_turn < turns /*+ game.rgame.min_dist_bw_cities*/) { + break; + } + } pf_iterator_end; + + pf_destroy_map(map); + } +} diff -X freeciv/diff_ignore -Nur freeciv_adm/ai/aisettler.h freeciv/ai/aisettler.h --- freeciv_adm/ai/aisettler.h Thu Jan 1 01:00:00 1970 +++ freeciv/ai/aisettler.h Sun Nov 30 00:08:49 2003 @@ -0,0 +1,34 @@ +/********************************************************************** + Freeciv - Copyright (C) 2002 - The Freeciv Team + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +***********************************************************************/ +#ifndef FC__AISETTLER_H +#define FC__AISETTLER_H + +struct citytile { + int food, shield, trade; +}; + +struct cityresult { + int x, y; /* coords */ + int total; /* total value of position */ + int other_x, other_y; /* coords to best other tile */ + int o_x, o_y; /* city-relative coords for other tile */ + int city_center; /* value of city center */ + int best_other; /* value of best other tile */ + int remaining; /* value of all other tiles */ + struct citytile tile[CITY_MAP_SIZE][CITY_MAP_SIZE]; +}; + +void find_best_city_placement(struct unit *punit, struct cityresult *best); +void ai_settler_init(struct player *pplayer); + +#endif diff -X freeciv/diff_ignore -Nur freeciv_adm/ai/aitools.c freeciv/ai/aitools.c --- freeciv_adm/ai/aitools.c Tue Dec 2 00:34:00 2003 +++ freeciv/ai/aitools.c Tue Dec 2 00:26:51 2003 @@ -15,6 +15,7 @@ #include #endif +#include #include #include @@ -48,6 +49,7 @@ #include "aidata.h" #include "ailog.h" #include "aiunit.h" +#include "citymap.h" #include "aitools.h" @@ -205,7 +207,117 @@ /* 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 */ + return SINGLE_MOVE * PF_TURN_FACTOR / 16; + } else { + return 0; + } +} + +/**************************************************************************** + Proper PF function for finding a boat. + + TODO: Lift the path off the Pf map. +****************************************************************************/ +static int find_ferry(struct unit *punit, int cap) +{ + int best_turns = FC_INFINITY; + int best_id = 0; + struct pf_parameter param; + struct pf_map *search_map; + + pft_fill_default_parameter(¶m); + pft_fill_unit_parameter(¶m, 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(¶m); + + 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 condiion 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(LOG_DEBUG, punit, + "Found a potential boat %s[%d](%d,%d)", + unit_type(aunit)->name, aunit->id, aunit->x, aunit->y); + 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. @@ -241,8 +353,7 @@ dest_x, dest_y); if (boatid <= 0) { - int bx, by; - boatid = find_boat(pplayer, &bx, &by, 2); + boatid = find_ferry(punit, 2); } ferryboat = find_unit_by_id(boatid); @@ -440,7 +551,9 @@ } if (punit->ai.ai_role == AIUNIT_BUILD_CITY) { + assert(is_normal_map_pos(goto_dest_x(punit), goto_dest_y(punit))); remove_city_from_minimap(goto_dest_x(punit), goto_dest_y(punit)); + citymap_free_city_spot(goto_dest_x(punit), goto_dest_y(punit), punit->id); } if (charge && (charge->ai.bodyguard == punit->id)) { @@ -464,9 +577,11 @@ ai_unit_new_role(bodyguard, AIUNIT_NONE, -1, -1); } - if (punit->ai.ai_role == AIUNIT_BUILD_CITY) { + /* Reserve city spot, _unless_ we want to add ourselves to a city. */ + if (punit->ai.ai_role == AIUNIT_BUILD_CITY && !map_get_city(x, y)) { assert(is_normal_map_pos(x, y)); add_city_to_minimap(x, y); + citymap_reserve_city_spot(x, y, punit->id); } } diff -X freeciv/diff_ignore -Nur freeciv_adm/ai/aiunit.c freeciv/ai/aiunit.c --- freeciv_adm/ai/aiunit.c Tue Dec 2 00:34:00 2003 +++ freeciv/ai/aiunit.c Sun Nov 30 00:58:38 2003 @@ -2325,11 +2325,20 @@ } /* Do we have the passenger-in-charge on board? */ - 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); - punit->ai.passenger = 0; + 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) { @@ -2618,8 +2627,6 @@ return; } - ai_clear_ferry(punit); - if ((unit_flag(punit, F_DIPLOMAT)) || (unit_flag(punit, F_SPY))) { ai_manage_diplomat(pplayer, punit); @@ -2646,6 +2653,7 @@ * pretend they have fuel = HP / 3 or something. */ return; } else if (is_military_unit(punit)) { + ai_clear_ferry(punit); ai_manage_military(pplayer,punit); return; } else { diff -X freeciv/diff_ignore -Nur freeciv_adm/common/aicore/Makefile.am freeciv/common/aicore/Makefile.am --- freeciv_adm/common/aicore/Makefile.am Sun Aug 31 16:32:51 2003 +++ freeciv/common/aicore/Makefile.am Sun Nov 30 00:10:15 2003 @@ -12,4 +12,6 @@ pf_tools.c \ pf_tools.h \ cm.c \ - cm.h + cm.h \ + citymap.c \ + citymap.h diff -X freeciv/diff_ignore -Nur freeciv_adm/common/aicore/citymap.c freeciv/common/aicore/citymap.c --- freeciv_adm/common/aicore/citymap.c Thu Jan 1 01:00:00 1970 +++ freeciv/common/aicore/citymap.c Sun Nov 30 00:13:05 2003 @@ -0,0 +1,164 @@ +/********************************************************************** + Freeciv - Copyright (C) 2003 - Per I. Mathisen + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include "city.h" +#include "game.h" +#ifdef DEBUG +#include "log.h" +#endif +#include "map.h" +#include "mem.h" +#include "support.h" +#include "unit.h" +#include "unittype.h" + +#include "citymap.h" + +/* CITYMAP - reserve space for cities + * + * The citymap is a large int double array that corresponds to + * the freeciv main map. For each tile, it stores three different + * and exclusive values in a single int: A positive int tells you + * how many cities that can use it, a crowdedness indicator. A + * value of zero indicates that the tile is presently unused and + * available. A negative value means that this tile is occupied + * and reserved by some city or unit. + * + * Code that uses the citymap should modify its behaviour based on + * positive values encountered, and never attempt to steal a tile + * which has a negative value. + */ + +static int citymap[MAP_MAX_WIDTH][MAP_MAX_HEIGHT]; + +#define LOG_CITYMAP LOG_DEBUG + +/************************************************************************** + Initialize citymap by reserving worked tiles and establishing the + crowdedness of (virtual) cities. +**************************************************************************/ +void citymap_turn_init(struct player *pplayer) +{ + memset(citymap, 0, sizeof(citymap)); + players_iterate(pplayer) { + city_list_iterate(pplayer->cities, pcity) { + map_city_radius_iterate(pcity->x, pcity->y, x1, y1) { + struct tile *ptile = map_get_tile(pcity->x, pcity->y); + + if (ptile->worked) { + citymap[x1][y1] = -(ptile->worked->id); + } else { + citymap[x1][y1] = citymap[x1][y1]++; + } + } map_city_radius_iterate_end; + } city_list_iterate_end; + } players_iterate_end; + unit_list_iterate(pplayer->units, punit) { + if (unit_flag(punit, F_CITIES) + && punit->ai.ai_role == AIUNIT_BUILD_CITY) { + map_city_radius_iterate(goto_dest_x(punit), goto_dest_y(punit), x1, y1) { + if (citymap[x1][y1] >= 0) { + citymap[x1][y1]++; + } + } map_city_radius_iterate_end; + citymap[goto_dest_x(punit)][goto_dest_y(punit)] = -(punit->id); + } + } unit_list_iterate_end; +} + +/************************************************************************** + Returns a positive value if within a city radius, which is 1 x number of + cities you are within the radius of, or zero or less if not. A negative + value means this tile is reserved by a city and should not be taken. +**************************************************************************/ +int citymap_read(int x, int y) +{ +#ifdef DEBUG + assert(is_normal_map_pos(x, y)); +#endif + + return citymap[x][y]; +} + +/************************************************************************** + A tile is reserved if it contains a city or unit id, or a worker is + assigned to it. +**************************************************************************/ +bool citymap_is_reserved(int x, int y) +{ + struct tile *ptile; + +#ifdef DEBUG + assert(is_normal_map_pos(x, y)); +#endif + + ptile = map_get_tile(x, y); + if (ptile->worked || ptile->city) { + return TRUE; + } + return (citymap[x][y] < 0); +} + +/************************************************************************** + This function reserves a single tile for a (possibly virtual) city with + a settler's or a city's id. Then it 'crowds' tiles that this city can + use to make them less attractive to other cities we may consider making. +**************************************************************************/ +void citymap_reserve_city_spot(int x, int y, int id) +{ +#ifdef DEBUG + assert(is_normal_map_pos(x, y)); + freelog(LOG_CITYMAP, "id %d reserving (%d, %d), was %d", + id, x, y, citymap[x][y]); + assert(citymap[x][y] >= 0); +#endif + + /* Tiles will now be "reserved" by actual workers, so free excess + * reservations. Also mark tiles for city overlapping, or + * 'crowding'. */ + map_city_radius_iterate(x, y, x1, y1) { + if (citymap[x1][y1] == -id) { + citymap[x1][y1] = 0; + } + if (citymap[x1][y1] >= 0) { + citymap[x1][y1]++; + } + } map_city_radius_iterate_end; + citymap[x][y] = -(id); +} + +/************************************************************************** + Reverse any reservations we have made in the surrounding area. +**************************************************************************/ +void citymap_free_city_spot(int x, int y, int id) +{ +#ifdef DEBUG + assert(is_normal_map_pos(x, y)); +#endif + + map_city_radius_iterate(x, y, x1, y1) { + if (citymap[x1][y1] == -(id)) { + citymap[x1][y1] = 0; + } else if (citymap[x1][y1] > 0) { + citymap[x1][y1]--; + } + } map_city_radius_iterate_end; +} diff -X freeciv/diff_ignore -Nur freeciv_adm/common/aicore/citymap.h freeciv/common/aicore/citymap.h --- freeciv_adm/common/aicore/citymap.h Thu Jan 1 01:00:00 1970 +++ freeciv/common/aicore/citymap.h Sun Nov 30 00:13:11 2003 @@ -0,0 +1,22 @@ +/********************************************************************** + Freeciv - Copyright (C) 2003 - Per I. Mathisen + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +***********************************************************************/ +#ifndef FC__CITYMAP_H +#define FC__CITYMAP_H + +void citymap_turn_init(struct player *pplayer); +int citymap_read(int x, int y); +bool citymap_is_reserved(int x, int y); +void citymap_reserve_city_spot(int x, int y, int id); +void citymap_free_city_spot(int x, int y, int id); + +#endif diff -X freeciv/diff_ignore -Nur freeciv_adm/server/cityturn.c freeciv/server/cityturn.c --- freeciv_adm/server/cityturn.c Tue Dec 2 00:34:30 2003 +++ freeciv/server/cityturn.c Sun Nov 30 00:10:15 2003 @@ -182,7 +182,7 @@ cmp.factor[TRADE] = 10; cmp.factor[GOLD] = ai->gold_priority - 11; cmp.factor[LUXURY] = ai->luxury_priority; - cmp.factor[SCIENCE] = ai->science_priority + 3; + cmp.factor[SCIENCE] = ai->science_priority - 7; cmp.happy_factor = ai->happy_priority - 1; cmp.minimal_surplus[FOOD] = 1; diff -X freeciv/diff_ignore -Nur freeciv_adm/server/settlers.c freeciv/server/settlers.c --- freeciv_adm/server/settlers.c Sat Oct 25 22:49:17 2003 +++ freeciv/server/settlers.c Sun Nov 30 00:10:15 2003 @@ -39,8 +39,10 @@ #include "aicity.h" #include "aidata.h" #include "ailog.h" +#include "aisettler.h" #include "aitools.h" #include "aiunit.h" +#include "citymap.h" #include "settlers.h" @@ -56,7 +58,6 @@ static void auto_settler_findwork(struct player *pplayer, struct unit *punit); -static void auto_settlers_player(struct player *pplayer); static bool is_already_assigned(struct unit *myunit, struct player *pplayer, int x, int y); static int city_desirability(struct player *pplayer, int x, int y); @@ -71,6 +72,11 @@ struct packet_unit_request req; struct city *pcity; + handle_unit_activity_request(punit, ACTIVITY_IDLE); + + /* Free city reservations */ + ai_unit_new_role(punit, AIUNIT_NONE, -1, -1); + req.unit_id = punit->id; sz_strlcpy(req.name, city_name_suggestion(pplayer, x, y)); handle_unit_build_city(pplayer, &req); @@ -1189,7 +1195,7 @@ } /************************************************************************** - find some work for the settler + Find some work for the settler **************************************************************************/ static void auto_settler_findwork(struct player *pplayer, struct unit *punit) { @@ -1253,7 +1259,6 @@ && same_pos(gx, gy, punit->x, punit->y)) { if (best_act == ACTIVITY_UNKNOWN) { remove_city_from_minimap(gx, gy); /* yeah, I know. -- Syela */ - handle_unit_activity_request(punit, ACTIVITY_IDLE); (void) ai_do_build_city(pplayer, punit); return; } @@ -1264,6 +1269,112 @@ } /************************************************************************** + New implementation of server/settlers.c function with same name +**************************************************************************/ +#define LOG_SETTLER LOG_NORMAL +static void auto_settler_findwork2(struct player *pplayer, struct unit *punit) +{ + struct cityresult result; + int best_impr = 0; /* best terrain improvement we can do */ + enum unit_activity best_act = ACTIVITY_IDLE; /* compat. kludge */ + int gx = -1, gy = -1; + + CHECK_UNIT(punit); + + result.total = 0; + + assert(pplayer && punit); + assert(unit_flag(punit, F_CITIES) || unit_flag(punit, F_SETTLERS)); + + /*** If we are on a city mission: Go where we should ***/ + + if (punit->ai.ai_role == AIUNIT_BUILD_CITY) { + int x = goto_dest_x(punit), y = goto_dest_y(punit), sanity = punit->id; + + /* Check that missions is still possible */ + if (!city_can_be_built_here(x, y, punit)) { + UNIT_LOG(LOG_SETTLER, punit, "city founding mission failed"); + ai_unit_new_role(punit, AIUNIT_NONE, -1, -1); + } else { + /* Go there */ + if ((!ai_gothere(pplayer, punit, x, y) && !find_unit_by_id(sanity)) + || punit->moves_left <= 0) { + return; + } + if (same_pos(punit->x, punit->y, x, y)) { + if (!ai_do_build_city(pplayer, punit)) { + UNIT_LOG(LOG_ERROR, punit, "could not make city on %s", + map_get_tile_info_text(punit->x, punit->y)); + ai_unit_new_role(punit, AIUNIT_NONE, -1, -1); + } else { + return; /* We came, we saw, we built... */ + } + } else { + UNIT_LOG(LOG_SETTLER, punit, "could not get to target this turn"); + // ai_unit_new_role(punit, AIUNIT_NONE, -1, -1); + return; + } + } + } + + CHECK_UNIT(punit); + + /*** Try find some work ***/ + + if (unit_flag(punit, F_SETTLERS)) { + best_impr = evaluate_improvements(punit, &best_act, &gx, &gy); + } + + if (unit_flag(punit, F_CITIES)) { + find_best_city_placement(punit, &result); + UNIT_LOG(LOG_SETTLER, punit, "city want %d (impr want %d)", result.total, + best_impr); + if (result.total > best_impr) { + if (map_get_city(result.x, result.y)) { + UNIT_LOG(LOG_SETTLER, punit, "immigrates to %s (%d, %d)", + map_get_city(result.x, result.y), result.x, result.y); + } else { + UNIT_LOG(LOG_SETTLER, punit, "makes city at (%d, %d)", + result.x, result.y); + } + /* Go make a city! */ + ai_unit_new_role(punit, AIUNIT_BUILD_CITY, result.x, result.y); + set_goto_dest(punit, result.x, result.y); /* TMP */ + } else if (best_impr > 0) { + UNIT_LOG(LOG_SETTLER, punit, "decided to improve terrain"); + /* Terrain improvements follows the old model, and is recalculated + * each turn. */ + ai_unit_new_role(punit, AIUNIT_AUTO_SETTLER, gx, gy); + set_goto_dest(punit, gx, gy); /* TMP */ + /* Mark the square as taken. */ + map_get_tile(gx, gy)->assigned = + map_get_tile(gx, gy)->assigned | 1<player_no; + if (do_unit_goto(punit, GOTO_MOVE_ANY, FALSE) == GR_DIED) { + return; + } + if (punit->moves_left > 0 + && same_pos(gx, gy, punit->x, punit->y)) { + UNIT_LOG(LOG_SETTLER, punit, "improving terrain"); + handle_unit_activity_request(punit, best_act); + send_unit_info(NULL, punit); + return; + } + } else { + UNIT_LOG(LOG_SETTLER, punit, "cannot find work"); + ai_unit_new_role(punit, AIUNIT_NONE, -1, -1); + return; + } + } + + /*** Recurse if we want to found a city ***/ + + if (punit->ai.ai_role == AIUNIT_BUILD_CITY) { + auto_settler_findwork2(pplayer, punit); + } +} +#undef LOG_SETTLER + +/************************************************************************** Do all tile improvement calculations and cache them for later. **************************************************************************/ void initialize_infrastructure_cache(struct city *pcity) @@ -1298,15 +1409,20 @@ } /************************************************************************** - run through all the players settlers and let those on ai.control work - automagically + Run through all the players settlers and let those on ai.control work + automagically. **************************************************************************/ -static void auto_settlers_player(struct player *pplayer) +void auto_settlers_player(struct player *pplayer) { static struct timer *t = NULL; /* alloc once, never free */ t = renew_timer_start(t, TIMER_CPU, TIMER_DEBUG); + if (pplayer->ai.control && ai_handicap(pplayer, H_EXPERIMENTAL)) { + /* Set up our city map. */ + citymap_turn_init(pplayer); + } + city_list_iterate(pplayer->cities, pcity) initialize_infrastructure_cache(pcity); /* saves oodles of time -- Syela */ city_list_iterate_end; @@ -1328,9 +1444,12 @@ handle_unit_activity_request(punit, ACTIVITY_IDLE); } if (punit->activity == ACTIVITY_IDLE) { - auto_settler_findwork(pplayer, punit); + if (pplayer->ai.control && ai_handicap(pplayer, H_EXPERIMENTAL)) { + auto_settler_findwork2(pplayer, punit); + } else { + auto_settler_findwork(pplayer, punit); + } } - freelog(LOG_DEBUG, "Has been processed."); } } unit_list_iterate_end; @@ -1449,7 +1568,6 @@ */ } - /************************************************************************** Recalculate enemies[] table **************************************************************************/ @@ -1465,9 +1583,9 @@ } /************************************************************************** - Do the auto_settler stuff for all the players. + Initialize autosettler code. **************************************************************************/ -void auto_settlers(void) +void auto_settlers_init(void) { assign_settlers(); assign_territory(); @@ -1478,8 +1596,8 @@ } /************************************************************************** -used to use old crappy formulas for settler want, but now using actual -want! + Return want for city settler. Note that we rely here on the fact that + ai_settler_init() has been run while doing autosettlers. **************************************************************************/ void contemplate_new_city(struct city *pcity) { @@ -1499,7 +1617,15 @@ virtualunit = create_unit_virtual(pplayer, pcity, unit_type, FALSE); virtualunit->x = pcity->x; virtualunit->y = pcity->y; - want = evaluate_city_building(virtualunit, &gx, &gy, &ferryboat); + if (pplayer->ai.control && ai_handicap(pplayer, H_EXPERIMENTAL)) { + struct cityresult result; + + find_best_city_placement(virtualunit, &result); + pcity->ai.founder_want = result.total; + return; + } else { + want = evaluate_city_building(virtualunit, &gx, &gy, &ferryboat); + } free(virtualunit); unit_list_iterate(pplayer->units, qpass) { diff -X freeciv/diff_ignore -Nur freeciv_adm/server/settlers.h freeciv/server/settlers.h --- freeciv_adm/server/settlers.h Wed Oct 1 13:50:36 2003 +++ freeciv/server/settlers.h Sun Nov 30 00:10:15 2003 @@ -19,7 +19,8 @@ struct unit; struct city; -void auto_settlers(void); +void auto_settlers_init(void); +void auto_settlers_player(struct player *pplayer); int find_boat(struct player *pplayer, int *x, int *y, int cap); #define MORT 24 diff -X freeciv/diff_ignore -Nur freeciv_adm/server/srv_main.c freeciv/server/srv_main.c --- freeciv_adm/server/srv_main.c Tue Dec 2 00:34:31 2003 +++ freeciv/server/srv_main.c Sun Nov 30 00:10:15 2003 @@ -96,6 +96,8 @@ #include "advmilitary.h" #include "aidata.h" #include "aihand.h" +#include "aisettler.h" +#include "citymap.h" #include "srv_main.h" @@ -490,8 +492,13 @@ nocity_send = TRUE; /* AI end of turn activities */ + auto_settlers_init(); players_iterate(pplayer) { if (pplayer->ai.control) { + ai_settler_init(pplayer); + } + auto_settlers_player(pplayer); + if (pplayer->ai.control) { ai_do_last_activities(pplayer); } } players_iterate_end; @@ -1531,8 +1538,6 @@ summon_barbarians(); /* wild guess really, no idea where to put it, but I want to give them chance to move their units */ /* Moved this to after the human turn for efficiency -- Syela */ - freelog(LOG_DEBUG, "Autosettlers"); - auto_settlers(); freelog(LOG_DEBUG, "Auto-Attack phase"); auto_attack(); freelog(LOG_DEBUG, "Endturn");