← Back to team overview

widelands-dev team mailing list archive

[Merge] lp:~widelands-dev/widelands/ai_small_tweaks into lp:widelands

 

TiborB has proposed merging lp:~widelands-dev/widelands/ai_small_tweaks into lp:widelands.

Requested reviews:
  Widelands Developers (widelands-dev)

For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/ai_small_tweaks/+merge/286407

Another bunch of changes to AI, partially code cleaning and partially functional improvements, areas affected
- blocked fields management
- player strength processing
- attacking reworked a bit
- road management modified
- other nits
-- 
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/ai_small_tweaks into lp:widelands.
=== modified file 'src/ai/ai_help_structs.h'
--- src/ai/ai_help_structs.h	2016-01-20 20:12:00 +0000
+++ src/ai/ai_help_structs.h	2016-02-17 21:18:41 +0000
@@ -168,6 +168,20 @@
 	}
 };
 
+// Unowned but walkable fields nearby
+struct FindNodeUnownedWalkable {
+	bool accept(const Map&, const FCoords& fc) const {
+
+		return (fc.field->nodecaps() & MOVECAPS_WALK) && (fc.field->get_owned_by() == 0);
+	}
+
+	Player* player_;
+	Game& game;
+
+	FindNodeUnownedWalkable(Player* p, Game& g) : player_(p), game(g) {
+	}
+};
+
 // Looking only for mines-capable fields nearby
 // of specific type
 struct FindNodeMineable {
@@ -245,14 +259,6 @@
 };
 }
 
-struct BlockedField {
-	Widelands::FCoords coords;
-	uint32_t blocked_until_;
-
-	BlockedField(Widelands::FCoords c, int32_t until) : coords(c), blocked_until_(until) {
-	}
-};
-
 struct BuildableField {
 	Widelands::FCoords coords;
 
@@ -453,6 +459,7 @@
 	uint32_t stocklevel_time;  // time when stocklevel_ was last time recalculated
 	uint32_t last_dismantle_time_;
 	uint32_t construction_decision_time_;
+	uint32_t last_building_built_;
 
 	uint32_t unoccupied_count_;
 
@@ -593,4 +600,123 @@
 
 };
 
+// List of blocked fields with block time, with some accompanying functions
+struct BlockedFields {
+	// <hash of field coordinates, time till blocked>
+	// of course hash of an blocked field is unique
+	std::map<uint32_t, uint32_t> BlockedFields;
+
+	void add(uint32_t hash, uint32_t till){
+		if (BlockedFields.count(hash) == 0) {
+			BlockedFields.insert(std::pair<uint32_t, uint32_t>(hash, till));
+		} else if (BlockedFields[hash] < till) {
+			BlockedFields[hash] = till;
+		}
+		//third possibility is that a field has been already blocked for longer time than 'till'
+	}
+
+	uint32_t count(){
+		return BlockedFields.size();
+	}
+
+	void remove_expired(uint32_t gametime) {
+		std::vector<uint32_t> fields_to_remove;
+		for (auto field: BlockedFields) {
+			if (field.second<gametime) {
+				fields_to_remove.push_back(field.first);
+			}
+		}
+		while (!fields_to_remove.empty()) {
+			BlockedFields.erase(fields_to_remove.back());
+			fields_to_remove.pop_back();
+		}
+	}
+
+	bool is_blocked(uint32_t hash){
+		if (BlockedFields.count(hash) == 0) {
+			return false;
+		} else {
+			return true;
+		}
+	}
+};
+
+// This is an struct that stores strength of players, info on teams and provides some outputs from these data
+struct PlayersStrengths {
+	struct PlayerStat {
+		uint8_t tn_;
+		uint32_t players_power_;
+
+		PlayerStat() {};
+		PlayerStat(uint8_t tc, uint32_t pp) : tn_(tc), players_power_(pp) {}
+	};
+
+	// This is core part of this struct
+	std::map<uint16_t, PlayerStat> all_stats;
+
+	// Number of team, sum of players' strength
+	std::map<uint8_t, uint32_t> team_powers;
+
+	// Inserting/updating data
+	void add(uint16_t pn, uint8_t tn, uint32_t pp){
+		if (all_stats.count(pn) == 0) {
+			all_stats.insert(std::pair<uint16_t, PlayerStat>(pn, PlayerStat(tn, pp)));
+		} else {
+			all_stats[pn].players_power_ = pp;
+		}
+	}
+
+	void recalculate_team_power() {
+		team_powers.clear();
+		for (auto& item: all_stats){
+			if (item.second.tn_ > 0) { //is a member of a team
+				if (team_powers.count(item.second.tn_) > 0){
+					team_powers[item.second.tn_] += item.second.players_power_;
+				} else {
+					team_powers[item.second.tn_] = item.second.players_power_;
+				}
+			}
+		}
+	}
+
+	// This is strength of player plus third of strength of other members of his team
+	uint32_t get_modified_player_power(uint16_t pn){
+		uint32_t result = 0;
+		uint8_t team = 0;
+		if (all_stats.count(pn) > 0) {
+			result = all_stats[pn].players_power_;
+			team = all_stats[pn].tn_;
+		};
+		if (team > 0 && team_powers.count(team) > 0) {
+			result = result + (team_powers[team] - result) / 3;
+		};
+		return result;
+	}
+
+	bool players_in_same_team(uint16_t pl1, uint16_t pl2){
+		if (all_stats.count(pl1) > 0 && all_stats.count(pl2) > 0 && pl1 != pl2) {
+			// team number 0 = no team
+			return all_stats[pl1].tn_ > 0 && all_stats[pl1].tn_ == all_stats[pl2].tn_;
+		} else {
+			return false;
+		}
+	}
+
+	bool strong_enough(uint16_t pl) {
+		if (all_stats.count(pl) == 0) {
+			return false;
+		}
+		uint32_t my_strength = all_stats[pl].players_power_;
+		uint32_t strongest_oponent_strength=0;
+		for (auto item : all_stats) {
+			if (!players_in_same_team(item.first, pl) && pl != item.first) {
+				if (get_modified_player_power(item.first) > strongest_oponent_strength) {
+					strongest_oponent_strength = get_modified_player_power(item.first);
+				}
+			}
+		}
+		return my_strength > strongest_oponent_strength + 50;
+	}
+};
+
 #endif  // end of include guard: WL_AI_AI_HELP_STRUCTS_H

=== modified file 'src/ai/defaultai.cc'
--- src/ai/defaultai.cc	2016-02-13 12:15:29 +0000
+++ src/ai/defaultai.cc	2016-02-17 21:18:41 +0000
@@ -75,6 +75,10 @@
 constexpr uint32_t kNever = std::numeric_limits<uint32_t>::max();
 constexpr uint32_t kNoExpedition = 0;
 
+// following two are used for roads management, for creating shortcuts and dismantling dispensable roads
+constexpr int32_t kSpotsTooLittle = 15;
+constexpr int32_t kSpotsEnough = 25;
+
 // this is intended for map developers, by default should be off
 constexpr bool kPrintStats = false;
 
@@ -510,6 +514,7 @@
 		// this is set to negative number, otherwise the AI would wait 25 sec
 		// after game start not building anything
 		bo.construction_decision_time_ = -60 * 60 * 1000;
+		bo.last_building_built_ = kNever;
 		bo.build_material_shortage_ = false;
 		bo.production_hint_ = kUncalculated;
 		bo.current_stats_ = 0;
@@ -831,9 +836,7 @@
 			MapRegion<Area<FCoords>> mr(
 			   map, Area<FCoords>(map.get_fcoords(ps_obs.site->get_position()), 4));
 			do {
-				BlockedField blocked2(
-				   map.get_fcoords(*(mr.location().field)), game().get_gametime() + 20 * 60 * 1000);
-				blocked_fields.push_back(blocked2);
+				blocked_fields.add(coords_hash(mr.location()), game().get_gametime() + 20 * 60 * 1000);
 			} while (mr.advance(map));
 		}
 	}
@@ -1003,12 +1006,12 @@
 	// look if there is any unowned land nearby
 	Map& map = game().map();
 	const uint32_t gametime = game().get_gametime();
-	FindNodeUnowned find_unowned(player_, game());
+	FindNodeUnownedWalkable find_unowned_walkable(player_, game());
 	FindNodeUnownedMineable find_unowned_mines_pots(player_, game());
 	PlayerNumber const pn = player_->player_number();
 	const World& world = game().world();
 	field.unowned_land_nearby_ =
-	   map.find_fields(Area<FCoords>(field.coords, range), nullptr, find_unowned);
+	   map.find_fields(Area<FCoords>(field.coords, range), nullptr, find_unowned_walkable);
 	FindNodeAllyOwned find_ally(player_, game(), player_number());
 	const int32_t AllyOwnedFields =
 	   map.find_fields(Area<FCoords>(field.coords, 3), nullptr, find_ally);
@@ -1017,7 +1020,7 @@
 	if (AllyOwnedFields > 0) {
 		field.near_border_ = true;
 	} else if (field.unowned_land_nearby_ > 0) {
-		if (map.find_fields(Area<FCoords>(field.coords, 4), nullptr, find_unowned) > 0) {
+		if (map.find_fields(Area<FCoords>(field.coords, 4), nullptr, find_unowned_walkable) > 0) {
 			field.near_border_ = true;
 		}
 	}
@@ -1059,7 +1062,7 @@
 	// testing for near portspaces
 	if (field.portspace_nearby_ == Widelands::ExtendedBool::kUnset) {
 		field.portspace_nearby_ = ExtendedBool::kFalse;
-		MapRegion<Area<FCoords>> mr(map, Area<FCoords>(field.coords, 2));
+		MapRegion<Area<FCoords>> mr(map, Area<FCoords>(field.coords, 4));
 		do {
 			if (port_reserved_coords.count(coords_hash(mr.location())) > 0) {
 				field.portspace_nearby_ = ExtendedBool::kTrue;
@@ -1442,7 +1445,6 @@
 	}
 	// Just used for easy checking whether a mine or something else was built.
 	bool mine = false;
-	bool field_blocked = false;
 	uint32_t consumers_nearby_count = 0;
 	std::vector<int32_t> spots_avail;
 	spots_avail.resize(4);
@@ -1502,7 +1504,8 @@
 	bool needs_boost_economy = false;
 	if (highest_nonmil_prio_ > 10
 		&& has_enough_space
-		&& virtual_mines >= 5){
+		&& virtual_mines >= 5 &&
+		!player_statistics.strong_enough(player_number())) {
 			needs_boost_economy = true;
 		}
 
@@ -1635,12 +1638,7 @@
 	Coords proposed_coords;
 
 	// Remove outdated fields from blocker list
-	for (std::list<BlockedField>::iterator i = blocked_fields.begin(); i != blocked_fields.end();)
-		if (i->blocked_until_ < gametime) {
-			i = blocked_fields.erase(i);
-		} else {
-			++i;
-		}
+	blocked_fields.remove_expired(gametime);
 
 	// testing big military buildings, whether critical construction
 	// material is available (at least in amount of
@@ -1779,17 +1777,7 @@
 		}
 
 		// Continue if field is blocked at the moment
-		field_blocked = false;
-
-		for (std::list<BlockedField>::iterator j = blocked_fields.begin(); j != blocked_fields.end();
-		     ++j) {
-			if (j->coords == bf->coords) {
-				field_blocked = true;
-			}
-		}
-
-		// continue;
-		if (field_blocked) {
+		if (blocked_fields.is_blocked(coords_hash(bf->coords))) {
 			continue;
 		}
 
@@ -2129,7 +2117,7 @@
 			}  // production sites done
 			else if (bo.type == BuildingObserver::MILITARYSITE) {
 
-				if (!bf->unowned_land_nearby_) {
+				if (!(bf->unowned_land_nearby_ || bf->enemy_nearby_)) {
 					continue;
 				}
 
@@ -2183,7 +2171,20 @@
 				prio += bf->distant_water_ * resource_necessity_water_needed_ / 100;
 				prio += bf->military_loneliness_ / 10;
 				prio += bf->trees_nearby_ / 3;
-				if (bf->portspace_nearby_ == ExtendedBool::kTrue) prio += 25;
+				if (bf->portspace_nearby_ == ExtendedBool::kTrue) {
+					if (num_ports == 0) {
+						prio += 100;
+					} else {
+						prio += 25;
+					}
+				}
+				//sometimes expansion is stalled and this is to help boost it
+				if (msites_in_constr() == 0 && vacant_mil_positions_ <= 2) {
+					prio += 10;
+					if (bf->enemy_nearby_){
+						prio += 20;
+					}
+				}
 
 				// additional score for bigger buildings
 				int32_t prio_for_size = bo.desc->get_size() - 1;
@@ -2443,18 +2444,7 @@
 					prio += mf->same_mine_fields_nearby_;
 
 					// Continue if field is blocked at the moment
-					bool blocked = false;
-
-					for (std::list<BlockedField>::iterator k = blocked_fields.begin();
-					     k != blocked_fields.end();
-					     ++k)
-						if ((*j)->coords == k->coords) {
-							blocked = true;
-							break;
-						}
-
-					if (blocked) {
-
+					if (blocked_fields.is_blocked(coords_hash(mf->coords))) {
 						continue;
 					}
 
@@ -2490,9 +2480,7 @@
 
 	// send the command to construct a new building
 	game().send_player_build(player_number(), proposed_coords, best_building->id);
-	BlockedField blocked(
-	   game().map().get_fcoords(proposed_coords), game().get_gametime() + 120000);  // two minutes
-	blocked_fields.push_back(blocked);
+	blocked_fields.add(coords_hash(proposed_coords), game().get_gametime() + 2 * 60 * 1000);
 
 	// resetting new_building_overdue_
 	best_building->new_building_overdue_ = 0;
@@ -2505,7 +2493,11 @@
 		uint32_t block_time = 0;
 		uint32_t block_area = 0;
 		if (best_building->space_consumer_) {
-			block_time = 45 * 60 * 1000;
+			if (spots_ > kSpotsEnough) {
+				block_time = 45 * 60 * 1000;
+			} else {
+				block_time = 10 * 60 * 1000;
+			}
 			block_area = 3;
 		} else {  // militray buildings for a very short time
 			block_time = 25 * 1000;
@@ -2514,9 +2506,7 @@
 
 		MapRegion<Area<FCoords>> mr(map, Area<FCoords>(map.get_fcoords(proposed_coords), block_area));
 		do {
-			BlockedField blocked2(
-			   map.get_fcoords(*(mr.location().field)), game().get_gametime() + block_time);
-			blocked_fields.push_back(blocked2);
+			blocked_fields.add(coords_hash(mr.location()), game().get_gametime() + block_time);
 		} while (mr.advance(map));
 	}
 
@@ -2538,12 +2528,11 @@
 // improves current road system
 bool DefaultAI::improve_roads(uint32_t gametime) {
 
-	// first force a split on roads that are longer than 3 parts
-	// with exemption when there is too few building spots
-	if (spots_ > 20 && !roads.empty()) {
+	if (!roads.empty()) {
 		const Path& path = roads.front()->get_path();
 
-		if (path.get_nsteps() > 3) {
+		// first force a split on roads that are longer than 3 parts
+		if (path.get_nsteps() > 3 && spots_ > kSpotsEnough) {
 			const Map& map = game().map();
 			CoordPath cp(map, path);
 			// try to split after two steps
@@ -2570,13 +2559,15 @@
 
 			// Unable to set a flag - perhaps the road was build stupid
 			game().send_player_bulldoze(*const_cast<Road*>(roads.front()));
+			return true;
 		}
 
 		roads.push_back(roads.front());
 		roads.pop_front();
 
 		// occasionaly we test if the road can be dismounted
-		if (gametime % 5 == 0) {
+		// if there is shortage of spots we do it allways
+		if (gametime % 5 == 0 || spots_ < kSpotsTooLittle) {
 			const Road& road = *roads.front();
 			if (dispensable_road_test(*const_cast<Road*>(&road))) {
 				game().send_player_bulldoze(*const_cast<Road*>(&road));
@@ -2627,16 +2618,33 @@
 		}
 	}
 
-	// if this is end flag (or sole building) or just randomly
-	if (flag.nr_of_roads() <= 1 || gametime % 10 == 0) {
+	// is connected to a warehouse?
+	const bool needs_warehouse = flag.get_economy()->warehouses().empty();
+
+	// needs to be connected
+	if (flag.nr_of_roads() == 0 || needs_warehouse) {
 		create_shortcut_road(flag, 13, 22, gametime);
 		inhibit_road_building_ = gametime + 800;
+	} else if (flag.nr_of_roads() == 1 || gametime % 10 == 0) {
+		if (spots_ > kSpotsEnough) {
+			// This is normal situation
+			create_shortcut_road(flag, 13, 22, gametime);
+			inhibit_road_building_ = gametime + 800;
+		} else if (spots_ > kSpotsTooLittle)  {
+			// We are shoft of spots so shortening must be significant
+			create_shortcut_road(flag, 13, 35, gametime);
+			inhibit_road_building_ = gametime + 800;
+		} else {
+			// We are very shoft of spots so shortening must be even bigger
+			create_shortcut_road(flag, 13, 50, gametime);
+			inhibit_road_building_ = gametime + 800;
+		}
 		// a warehouse with 3 or less roads
 	} else if (is_warehouse && flag.nr_of_roads() <= 3) {
 		create_shortcut_road(flag, 9, -1, gametime);
 		inhibit_road_building_ = gametime + 400;
 		// and when a flag is full with wares
-	} else if (flag.current_wares() > 5) {
+	} else if (spots_ > kSpotsEnough && flag.current_wares() > 5) {
 		create_shortcut_road(flag, 9, -2, gametime);
 		inhibit_road_building_ = gametime + 400;
 	} else {
@@ -2655,7 +2663,9 @@
 	Flag& roadstartflag = road.get_flag(Road::FlagStart);
 	Flag& roadendflag = road.get_flag(Road::FlagEnd);
 
-	if (roadstartflag.current_wares() > 0 || roadendflag.current_wares() > 0) {
+	// We do not dismantle (even consider it) if the road is busy (some wares on flags), unless there
+	// is shortage of build spots
+	if (spots_ > kSpotsTooLittle && roadstartflag.current_wares() + roadendflag.current_wares() > 0) {
 		return false;
 	}
 
@@ -2664,7 +2674,14 @@
 	std::vector<NearFlag> reachableflags;
 	queue.push(NearFlag(roadstartflag, 0, 0));
 	uint8_t pathcounts = 0;
-	uint8_t checkradius = 8;
+	uint8_t checkradius = 0;
+	if (spots_ > kSpotsEnough) {
+		checkradius = 8;
+	} else if (spots_ > kSpotsTooLittle) {
+		checkradius = 12;
+	} else {
+		checkradius = 16;
+	}
 	Map& map = game().map();
 
 	// algorithm to walk on roads
@@ -3000,11 +3017,12 @@
 	// if all possible roads skipped
 	if (last_attempt_) {
 		Building* bld = flag.get_building();
-		// first we block the field for 15 minutes, probably it is not good place to build a
-		// building on
-		BlockedField blocked(
-		   game().map().get_fcoords(bld->get_position()), game().get_gametime() + 15 * 60 * 1000);
-		blocked_fields.push_back(blocked);
+		// first we block the field and vicinity for 15 minutes, probably it is not good place to build on
+		MapRegion<Area<FCoords>> mr(
+			   game().map(), Area<FCoords>(map.get_fcoords(bld->get_position()), 2));
+		do {
+			blocked_fields.add(coords_hash(mr.location()), game().get_gametime() + 15 * 60 * 1000);
+		} while (mr.advance(map));
 		eco->flags.remove(&flag);
 		game().send_player_bulldoze(*const_cast<Flag*>(&flag));
 		return true;
@@ -3169,6 +3187,11 @@
 	// Lumberjack / Woodcutter handling
 	if (site.bo->need_trees_) {
 
+		//do not dismantle immediatelly
+		if ((game().get_gametime() - site.built_time_) < 4 * 60 * 1000) {
+			return false;
+		}
+
 		const uint32_t remaining_trees =
 		   map.find_immovables(Area<FCoords>(map.get_fcoords(site.site->get_position()), radius),
 		                       nullptr,
@@ -3188,8 +3211,6 @@
 			return false;
 		}
 
-
-
 		// do not dismantle if there are some trees remaining
 		if (remaining_trees > 5) {
 			return false;
@@ -4118,6 +4139,10 @@
 			if (bo.current_stats_ < 40) {
 				return BuildingNecessity::kForbidden;
 			}
+			assert (bo.last_building_built_ != kNever);
+			if (gametime < bo.last_building_built_ + 3 * 60 * 1000) {
+				return BuildingNecessity::kForbidden;
+			}
 			return needed_type;
 		} if (bo.max_needed_preciousness_ > 0) {
 			if (bo.cnt_under_construction_ + bo.unoccupied_count_ > 0) {
@@ -4134,7 +4159,14 @@
 			} else if (bo.total_count() == 0) {
 				return needed_type;
 			} else if (bo.current_stats_ > 10 + 70 / bo.outputs_.size()) {
-				return needed_type;
+				assert (bo.last_building_built_ != kNever);
+				if (gametime < bo.last_building_built_ + 10 * 60 * 1000) {
+					// Previous building built less then 10 minutes ago
+					// Wait a bit, perhaps average utilization will drop down in the meantime
+					return BuildingNecessity::kNeededPending;
+				} else {
+					return needed_type;
+				}
 			} else if (needs_second_for_upgrade) {
 				return needed_type;
 			} else {
@@ -4559,6 +4591,9 @@
 	// and if close (up to 2 fields away) from border
 	if (bf.near_border_) {
 		prio -= 10;
+		if (spots_ < kSpotsEnough){
+			prio +=  3 * (spots_ - kSpotsEnough);
+		}
 	}
 
 	return prio;
@@ -4898,7 +4933,7 @@
 
 	// If we have portspace following options are avaiable:
 	// 1. Build a port there
-	if (!so.ship->exp_port_spaces().empty()) {  // making sure we have possible portspaces
+	if (so.ship->exp_port_spaces().size() > 0) {  // making sure we have possible portspaces
 
 		// we score the place
 		const uint8_t spot_score = spot_scoring(so.ship->exp_port_spaces().front());
@@ -4914,9 +4949,7 @@
 			MapRegion<Area<FCoords>> mr(
 			   game().map(), Area<FCoords>(map.get_fcoords(so.ship->exp_port_spaces().front()), 8));
 			do {
-				BlockedField blocked2(
-				   map.get_fcoords(*(mr.location().field)), gametime + 5 * 60 * 1000);
-				blocked_fields.push_back(blocked2);
+				blocked_fields.add(coords_hash(mr.location()), game().get_gametime() + 5 * 60 * 1000);
 			} while (mr.advance(map));
 
 			return;
@@ -5006,14 +5039,16 @@
 
 	} else {
 		++bo.cnt_built_;
+		const uint32_t gametime = game().get_gametime();
+		bo.last_building_built_ = gametime;
 
 		if (bo.type == BuildingObserver::PRODUCTIONSITE) {
 			productionsites.push_back(ProductionSiteObserver());
 			productionsites.back().site = &dynamic_cast<ProductionSite&>(b);
 			productionsites.back().bo = &bo;
 			productionsites.back().bo->new_building_overdue_ = 0;
-			productionsites.back().built_time_ = game().get_gametime();
-			productionsites.back().unoccupied_till_ = game().get_gametime();
+			productionsites.back().built_time_ = gametime;
+			productionsites.back().unoccupied_till_ = gametime;
 			productionsites.back().stats_zero_ = 0;
 			productionsites.back().no_resources_since_ =  kNever;
 			productionsites.back().bo->unoccupied_count_ += 1;
@@ -5031,7 +5066,7 @@
 			mines_.push_back(ProductionSiteObserver());
 			mines_.back().site = &dynamic_cast<ProductionSite&>(b);
 			mines_.back().bo = &bo;
-			mines_.back().built_time_ = game().get_gametime();
+			mines_.back().built_time_ =gametime;
 			mines_.back().no_resources_since_ =  kNever;
 			mines_.back().bo->unoccupied_count_ += 1;
 
@@ -5301,7 +5336,7 @@
 	Map& map = game().map();
 
 	// define which players are attackable
-	std::vector<bool> player_attackable;
+	std::vector<Attackable> player_attackable;
 	PlayerNumber const nr_players = map.get_nrplayers();
 	player_attackable.resize(nr_players);
 	uint32_t plr_in_game = 0;
@@ -5312,28 +5347,22 @@
 	// receiving games statistics and parsing it (reading latest entry)
 	const Game::GeneralStatsVector& genstats = game().get_general_statistics();
 
-	// summing team power, creating team_power std::map of team_number:strength
-	std::map<TeamNumber, uint32_t> team_power;
+	//Collecting statistics and saving them in player_statistics object
 	for (uint8_t j = 1; j <= plr_in_game; ++j) {
-		const Player* other = game().get_player(j);
-		const TeamNumber tm = other ? other->team_number() : 0;
-		if (tm == 0) {
-			continue;
-		}
-		// for case this is new team
-		if (team_power.count(tm) == 0) {
-			// adding this team (number) to vector
-			team_power[tm] = 0;
-		}
-		try {
-			team_power[tm] += genstats.at(j - 1).miltary_strength.back();
-		} catch (const std::out_of_range&) {
-			log("ComputerPlayer(%d): genstats entry missing - size :%d\n",
-			    player_number(),
-			    static_cast<unsigned int>(genstats.size()));
+		const Player* this_player = game().get_player(j);
+		if (this_player) {
+			try {
+				player_statistics.add(j, this_player->team_number(), genstats.at(j - 1).miltary_strength.back());
+			} catch (const std::out_of_range&) {
+				log("ComputerPlayer(%d): genstats entry missing - size :%d\n",
+				    player_number(),
+				    static_cast<unsigned int>(genstats.size()));
+			}
 		}
 	}
 
+	player_statistics.recalculate_team_power();
+
 	// defining treshold ratio of own_strength/enemy's_strength
 	uint32_t treshold_ratio = 100;
 	if (type_ == DefaultAI::Type::kNormal) {
@@ -5358,65 +5387,37 @@
 		treshold_ratio += persistent_data->ai_personality_attack_margin;
 	}
 
-	uint32_t my_power = 0;
-	try {
-		my_power = genstats.at(pn - 1).miltary_strength.back();
-	} catch (const std::out_of_range&) {
-		log("ComputerPlayer(%d): genstats entry missing - size :%d\n",
-		    player_number(),
-		    static_cast<unsigned int>(genstats.size()));
-	}
-	// adding power of team (minus my power) divided by 2
-	// (if I am a part of a team of course)
-	const TeamNumber team_number = player_->team_number();
-	if (team_number > 0) {
-		my_power += (team_power[team_number] - my_power) / 2;
-	}
+	const uint32_t my_power = player_statistics.get_modified_player_power(pn);
+
 
 	// now we test all players to identify 'attackable' ones
 	for (uint8_t j = 1; j <= plr_in_game; ++j) {
-		// if it's me
-		if (pn == j) {
-			player_attackable[j - 1] = false;
-			continue;
-		}
-		// if we are the same team
-		const Player* other = game().get_player(j);
-		const TeamNumber tm = other ? other->team_number() : 0;
-		if (team_number > 0 && team_number == tm) {
-			player_attackable[j - 1] = false;
+		// if we are the same team, or just it is me
+		if (player_statistics.players_in_same_team(pn, j) || pn == j) {
+			player_attackable[j - 1] = Attackable::kNotAttackable;
 			continue;
 		}
 
 		// now we compare strength
-		try {
-			// strength of the other player
-			uint32_t players_power = 0;
-			if (!genstats.at(j - 1).miltary_strength.empty()) {
-				players_power += genstats.at(j - 1).miltary_strength.back();
-			}
-			// +power of team (if member of a team)
-			if (tm > 0) {
-				players_power += (team_power[tm] - players_power) / 2;
-			}
+		// strength of the other player (considering his team)
+		uint32_t players_power = player_statistics.get_modified_player_power(j);;
 
-			if (players_power == 0) {
-				player_attackable.at(j - 1) = true;
-			} else if (my_power * 100 / players_power > treshold_ratio) {
-				player_attackable.at(j - 1) = true;
-			} else {
-				player_attackable.at(j - 1) = false;
-			}
-		} catch (const std::out_of_range&) {
-			log("ComputerPlayer(%d): genstats entry missing - size :%d\n",
-			    player_number(),
-			    static_cast<unsigned int>(genstats.size()));
-			player_attackable.at(j - 1) = false;
+		if (players_power == 0) {
+			player_attackable.at(j - 1) = Attackable::kAttackable;
+		} else if (my_power * 100 / players_power > treshold_ratio * 8) {
+			player_attackable.at(j - 1) = Attackable::kAttackableVeryWeak;
+		} else if (my_power * 100 / players_power > treshold_ratio * 4) {
+			player_attackable.at(j - 1) = Attackable::kAttackableAndWeak;
+		} else if (my_power * 100 / players_power > treshold_ratio) {
+			player_attackable.at(j - 1) = Attackable::kAttackable;
+		} else {
+			player_attackable.at(j - 1) = Attackable::kNotAttackable;
 		}
+
 	}
 
 	// first we scan vicitnity of couple of militarysites to get new enemy sites
-	// militarysites rotate
+	// Militarysites rotate (see check_militarysites())
 	int32_t i = 0;
 	for (MilitarySiteObserver mso : militarysites) {
 		i += 1;
@@ -5506,6 +5507,8 @@
 			;
 	}
 
+	const bool strong_enough = player_statistics.strong_enough(pn);
+
 	for (std::map<uint32_t, EnemySiteObserver>::iterator site = enemy_sites.begin();
 	     site != enemy_sites.end();
 	     ++site) {
@@ -5598,7 +5601,9 @@
 
 			if (site->second.attack_soldiers_strength > 0
 				&&
-				player_attackable[owner_number - 1]) {
+				(player_attackable[owner_number - 1] == Attackable::kAttackable ||
+				player_attackable[owner_number - 1] == Attackable::kAttackableAndWeak ||
+				player_attackable[owner_number - 1] == Attackable::kAttackableVeryWeak)) {
 				site->second.score = site->second.attack_soldiers_strength - site->second.defenders_strength / 2;
 
 				if (is_warehouse) {
@@ -5626,6 +5631,19 @@
 				// Applying (decreasing score) if trainingsites are not working
 				site->second.score += training_score;
 
+				// We have an advantage over stongest opponent
+				if (strong_enough) {
+					site->second.score += 3;
+				}
+
+				// Enemy is too weak, be more aggressive attacking him
+				if (player_attackable[owner_number - 1] == Attackable::kAttackableAndWeak) {
+					site->second.score += 4;
+				}
+				if (player_attackable[owner_number - 1] == Attackable::kAttackableVeryWeak) {
+					site->second.score += 8;
+				}
+
 				// treating no attack score
 				if (site->second.no_attack_counter < 0) {
 					// we cannot attack yet

=== modified file 'src/ai/defaultai.h'
--- src/ai/defaultai.h	2016-02-09 20:22:50 +0000
+++ src/ai/defaultai.h	2016-02-17 21:18:41 +0000
@@ -85,6 +85,7 @@
 	enum class WoodPolicy : uint8_t {kDismantleRangers, kStopRangers, kAllowRangers};
 	enum class NewShip : uint8_t {kBuilt, kFoundOnLoad};
 	enum class PerfEvaluation : uint8_t {kForConstruction, kForDismantle};
+	enum class Attackable : uint8_t {kNotAttackable, kAttackable, kAttackableAndWeak, kAttackableVeryWeak};
 
 	enum class Tribes : uint8_t {
 		kNone,
@@ -93,7 +94,6 @@
 		kEmpire
 	};
 
-
 	/// Implementation for Strong
 	struct NormalImpl : public ComputerPlayer::Implementation {
 		NormalImpl() {
@@ -263,7 +263,8 @@
 
 	std::list<Widelands::FCoords> unusable_fields;
 	std::list<BuildableField*> buildable_fields;
-	std::list<BlockedField> blocked_fields;
+	BlockedFields blocked_fields;
+	PlayersStrengths player_statistics;
 	std::unordered_set<uint32_t> port_reserved_coords;
 	std::list<MineableField*> mineable_fields;
 	std::list<Widelands::Flag const*> new_flags;


Follow ups