← Back to team overview

widelands-dev team mailing list archive

Re: [Merge] lp:~widelands-dev/widelands/ai_persistent_data into lp:widelands

 

I can't say anything about how good the heuristics are, but I have added some code style comments / ideas.

Diff comments:

> === modified file 'src/ai/ai_help_structs.h'
> --- src/ai/ai_help_structs.h	2015-08-19 19:29:56 +0000
> +++ src/ai/ai_help_structs.h	2015-10-05 17:29:41 +0000
> @@ -303,7 +313,13 @@
>  	     military_unstationed_(0),
>  	     is_portspace_(false),
>  	     port_nearby_(false),
> -	     portspace_nearby_(Widelands::ExtendedBool::kUnset) {
> +	     portspace_nearby_(Widelands::ExtendedBool::kUnset),
> +	     max_buildcap_nearby_(0),
> +	     last_resources_check_time_(0) {
> +	}
> +
> +	int32_t own_military_sites_nearby_(){

Add a space before {

> +		return military_stationed_ + military_unstationed_;
>  	}
>  };
>  
> 
> === modified file 'src/ai/defaultai.cc'
> --- src/ai/defaultai.cc	2015-08-28 19:09:59 +0000
> +++ src/ai/defaultai.cc	2015-10-05 17:29:41 +0000
> @@ -690,8 +733,98 @@
>  	taskDue[ScheduleTasks::kUnbuildableFCheck] = 1000;
>  	taskDue[ScheduleTasks::kWareReview] = 15 * 60 * 1000;
>  	taskDue[ScheduleTasks::kPrintStats] = 30 * 60 * 1000;
> -	taskDue[ScheduleTasks::kCountMilitaryVacant] = 10 * 60 * 1000;
> +	taskDue[ScheduleTasks::kCountMilitaryVacant] = 1 * 60 * 1000;
>  	taskDue[ScheduleTasks::kCheckEnemySites] = 10 * 60 * 1000;
> +
> +	// Here the AI persistent data either exists - then they are read
> +	// or does not exist, then they are created and saved
> +	const int16_t persistent_data_exists_ = player_->get_ai_data_int16(kPersistentData);
> +
> +	// 0 implies no saved data exits yet
> +	if (persistent_data_exists_ == 0) {
> +		player_->set_ai_data(static_cast<int16_t>(1), kPersistentData);
> +
> +		// these random values to make some small differences between players
> +		// they are immediately saved
> +		// these values are never changed
> +		ai_personality_military_loneliness_ = std::rand() % 5 * 30 - 60;
> +		player_->set_ai_data(ai_personality_military_loneliness_, kMilitLoneliness);
> +
> +		ai_personality_attack_margin_ = std::max(std::rand() % 20 - 5, 0);
> +		player_->set_ai_data(ai_personality_attack_margin_, kAttackMargin);
> +
> +		ai_personality_wood_difference_ = std::rand() % 40 - 20;
> +		player_->set_ai_data(ai_personality_wood_difference_, kWoodDiff);
> +
> +		ai_productionsites_ratio_ = std::rand() % 5 + 7;
> +		player_->set_ai_data(ai_productionsites_ratio_, kProdRatio);
> +
> +		ai_personality_early_militarysites = std::rand() % 20 + 20;
> +		player_->set_ai_data(ai_personality_early_militarysites, kEarlyMilitary);
> +
> +	} else {
> +		log (" %d: restoring saved AI data...\n", player_number());
> +
> +
> +		// Restoring data and doing some basic check
> +		ai_personality_military_loneliness_ = player_->get_ai_data_int16(kMilitLoneliness);
> +		if (ai_personality_military_loneliness_ < -60 || ai_personality_military_loneliness_ > 60) {
> +			log(" %d: unexpected value for ai_personality_military_loneliness_: %d\n",
> +			player_number(), ai_personality_military_loneliness_);
> +		}
> +
> +		ai_personality_attack_margin_ = player_->get_ai_data_uint32(kAttackMargin);
> +		if (ai_personality_attack_margin_ > 15) {
> +			log(" %d: unexpected value for ai_personality_attack_margin_: %d\n",
> +			player_number(), ai_personality_attack_margin_);
> +		}
> +
> +		ai_personality_wood_difference_ = player_->get_ai_data_int32(kWoodDiff);
> +		if (ai_personality_wood_difference_ < -20 || ai_personality_wood_difference_ > 19) {
> +			log(" %d: unexpected value for ai_personality_wood_difference_: %d\n",
> +			player_number(), ai_personality_wood_difference_);
> +		}
> +
> +		ai_productionsites_ratio_ = player_->get_ai_data_uint32(kProdRatio);
> +		if (ai_productionsites_ratio_< 5 || ai_productionsites_ratio_ > 15) {
> +			log(" %d: unexpected value for ai_productionsites_ratio_: %d\n",
> +			player_number(), ai_productionsites_ratio_);
> +		}
> +
> +		ai_personality_early_militarysites = player_->get_ai_data_uint32(kEarlyMilitary);
> +		if (ai_personality_early_militarysites< 20 || ai_personality_early_militarysites > 40) {
> +			log(" %d: unexpected value for ai_personality_early_msites: %d\n",
> +			player_number(), ai_personality_early_militarysites);
> +		}
> +

We have a lot of similar repeating code here. Can we shift this into a generic function somehow? Since we have different data types, a template function might do the trick. Something like:

ai_personality_early_militarysites = player_->get_ai_data<uint32>(kEarlyMilitary)

Or just pick an int type that is big enough for all of them - we don't have that many values, so saving memory size shouldn't be an issue.

Then we could have a generic function that takes the value with the upper and lower bounds.

We also don't get the initial random values here if the boundaries aren't met - maybe instead of
  if (persistent_data_exists_ == 0)
initialize anyway, and then replace the "else" with "if (persistent_data_exists_ != 0)"?

You could look at http://bazaar.launchpad.net/~widelands-dev/widelands/trunk/view/head:/src/graphic/text/font_set.cc#L152 for inspiration, which SirVer helped me create.

> +		// Now some runtime values that are generated and saved during course of game
> +		last_attack_time_ = player_->get_ai_data_uint32(kLastAttack);
> +
> +		last_attacked_player_ = static_cast<uint16_t>(player_->get_ai_data_int16(kAttacker));
> +		if (last_attacked_player_ > 8) {
> +			log(" %d: unexpected value for last_attacked_player_: %d\n",
> +			player_number(), ai_personality_attack_margin_);
> +		}
> +
> +		colony_scan_area_ = player_->get_ai_data_uint32(kColonyScan);
> +		if (colony_scan_area_ > 50) {
> +			log(" %d: unexpected value for colony_scan_area_: %d\n",
> +			player_number(), colony_scan_area_);
> +		}
> +
> +		trees_around_cutters_ = player_->get_ai_data_uint32(kTreesAround);
> +
> +		least_military_score_ = player_->get_ai_data_int32(kLeastMilit);
> +		if (least_military_score_< 0 || least_military_score_ > 1000) {
> +			log(" %d: unexpected value for least_military_score_: %d\n",
> +			player_number(), least_military_score_);
> +		}
> +		target_military_score_ = player_->get_ai_data_int32(kTargetMilit);
> +		if (target_military_score_< least_military_score_ || target_military_score_ > 1000) {
> +			log(" %d: unexpected value for target_military_score_: %d\n",
> +			player_number(), target_military_score_);
> +		}
> +	}
>  }
>  
>  /**
> @@ -841,8 +974,18 @@
>  		}
>  	}
>  
> +
> +	// are we going to count resources now?
> +	bool resource_count_now = false;
> +	// Testing in first 10 seconds or if last testing was more then 60 sec ago
> +	if (field.last_resources_check_time_ < 10000 ||
> +		field.last_resources_check_time_ - gametime > 60 * 1000) {
> +			resource_count_now = true;
> +			field.last_resources_check_time_ = gametime;
> +	}
> +
>  	// to save some CPU
> -	if ((mines_.size() > 8 && gametime % 3 > 0) || field.unowned_land_nearby_ == 0) {
> +	if (mines_.size() > 8 && !resource_count_now) {
>  		field.unowned_mines_pots_nearby_ = 0;

I think there is a typo in the variable name. Should this be called unowned_mine_spots_nearby_?

>  	} else {
>  		uint32_t close_mines =
> @@ -1278,11 +1429,63 @@
>  		needed_spots = 1300 + (productionsites.size() - 200) * 20;
>  	}
>  
> +	// *_military_scores are used as minimal score for a new military building
> +	// to be built. As AI does not traverse all building fields at once, these thresholds
> +	// are gradually going down until it finds a field&building that are above threshold
> +	// and this combination is used...
> +	// least_military_score_ is hardlimit, floating very slowly
> +	// target_military_score_ is always set according to latest best building (using the same
> +	// score) and quickly falling down until it reaches the least_military_score_
> +	// this one (=target_military_score_) is actually used to decide if building&field is allowed
> +	// candidate
> +	// least_military_score_ is allowed to get bellow 100 only if there is no military site in construction
> +	// right now in order to (try to) avoid expansion lockup
> +
> +	// this is just helpers to improve readability of code
> +	const bool too_many_ms_constructionsites =
> +		((msites_in_constr() * msites_in_constr()) > militarysites.size());

How about:

((pow(msites_in_constr(), 2)) > militarysites.size());

function pow is included in <cmath>

> +	const bool too_many_vacant_mil =
> +		(vacant_mil_positions_ * 3 > static_cast<int32_t>(militarysites.size()));
> +	const int32_t kUpperLimit = 275;
> +	const int32_t kBottomLimit = 40; // to prevent too dense militarysites
> +	// modifying least_military_score_, down if more military sites are needed and vice versa
> +	if (too_many_ms_constructionsites || too_many_vacant_mil) {
> +		if (least_military_score_ < kUpperLimit) { //no sense to let it grow too hight
> +			least_military_score_ += 2;
> +		}
> +	} else {
> +		least_military_score_ -= 4;
> +		// do not get bellow 100 if there is at least one ms in construction
> +		if ((msites_in_constr() > 0 || too_many_vacant_mil) && least_military_score_ < kBottomLimit) {
> +			least_military_score_ = kBottomLimit;
> +		}
> +		if (least_military_score_ < 0) {
> +			least_military_score_ = 0;
> +		}
> +	}
> +
> +	// This is effective score, falling down very quickly
> +	if (target_military_score_ > 350) {
> +		target_military_score_ = 8 * target_military_score_ / 10;
> +	} else {
> +		target_military_score_ = 9 * target_military_score_ / 10;
> +	}
> +	if (target_military_score_ < least_military_score_) {
> +		target_military_score_ = least_military_score_;
> +	}
> +	player_->set_ai_data(target_military_score_, kTargetMilit);
> +	player_->set_ai_data(least_military_score_, kLeastMilit);
> +
> +
>  	// there are many reasons why to stop building production buildings
>  	// (note there are numerous exceptions)
>  	// 1. to not have too many constructionsites
> -	if (num_prod_constructionsites > productionsites.size() / 7 + 2) {
> -		new_buildings_stop_ = true;
> +	if ((num_prod_constructionsites + mines_in_constr())
> +		>
> +		(productionsites.size() + mines_built())
> +		/
> +		ai_productionsites_ratio_ + 2) {
> +			new_buildings_stop_ = true;
>  	}
>  	// 2. to not exhaust all free spots
>  	if (spots_ < needed_spots) {
> @@ -3531,61 +3577,97 @@
>  		return true;
>  	}
>  
> -	// doing nothing when failed count is too low
> -	if (site.no_resources_count < 4) {
> +	// to avoid problems with uint underflow, we discourage considerations below
> +	if (gametime < 10 * 60 * 1000) {
>  		return false;
>  	}
>  
> -	// dismantling when the failed count is too high
> -	if (site.no_resources_count > 12) {
> -		flags_to_be_removed.push_back(site.site->base_flag().get_position());
> -		if (connected_to_wh) {
> -			game().send_player_dismantle(*site.site);
> -		} else {
> -			game().send_player_bulldoze(*site.site);
> -		}
> -		site.bo->construction_decision_time_ = gametime;
> -		return true;
> -	}
> -
> -	// is output needed (compare stocked materials vs target values)
> -	check_building_necessity(*site.bo);
> -
> -	// if we have enough of mined materials on stock - do not upgrade (yet)
> -	if (site.bo->output_needed_ == ExtendedBool::kFalse) {
> +	// if mine is working, doing nothing
> +	if (site.no_resources_since_ > gametime - 5 * 60 * 1000) {
>  		return false;
>  	}
>  
>  	// Check whether building is enhanceable. If yes consider an upgrade.
>  	const BuildingIndex enhancement = site.site->descr().enhancement();
> -
> -	// if no enhancement is possible
> -	if (enhancement == INVALID_INDEX) {
> -		// will be destroyed when no_resource_count will overflow
> +	bool has_upgrade = false;
> +	if (enhancement != INVALID_INDEX) {
> +		if (player_->is_building_type_allowed(enhancement)) {
> +			has_upgrade = true;
> +		}
> +	}
> +
> +	// every type of mine has minimal number of mines that are to be preserved
> +	// (we will not dismantle even if there are no mineable resources left for this level of mine
> +	// and output is not needed)
> +	bool forcing_upgrade = false;
> +	const uint16_t minimal_mines_count = (site.bo->built_mat_producer_)?2:1;

Add some spaces: ) ? 2 : 1

> +	if (has_upgrade &&
> +		mines_per_type[site.bo->mines_].total_count() <= minimal_mines_count) {
> +		forcing_upgrade = true;
> +	}
> +
> +
> +	// dismantling a mine
> +	if (!has_upgrade) { // if no upgrade, now
> +		flags_to_be_removed.push_back(site.site->base_flag().get_position());
> +		if (connected_to_wh) {
> +			game().send_player_dismantle(*site.site);
> +		} else {
> +			game().send_player_bulldoze(*site.site);
> +		}
> +		site.bo->construction_decision_time_ = gametime;
> +		return true;
> +	// if having an upgrade, after half hour
> +	} else if (site.no_resources_since_ < gametime - 30 * 60 * 1000 && !forcing_upgrade) {
> +		flags_to_be_removed.push_back(site.site->base_flag().get_position());
> +		if (connected_to_wh) {
> +			game().send_player_dismantle(*site.site);
> +		} else {
> +			game().send_player_bulldoze(*site.site);
> +		}
> +		site.bo->construction_decision_time_ = gametime;
> +		return true;
> +	}
> +
> +	// if we are here, a mine is upgradeable
> +
> +	// if we don't need the output, and we have other buildings of the same type, the function returns
> +	// and building will be dismantled later.
> +	check_building_necessity(*site.bo, PerfEvaluation::kForDismantle, gametime);
> +	if (site.bo->max_needed_preciousness_ == 0 && !forcing_upgrade) {
>  		return false;
>  	}
>  
> +	// again similarly, no upgrading if not connected, other parts of AI will dismantle it,
> +	// or connect to a warehouse
>  	if (!connected_to_wh) {
> -		// no enhancement possible
> +		return false;
> +	}
> +
> +	// don't upgrade now if other mines of the same type are right now in construction
> +	if (mines_per_type[site.bo->mines_].in_construction > 0) {
>  		return false;
>  	}
>  
>  	bool changed = false;
> -	if (player_->is_building_type_allowed(enhancement)) {
> -		// first exclude possibility there are enhancements in construction or unoccupied_
> -		const BuildingDescr& bld = *tribe_->get_building_descr(enhancement);
> -		BuildingObserver& en_bo = get_building_observer(bld.name().c_str());
> -
> -		// if it is too soon for enhancement and making sure there are no unoccupied mines
> -		if (gametime - en_bo.construction_decision_time_ >= kBuildingMinInterval &&
> -		    en_bo.unoccupied_ + en_bo.cnt_under_construction_ == 0) {
> -
> -			// now verify that there are enough workers
> -			if (site.site->has_workers(enhancement, game())) {  // enhancing
> -				game().send_player_enhance_building(*site.site, enhancement);
> -				en_bo.construction_decision_time_ = gametime;
> -				changed = true;
> -			}
> +
> +	// first exclude possibility there are enhancements in construction or unoccupied_count_
> +	const BuildingDescr& bld = *tribe_->get_building_descr(enhancement);
> +	BuildingObserver& en_bo = get_building_observer(bld.name().c_str());
> +
> +	// if it is too soon for enhancement
> +	if (gametime - en_bo.construction_decision_time_ >= kBuildingMinInterval) {
> +		// now verify that there are enough workers
> +		if (site.site->has_workers(enhancement, game())) {  // enhancing

For the future: Do we have this check for other productionsites as well? e.g. Axfactory.

> +			game().send_player_enhance_building(*site.site, enhancement);
> +			if (site.bo->max_needed_preciousness_ == 0) {
> +				assert (mines_per_type[site.bo->mines_].total_count() <= minimal_mines_count);
> +			}
> +			if (mines_per_type[site.bo->mines_].total_count() > minimal_mines_count) {
> +				assert(site.bo->max_needed_preciousness_ > 0);
> +			}
> +			en_bo.construction_decision_time_ = gametime;
> +			changed = true;
>  		}
>  	}
>  
> @@ -3624,6 +3712,9 @@
>  		target = std::max<uint16_t>(target, 1);
>  
>  		uint16_t preciousness = wares.at(bo.outputs_.at(m)).preciousness_;
> +		if (preciousness < 1) { // it seems there are wares with 0 preciousness
> +			preciousness = 1; // (no entry in conf files?). But we need positive value here

Yes, that would be no entry in conf files. Better leave this check as it is here, because the conf will change a bit with the one_tribe project - maybe put it on your todo-list to double-check when we have merged that branch?

How about adding a log output here, so we can track them down?

> +		}
>  
>  		if (get_warehoused_stock(wt) < target) {
>  			if (bo.max_needed_preciousness_ < preciousness) {
> @@ -3631,16 +3722,253 @@
>  			}
>  		}
>  
> -		if (bo.max_preciousness < preciousness) {
> -			bo.max_preciousness = preciousness;
> +		if (bo.max_preciousness_ < preciousness) {
> +			bo.max_preciousness_ = preciousness;
>  		}
>  	}
>  
> -	// here we decide if the building is needed
> +	if (!bo.outputs_.empty()) {
> +		assert (bo.max_preciousness_ > 0);
> +	}
> +
> +	// positive max_needed_preciousness_ says a building type is needed
> +	// here we increase of reset the counter
> +	// the counter is added to score when considering new building
>  	if (bo.max_needed_preciousness_ > 0) {
> -		bo.output_needed_ = ExtendedBool::kTrue;
> -	} else {
> -		bo.output_needed_ = ExtendedBool::kFalse;
> +		bo.new_building_overdue_ += 1;
> +	} else {
> +		bo.new_building_overdue_ = 0;
> +	}
> +
> +	// This flag is to be used when buildig is forced. AI will not build another building when
> +	// a substitution exists. F.e. mines or pairs like tavern-inn
> +	// To skip unnecessary calculation, we calculate this only if we have 0 count of the buildings
> +	bool has_substitution_building = false;
> +	if (bo.total_count() == 0 && bo.upgrade_substitutes_ && bo.type == BuildingObserver::PRODUCTIONSITE) {
> +		const BuildingIndex enhancement = bo.desc->enhancement();
> +		BuildingObserver& en_bo
> +			= get_building_observer(tribe_->get_building_descr(enhancement)->name().c_str());
> +		if (en_bo.total_count() > 0) {
> +			has_substitution_building = true;
> +		}
> +	}
> +	if (bo.total_count() == 0 && bo.type == BuildingObserver::MINE) {
> +		if (mines_per_type[bo.mines_].in_construction + mines_per_type[bo.mines_].finished > 0) {
> +			has_substitution_building = true;
> +		}
> +	}
> +
> +	// This function is going to say if a building is needed. But there is a 'new_buildings_stop_'
> +	// flag that should be obeyed, but sometimes can be ignored.
> +	// So we can have two types of needed: kNeeded and KNeededPending
> +	// below we define which one will be returned if building is 'needed'
> +	BuildingNecessity needed_type = BuildingNecessity::kNeeded;
> +	if (new_buildings_stop_) {
> +		needed_type = BuildingNecessity::kNeededPending;
> +		if (gametime < 15 * 60 * 1000) {
> +			; // no exemption here within first 15 minutes
> +		} else if (gametime < 25 * 60 * 1000) { // exemption after 15 minutes - 1 building allowed
> +
> +			if (bo.type == BuildingObserver::MINE) {
> +				if (mines_per_type[bo.mines_].in_construction + mines_per_type[bo.mines_].finished == 0) {
> +					needed_type = BuildingNecessity::kNeeded;
> +				}
> +			}
> +			if (bo.type == BuildingObserver::PRODUCTIONSITE) {
> +				if (bo.built_mat_producer_ || bo.max_needed_preciousness_ >= 10) {
> +					if (bo.total_count() == 0) {
> +						needed_type = BuildingNecessity::kNeeded;
> +					}
> +				}
> +			}
> +		} else { // exemption after 25 minutes - 2 buildings allowed
> +			if (bo.type == BuildingObserver::MINE) {
> +				if (mines_per_type[bo.mines_].in_construction + mines_per_type[bo.mines_].finished <= 1) {
> +					needed_type = BuildingNecessity::kNeeded;
> +				}
> +			}
> +			if (bo.type == BuildingObserver::PRODUCTIONSITE) {
> +				if (bo.built_mat_producer_ || bo.max_needed_preciousness_ >= 10) {
> +					if (bo.total_count() <= 1) {
> +						needed_type = BuildingNecessity::kNeeded;
> +					}
> +				}
> +			}
> +		}
> +	}
> +
> +	// And finally the 'core' of this function
> +	// First deal with construction of new sites
> +	if (purpose == PerfEvaluation::kForConstruction) {
> +		if (bo.forced_after_ < gametime && bo.total_count() == 0 && !has_substitution_building) {
> +			bo.max_needed_preciousness_ = bo.max_preciousness_;
> +			return BuildingNecessity::kForced;
> +		} else if (bo.prohibited_till_ > gametime) {
> +			return BuildingNecessity::kNotNeeded;
> +		} else if (bo.is_hunter_ || bo.is_fisher_) {
> +
> +			if (bo.max_needed_preciousness_ == 0) {
> +				return BuildingNecessity::kNotNeeded;
> +			} else if (bo.cnt_under_construction_ + bo.unoccupied_count_ > 0) {
> +				return BuildingNecessity::kNotNeeded;
> +			} else if (bo.total_count() > 0 && new_buildings_stop_) {
> +				return BuildingNecessity::kNotNeeded;
> +			} else {
> +				return BuildingNecessity::kNeeded;
> +			}
> +		} else if (bo.need_trees_) {
> +			if (bo.total_count() > 1 && (bo.cnt_under_construction_ + bo.unoccupied_count_ > 0)) {
> +				return BuildingNecessity::kNotNeeded;
> +			}
> +			bo.cnt_target_ =
> +					   3 + static_cast<int32_t>(mines_.size() + productionsites.size()) / 20;
> +
> +			// for case the wood is not needed yet, to avoid inconsistency later on
> +			bo.max_needed_preciousness_ = bo.max_preciousness_;
> +
> +			if (bo.total_count() < bo.cnt_target_) {
> +				return BuildingNecessity::kNeeded;
> +			} else {
> +				return BuildingNecessity::kAllowed;
> +			}
> +		} else if (bo.plants_trees_) {
> +
> +			bo.cnt_target_ =
> +				   2 +
> +				   static_cast<int32_t>(mines_.size() + productionsites.size()) / 40;
> +			if (wood_policy_ != WoodPolicy::kAllowRangers) {
> +				return BuildingNecessity::kNotNeeded;
> +			}
> +			// 150 corresponds to 15 trees
> +			if (trees_around_cutters_ < 150) {
> +				bo.cnt_target_ *= 4;
> +			}
> +			if (bo.total_count() > 1 && (bo.cnt_under_construction_ + bo.unoccupied_count_ > 0)) {
> +				return BuildingNecessity::kNotNeeded;
> +			} else if (bo.total_count() > bo.cnt_target_) {
> +				return BuildingNecessity::kNotNeeded;
> +			}
> +			return BuildingNecessity::kNeeded;
> +		} else if (bo.need_stones_ && bo.cnt_under_construction_ + bo.unoccupied_count_ == 0) {
> +			bo.max_needed_preciousness_ = bo.max_preciousness_; // even when stones are not needed
> +			return BuildingNecessity::kAllowed;
> +		} else if (bo.production_hint_ >= 0 && bo.cnt_under_construction_ + bo.unoccupied_count_ == 0) {
> +			return BuildingNecessity::kAllowed;
> +		} else if (bo.cnt_under_construction_ + bo.unoccupied_count_ > 0 && bo.max_needed_preciousness_ < 10) {
> +			return BuildingNecessity::kNotNeeded;
> +		} else if (bo.cnt_under_construction_ + bo.unoccupied_count_ > 0 && gametime < 30 * 60 * 1000) {
> +			return BuildingNecessity::kNotNeeded;
> +		} else if (bo.cnt_under_construction_ + bo.unoccupied_count_ > 1) {
> +			return BuildingNecessity::kNotNeeded; // for preciousness>=10 and after 30 min
> +		} else if (bo.type == BuildingObserver::MINE) {
> +			if ((mines_per_type[bo.mines_].in_construction + mines_per_type[bo.mines_].finished) == 0) {
> +				// unless a mine is prohibited, we want to have at least one of the kind
> +				bo.max_needed_preciousness_ = bo.max_preciousness_;
> +				return BuildingNecessity::kNeeded;
> +			} else if (((mines_per_type[bo.mines_].in_construction + mines_per_type[bo.mines_].finished)
> +				==
> +				1) && bo.built_mat_producer_) {
> +					bo.max_needed_preciousness_ = bo.max_preciousness_;
> +					return BuildingNecessity::kNeeded;
> +			}
> +			if (bo.max_needed_preciousness_ == 0) {
> +				return BuildingNecessity::kNotNeeded;
> +			}
> +			if (bo.total_count() - bo.unconnected_count_ >= 1 || bo.current_stats_ < 20) {
> +				return BuildingNecessity::kNotNeeded;
> +			}
> +			return needed_type;
> +		} if (bo.max_needed_preciousness_ > 0) {
> +			if (bo.cnt_under_construction_ + bo.unoccupied_count_ > 0) {
> +				assert (bo.cnt_under_construction_ + bo.unoccupied_count_ == 1);
> +				assert (bo.max_needed_preciousness_ >= 10 || bo.built_mat_producer_);
> +				assert (gametime >= 25 * 60 * 1000);
> +			}
> +
> +			// First 'if' is special support for hardwood producers (to have 2 of them)
> +			if (bo.built_mat_producer_ && bo.total_count() <= 1 && bo.current_stats_ > 10) {
> +				return BuildingNecessity::kNeeded;
> +			} else if (bo.inputs_.empty()) {
> +				return needed_type;
> +			} else if (bo.total_count() == 0) {
> +				return needed_type;
> +			} else if (bo.current_stats_ > 10 + 70 / bo.outputs_.size()) {
> +				return needed_type;
> +			} else {
> +				return BuildingNecessity::kNotNeeded;
> +			}
> +		} else if (bo.is_shipyard_) {
> +			return BuildingNecessity::kAllowed;
> +		} else  if (bo.max_needed_preciousness_ == 0) {
> +			return BuildingNecessity::kNotNeeded;
> +		} else {
> +			return BuildingNecessity::kNotNeeded;
> +		}
> +	} else if (purpose == PerfEvaluation::kForDismantle) { // now for dismantling
> +		// never dismantle last building (a care should be taken elsewhere)
> +		assert (bo.total_count() > 0);
> +		if (bo.total_count() == 1) {
> +			return BuildingNecessity::kNeeded;
> +		} else if (bo.max_preciousness_ >= 10 && bo.total_count() == 2) {
> +			return BuildingNecessity::kNeeded;
> +		} else if (bo.current_stats_ > (10 + 70 / bo.outputs_.size()) / 2) {
> +			return BuildingNecessity::kNeeded;
> +		} else {
> +			return BuildingNecessity::kNotNeeded;
> +		}
> +	} else {
> +		// impossible but still
> +		assert(false);
> +	}
> +}
> +
> +// Now we can prohibit some militarysites, based on size, the goal is not to
> +// exhaust AI resources on the beginning of the game
> +// We count bigger buildings, medium ones get 1 points, big ones 2 points
> +// and we force some proportion to the number of military sites
> +// sidenote: function can return kNotNeeded, but it means 'not allowed'
> +BuildingNecessity DefaultAI::check_building_necessity(const uint8_t size,
> +										const uint32_t gametime) {
> +
> +	assert (militarysites.size() ==  msites_built());
> +	// logically size of militerysite must in between 1 and 3 (including)
> +	assert (size >= 1 && size <= 3);

Is this BaseImmovable::SMALL, BaseImmovable::MEDIUM, and BaseImmovable::BIG? If not, ignore this comment.

If so, please use them (and also below) - it will make the code safer and easier to read.

The function could then also take BaseImmovable::Size as an argument instead of uint8_t. This check would then be assert (size != BaseImmovable::Size::NONE);

> +
> +	if (size == 1) { // this function is intended for medium and bigger sites
> +		return BuildingNecessity::kAllowed;
> +	}
> +
> +	uint32_t const big_buildings_score
> +		= msites_per_size[2].in_construction
> +		+ msites_per_size[2].finished
> +		+ msites_per_size[3].in_construction * 2
> +		+ msites_per_size[3].finished * 2;
> +
> +	const uint32_t msites_total = msites_built() + msites_in_constr();
> +
> +	// this is final proportion of big_buildings_score / msites_total
> +	// two exeptions:
> +	// if enemy nearby - can be higher
> +	// for early game - must be lower
> +	uint32_t limit = (msites_built() + msites_in_constr()) * 2 / 3;
> +
> +	// exemption first
> +	if (militarysites.size() > 3 && vacant_mil_positions_ == 0 && msites_in_constr() == 0) {
> +		return BuildingNecessity::kAllowed; // it seems the expansion is stuck so we allow big buildings
> +	} else if (gametime > enemy_last_seen_ &&
> +		gametime < enemy_last_seen_ + 30 * 60 * 1000 &&
> +		mines_.size() > 2) { // if enemies were nearby in last 30 minutes
> +			// we allow more big buidings
> +			limit *= 2;
> +	} else if (msites_total < ai_personality_early_militarysites) {
> +		// for the beginning of the game (first 30 military sites)
> +		limit = limit * msites_total / ai_personality_early_militarysites;
> +	}
> +
> +	if (big_buildings_score + size - 1  > limit) {
> +		return BuildingNecessity::kNotNeeded;
> +	} else {
> +		return BuildingNecessity::kAllowed;
>  	}
>  }
>  


-- 
https://code.launchpad.net/~widelands-dev/widelands/ai_persistent_data/+merge/271853
Your team Widelands Developers is subscribed to branch lp:~widelands-dev/widelands/ai_persistent_data.


References