← Back to team overview

widelands-dev team mailing list archive

[Merge] lp:~widelands-dev/widelands/tibor-ai6 into lp:widelands

 

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

Requested reviews:
  Widelands Developers (widelands-dev)

For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/tibor-ai6/+merge/238767

This is probably last big change to AI, I focused on military:

- reworked attacking
- building of medium/big military buildings considers shortage of critical building materials (marble, diamonts and so on)
- AI preserves some forces on the begin of game to be able to reach mountains (in some maps, players run into each other before they establish mines and exhaust themselves by attacking)
- small change to upgrading policy of production sites (more aggressive)
- fixed small issues as I run into them
-- 
https://code.launchpad.net/~widelands-dev/widelands/tibor-ai6/+merge/238767
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/tibor-ai6 into lp:widelands.
=== modified file 'src/ai/ai_help_structs.h'
--- src/ai/ai_help_structs.h	2014-10-08 18:06:06 +0000
+++ src/ai/ai_help_structs.h	2014-10-17 19:46:00 +0000
@@ -225,6 +225,8 @@
 	int16_t military_presence_;
 	// stationed (manned) military buildings nearby
 	int16_t military_stationed_;
+	// stationed (manned) military buildings nearby
+	int16_t military_unstationed_;
 	// some buildings must be postponed bit
 	int32_t prohibited_till_;
 	// and then some must be forced
@@ -330,6 +332,7 @@
 
 	std::vector<int16_t> inputs_;
 	std::vector<int16_t> outputs_;
+	std::vector<Widelands::WareIndex> critical_built_mat_;
 	int16_t production_hint_;
 
 	int32_t cnt_built_;
@@ -341,6 +344,7 @@
 	int32_t stocklevel_time;  // time when stocklevel_ was last time recalculated
 	int32_t last_dismantle_time_;
 	int32_t construction_decision_time_;
+	bool built_mat_shortage_;
 
 	int32_t total_count() const {
 		return cnt_built_ + cnt_under_construction_;

=== modified file 'src/ai/defaultai.cc'
--- src/ai/defaultai.cc	2014-10-16 08:11:06 +0000
+++ src/ai/defaultai.cc	2014-10-17 19:46:00 +0000
@@ -58,7 +58,7 @@
 constexpr int kBusyMineUpdateInterval = 2000;
 // building of the same building can be started after 25s at earliest
 constexpr int kBuildingMinInterval = 25 * 1000;
-constexpr int kMinBFCheckInterval = 6 * 1000;
+constexpr int kMinBFCheckInterval = 5 * 1000;
 // Some buildings have to be built close to borders and their
 // priority might be decreased below 0, so this is to
 // compensate
@@ -104,7 +104,9 @@
      unstationed_milit_buildings_(0),
      military_last_dismantle_(0),
      military_last_build_(-60 * 1000),
-
+	last_attack_target_(
+        std::numeric_limits<uint16_t>::max(), std::numeric_limits<uint16_t>::max()),
+     next_attack_waittime_(10),
      spots_(0) {
 
 	// Subscribe to NoteFieldPossession.
@@ -299,6 +301,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.built_mat_shortage_ = false;
 		bo.production_hint_ = -1;
 		bo.current_stats_ = 0;
 		bo.unoccupied_ = false;
@@ -362,8 +365,25 @@
 			continue;
 		}
 
+		// now for every military building, we fill critical_built_mat_ vector
+		// with critical construction wares
+		// non critical are excluded (see below)
 		if (typeid(bld) == typeid(MilitarySiteDescr)) {
 			bo.type = BuildingObserver::MILITARYSITE;
+			const MilitarySiteDescr& milit =
+			   ref_cast<MilitarySiteDescr const, BuildingDescr const>(bld);
+			for (const std::pair<unsigned char, unsigned char>& temp_buildcosts : milit.buildcost()) {
+				// bellow are non-critical wares
+				if (tribe_->ware_index("log") == temp_buildcosts.first ||
+				    tribe_->ware_index("blackwood") == temp_buildcosts.first ||
+				    tribe_->ware_index("planks") == temp_buildcosts.first ||
+				    tribe_->ware_index("wood") == temp_buildcosts.first ||
+				    tribe_->ware_index("raw_stone") == temp_buildcosts.first ||
+				    tribe_->ware_index("stone") == temp_buildcosts.first)
+					continue;
+
+				bo.critical_built_mat_.push_back(temp_buildcosts.first);
+			}
 			continue;
 		}
 
@@ -704,6 +724,7 @@
 	immovables.clear();
 	map.find_immovables(Area<FCoords>(field.coords, (range < 10) ? 10 : range), &immovables);
 	field.military_stationed_ = 0;
+	field.military_unstationed_ = 0;
 	field.military_in_constr_nearby_ = 0;
 	field.military_capacity_ = 0;
 	field.military_loneliness_ = 1000;
@@ -734,9 +755,8 @@
 				if (upcast(MilitarySiteDescr const, target_ms_d, &target_descr)) {
 					const int32_t dist = map.calc_distance(field.coords, immovables.at(i).coords);
 					const int32_t radius = target_ms_d->get_conquers() + 4;
-					const int32_t v = radius - dist;
 
-					if (v > 0) {
+					if (radius > dist) {
 						field.military_capacity_ += target_ms_d->get_max_number_of_soldiers() / 2 + 1;
 						field.military_loneliness_ *= static_cast<double_t>(dist) / radius;
 						field.military_in_constr_nearby_ += 1;
@@ -747,18 +767,16 @@
 			if (upcast(MilitarySite const, militarysite, building)) {
 				const int32_t dist = map.calc_distance(field.coords, immovables.at(i).coords);
 				const int32_t radius = militarysite->descr().get_conquers() + 4;
-				const int32_t v = radius - dist;
 
-				if (v > 0 && dist > 0) {
+				if (radius > dist) {
 
 					field.military_capacity_ += militarysite->max_soldier_capacity();
 					field.military_presence_ += militarysite->stationed_soldiers().size();
 
-					if (!militarysite->stationed_soldiers().empty()) {
+					if (militarysite->stationed_soldiers().empty()) {
+						field.military_unstationed_ += 1;
+					} else {
 						field.military_stationed_ += 1;
-					} else {
-						// the name does not match much
-						field.military_in_constr_nearby_ += 1;
 					}
 
 					field.military_loneliness_ *= static_cast<double_t>(dist) / radius;
@@ -954,13 +972,13 @@
 	// set necessity for mines
 	// we use 'virtual mines', because also mine spots can be changed
 	// to mines when AI decides so
-	const int32_t virtual_mines = mines_.size() + mineable_fields.size() / 10;
-	if (virtual_mines <= 5) {
+	const int32_t virtual_mines = mines_.size() + mineable_fields.size() / 15;
+	if (virtual_mines <= 7) {
 		resource_necessity_mines_ = std::numeric_limits<uint8_t>::max();
-	} else if (virtual_mines > 14) {
+	} else if (virtual_mines > 19) {
 		resource_necessity_mines_ = 0;
 	} else {
-		const uint32_t tmp = ((16 - virtual_mines) * 255) / 12;
+		const uint32_t tmp = ((18 - virtual_mines) * 255) / 12;
 		resource_necessity_mines_ = tmp;
 	}
 
@@ -999,6 +1017,42 @@
 			++i;
 		}
 
+	// testing big military buildings, whether critical construction
+	// material is (not) needed
+	for (uint32_t j = 0; j < buildings_.size(); ++j) {
+
+		BuildingObserver& bo = buildings_.at(j);
+		if (!bo.buildable(*player_)) {
+			continue;
+		}
+
+		// not doing this for non-military buildins
+		if (!(bo.type == BuildingObserver::MILITARYSITE))
+			continue;
+
+		// and neither for small military buildings
+		if (bo.desc->get_size() == BaseImmovable::SMALL)
+			continue;
+
+		bo.built_mat_shortage_ = false;
+
+		for (EconomyObserver* observer : economies) {
+			// Don't check if the economy has no warehouse.
+			if (observer->economy.warehouses().empty()) {
+				continue;
+			}
+
+			for (uint32_t m = 0; m < bo.critical_built_mat_.size(); ++m) {
+				WareIndex wt(static_cast<size_t>(bo.critical_built_mat_.at(m)));
+
+				if (observer->economy.needs_ware(wt)) {
+					bo.built_mat_shortage_ = true;
+					continue;
+				}
+			}
+		}
+	}
+
 	// these are 3 helping variables
 	bool output_is_needed = false;
 	int16_t max_preciousness = 0;         // preciousness_ of most precious output
@@ -1113,7 +1167,7 @@
 						bo.stocklevel_ = get_stocklevel(bo);
 						bo.stocklevel_time = game().get_gametime();
 					}
-					if (bo.stocklevel_ > 40) {
+					if (bo.stocklevel_ > 50 + productionsites.size() * 5) {
 						continue;
 					}
 					prio += bf->ground_water_ - 2;
@@ -1261,7 +1315,7 @@
 						}
 
 						// sometimes all area is blocked by trees so this is to prevent this
-						if (buildable_fields.size() < 4) {
+						if (buildable_fields.size() < 4 && bo.total_count() > 0) {
 							continue;
 						}
 
@@ -1278,7 +1332,8 @@
 							continue;
 						}
 						// we can go above target if there is shortage of logs on stock
-						else if (bo.total_count() >= bo.cnt_target_ && bo.stocklevel_ > 40) {
+						else if (bo.total_count() >= bo.cnt_target_ &&
+						         bo.stocklevel_ > 40 + productionsites.size() * 5) {
 							continue;
 						}
 
@@ -1415,6 +1470,12 @@
 			}  // production sites done
 			else if (bo.type == BuildingObserver::MILITARYSITE) {
 
+				// we allow 1 exemption from big buildings prohibition
+				if (bo.built_mat_shortage_ &&
+				    (bo.cnt_under_construction_ > 0 || !(bf->enemy_nearby_))) {
+					continue;
+				}
+
 				if (!bf->unowned_land_nearby_) {
 					continue;
 				}
@@ -1440,7 +1501,7 @@
 				}  // it is ok, go on
 				else if (bf->unowned_land_nearby_ && bo.expansion_type_ &&
 				         num_milit_constructionsites <= 1) {
-					{;}  // we allow big buildings now
+					;  // we allow big buildings now
 				} else if (bf->unowned_land_nearby_ &&
 				           bo.expansion_type_) {  // decreasing probability for big buidlings
 					if (bo.desc->get_size() == 2 && gametime % 15 >= 1) {
@@ -1456,7 +1517,8 @@
 				}  // the building is not suitable for situation
 
 				// not to build so many military buildings nearby
-				if (!bf->enemy_nearby_ && bf->military_in_constr_nearby_ > 0) {
+				if (!bf->enemy_nearby_ &&
+				    (bf->military_in_constr_nearby_ + bf->military_unstationed_) > 0) {
 					continue;
 				}
 
@@ -1481,8 +1543,17 @@
 
 				// we need to prefer military building near to borders
 				// with enemy
-				if (bf->enemy_nearby_ && bf->military_capacity_ < 12) {
-					prio += 50 + (12 - bf->military_capacity_) * 20;
+				// Note: if we dont have enough mines, we cannot afford
+				// full-power defense (we need to preserve material and soldiers
+				// for expansion)
+				const int16_t bottom_treshold =
+				   15 - ((virtual_mines <= 4) ? (5 - virtual_mines) * 2 : 0);
+				if (bf->enemy_nearby_ && bf->military_capacity_ < bottom_treshold) {
+					prio += 50 + (bottom_treshold - bf->military_capacity_) * 20;
+				}
+
+				if (bf->enemy_nearby_ && bf->military_capacity_ > bottom_treshold + 4) {
+					prio -= (bf->military_capacity_ - (bottom_treshold + 4)) * 5;
 				}
 
 			} else if (bo.type == BuildingObserver::WAREHOUSE) {
@@ -1922,7 +1993,7 @@
 
 	// 1. first we collect all reachange points
 	std::vector<NearFlag> nearflags;
-	std::unordered_set<int32_t> lookuptable;
+	std::unordered_set<uint32_t> lookuptable;
 
 	FindNodeWithFlagOrRoad functor;
 	CheckStepRoadAI check(player_, MOVECAPS_WALK, true);
@@ -2234,6 +2305,15 @@
 							enbld = enhancement;
 							bestbld = &en_bo;
 						}
+
+						if ((static_cast<int32_t>(en_bo.current_stats_) > 85 &&
+						     en_bo.total_count() * 2 < site.bo->total_count()) ||
+						    (static_cast<int32_t>(en_bo.current_stats_) > 50 &&
+						     en_bo.total_count() * 4 < site.bo->total_count())) {
+
+							enbld = enhancement;
+							bestbld = &en_bo;
+						}
 					}
 				}
 			}
@@ -2312,7 +2392,7 @@
 			site.bo->stocklevel_ = get_stocklevel(*site.bo);
 			site.bo->stocklevel_time = game().get_gametime();
 		}
-		if (site.bo->stocklevel_ > 250) {  // dismantle
+		if (site.bo->stocklevel_ > 250 + productionsites.size() * 5) {  // dismantle
 			site.bo->last_dismantle_time_ = game().get_gametime();
 			flags_to_be_removed.push_back(site.site->base_flag().get_position());
 			game().send_player_dismantle(*site.site);
@@ -2425,7 +2505,9 @@
 			site.bo->stocklevel_time = game().get_gametime();
 		}
 
-		uint16_t score = site.bo->stocklevel_;
+		// logs can be stored also in productionsites, they are counted as on stock
+		// but are no available for random production site
+		int16_t score = site.bo->stocklevel_ - productionsites.size() * 5;
 
 		if (score > 200 && site.bo->cnt_built_ > site.bo->cnt_target_) {
 
@@ -2606,6 +2688,22 @@
 	return count;
 }
 
+// counts produced output on stock
+// if multiple outputs, it returns lowest value
+uint32_t DefaultAI::get_stocklevel(WareIndex wt) {
+	uint32_t count = 0;
+
+	for (EconomyObserver* observer : economies) {
+		// Don't check if the economy has no warehouse.
+		if (observer->economy.warehouses().empty()) {
+			continue;
+		}
+		count += observer->economy.stock_ware(wt);
+	}
+
+	return count;
+}
+
 /**
  * Updates the first military building in list and reenques it at the end of
  * the list afterwards. If a militarysite is in secure area but holds more than
@@ -2660,7 +2758,7 @@
 				game().send_player_militarysite_set_soldier_preference(
 				   *ms, MilitarySite::kPrefersRookies);
 			} else if (j > 1) {
-				game().send_player_change_soldier_capacity(*ms, -1);
+				game().send_player_change_soldier_capacity(*ms, (j > 2) ? -2 : -1);
 			}
 			// if the building is in inner land and other militarysites still
 			// hold the miliary influence of the field, consider to destruct the
@@ -2693,15 +2791,11 @@
 		}
 	} else {
 
-		// If an enemy is in sight and the number of stationed soldier is not
-		// at maximum - set it to maximum.
-		uint32_t const j = ms->max_soldier_capacity();
-		uint32_t const k = ms->soldier_capacity();
+		uint32_t const total_capacity = ms->max_soldier_capacity();
+		uint32_t const target_capacity = ms->soldier_capacity();
 
-		if (j > k) {
-			game().send_player_change_soldier_capacity(*ms, j - k);
-			changed = true;
-		}
+		game().send_player_change_soldier_capacity(*ms, total_capacity - target_capacity);
+		changed = true;
 
 		// and also set preference to Heroes
 		if (MilitarySite::kPrefersHeroes != ms->get_soldier_preference()) {
@@ -3057,9 +3151,27 @@
  *
  * \returns true, if attack was started.
  */
+
 bool DefaultAI::consider_attack(int32_t const gametime) {
+
+	// we presume that we are not attacking so we extend waitperiod
+	// in case of attack the variable will be decreased below
+	// this is intended to save some CPU and add randomness in attacking
+	// and also differentiate according to type
+	next_attack_waittime_ += gametime % 30;
+	if (next_attack_waittime_ > 600 && type_ == DEFENSIVE) {
+		next_attack_waittime_ = 20;
+	}
+	if (next_attack_waittime_ > 450 && type_ == NORMAL) {
+		next_attack_waittime_ = 20;
+	}
+	if (next_attack_waittime_ > 300 && type_ == AGGRESSIVE) {
+		next_attack_waittime_ = 20;
+	}
+
 	// Only useable, if it owns at least one militarysite
 	if (militarysites.empty()) {
+		next_attack_consideration_due_ = next_attack_waittime_ * 1000 + gametime;
 		return false;
 	}
 
@@ -3071,8 +3183,10 @@
 	PlayerNumber const nr_players = game().map().get_nrplayers();
 	player_attackable.resize(nr_players);
 	bool any_attackable = false;
-	bool any_attacked = false;
 	uint16_t const pn = player_number();
+	std::unordered_set<uint32_t> irrelevant_immovables;
+
+	std::vector<ImmovableFound> target_buildings;
 
 	// defining treshold ratio of own_strenght/enemy's_strength
 	uint32_t treshold_ratio = 100;
@@ -3087,6 +3201,28 @@
 
 	// receiving games statistics and parsing it (reading latest entry)
 	const Game::GeneralStatsVector& genstats = game().get_general_statistics();
+
+	// first we try to prevent exhaustion of military forces (soldiers)
+	// via excessive attacking
+	// before building an economy with mines.
+	// 'Margin' is an difference between count of actual soldiers and
+	// military sites to be manned.
+	// If we have no mines yet, we need to preserve some soldiers for further
+	// expansion (if enemy allows this)
+	int32_t needed_margin = (mines_.size() < 6) ?
+	                           (6 - mines_.size()) * 3 :
+	                           0 + 2 + (type_ == NORMAL) ? 4 : 0 + (type_ == DEFENSIVE) ? 8 : 0;
+	const int32_t current_margin =
+	   genstats[pn - 1].miltary_strength.back() - militarysites.size() - num_milit_constructionsites;
+
+	if (current_margin < needed_margin) {  // no attacking!
+		last_attack_target_.x = std::numeric_limits<uint16_t>::max();
+		last_attack_target_.y = std::numeric_limits<uint16_t>::max();
+		next_attack_consideration_due_ = next_attack_waittime_ * 1000 + gametime;
+		return false;
+	}
+
+	// now we test all players which one are 'attackable'
 	for (uint8_t j = 1; j <= plr_in_game; ++j) {
 		if (pn == j) {
 			player_attackable[j - 1] = false;
@@ -3106,147 +3242,143 @@
 		}
 	}
 
+	// if we cannot attack anybody, terminating...
 	if (!any_attackable) {
-		next_attack_consideration_due_ = 120 * 1000 + (gametime % 30 + 2) * 1000 + gametime;
+		next_attack_consideration_due_ = next_attack_waittime_ * 1000 + gametime;
+		last_attack_target_.x = std::numeric_limits<uint16_t>::max();
+		last_attack_target_.y = std::numeric_limits<uint16_t>::max();
 		return false;
 	}
 
-	// the logic of attacking is to pick n military buildings - random ones
-	// and test them for possible attack
-	const uint16_t attempts = militarysites.size() / 6 + 1;
+	// the logic of attacking is to pick n own military buildings - random ones
+	// and test the vicinity for attackable buildings
+	// candidates are put into target_buildings vector for later processing
+	const uint16_t test_every = 4;
 	Map& map = game().map();
-
-	uint16_t position = 0;
-	for (uint32_t i = 0; i < attempts && !any_attacked; ++i) {
-		position = (game().get_gametime() + (3 * i)) % militarysites.size();
-
-		// picking random military sites
-		// using gametime as a random value, but it is constant so each next is on position +3
-		// iterating over fields
-		// (std::vector would be much better here)
-
+	MilitarySite* best_ms_target = nullptr;
+	Warehouse* best_wh_target = nullptr;
+	int32_t best_ms_score = 0;
+	int32_t best_wh_score = 0;
+	const int8_t minimal_difference = 2;
+
+	for (uint32_t position = gametime % test_every; position < militarysites.size();
+	     position += test_every) {
+		// checked_own_ms_tmp += 1;
 		std::list<MilitarySiteObserver>::iterator mso = militarysites.begin();
 		std::advance(mso, position);
 
 		MilitarySite* ms = mso->site;
-		Building* target = ms;  // dummy initialisation to silence the compiler
 		uint32_t const vision = ms->descr().vision_range();
 		FCoords f = map.get_fcoords(ms->get_position());
-		int32_t chance = 0;
-		uint32_t attackers = 0;
-		uint32_t defenders = 0;
-		uint32_t defend_ready_enemies = 0;  // enemy soldiers that can come to defend the attacked
-		                                    // building (one soldier has to stay)
-
-		// skipping if based on  "enemies nearby" there are probably no enemies nearby
-		if (!mso->enemies_nearby && gametime % 8 > 0) {
-			continue;  // go on with next attempt
-		}
-
-		// setting as default
-		mso->enemies_nearby = false;
-
-		// Search in a radius of the vision of the militarysite and collect
-		// information about immovables in the area
+
+		// get list of immovable around this our military site
 		std::vector<ImmovableFound> immovables;
-		map.find_immovables(Area<FCoords>(f, vision), &immovables, FindImmovableAttackable());
+		map.find_immovables(Area<FCoords>(f, vision + 3), &immovables, FindImmovableAttackable());
 
 		for (uint32_t j = 0; j < immovables.size(); ++j) {
+
+			// skip if in irrelevant_immovables
+			const uint32_t hash = immovables.at(j).coords.x << 16 | immovables.at(j).coords.y;
+			if (irrelevant_immovables.count(hash) > 0) {
+				continue;
+			} else {
+				irrelevant_immovables.insert(hash);
+			}
+
+			// maybe these are not good candidates to attack
 			if (upcast(MilitarySite, bld, immovables.at(j).object)) {
-				if (!player_->is_hostile(bld->owner())) {
-					continue;
-				}
-
-				mso->enemies_nearby = true;
 
 				if (!player_attackable[bld->owner().player_number() - 1]) {
 					continue;
 				}
 
+				// in case this is the same building as previously attacked
+				if (last_attack_target_ == bld->get_position()) {
+					continue;
+				}
+
 				if (bld->can_attack()) {
 
-					// any_attackable_building=true;
-
-					int32_t ta = player_->find_attack_soldiers(bld->base_flag());
-
-					if (type_ == NORMAL) {
-						ta = ta * 2 / 3;
-					}
-
-					if (ta < 1) {
-						continue;
-					}
-
-					int32_t const tc = ta - bld->present_soldiers().size();
-
-					if (bld->present_soldiers().size() > 1) {
-						defend_ready_enemies += bld->present_soldiers().size() - 1;
-					}
-
-					if (tc > chance) {
-						target = bld;
-						chance = tc;
-						attackers = ta;
-						defenders = bld->present_soldiers().size();
-					}
+					int32_t attack_soldiers = player_->find_attack_soldiers(bld->base_flag());
+					if (attack_soldiers < 1) {
+						continue;
+					}
+
+					// target_buildings.push_back(immovables.at(j));
+					const int32_t soldiers_difference =
+					   player_->find_attack_soldiers(bld->base_flag()) - bld->present_soldiers().size();
+
+					if (soldiers_difference < minimal_difference)
+						continue;
+					if (soldiers_difference <= best_ms_score)
+						continue;
+
+					best_ms_target = bld;
+					best_ms_score = soldiers_difference;
+					continue;
 				}
 			} else if (upcast(Warehouse, wh, immovables.at(j).object)) {
 				if (!player_->is_hostile(wh->owner())) {
 					continue;
 				}
 
+				// in case this is the same building as previously attacked
+				if (last_attack_target_ == wh->get_position()) {
+					continue;
+				}
+
 				if (wh->can_attack()) {
-					int32_t ta = player_->find_attack_soldiers(wh->base_flag());
-
-					if (ta < 1) {
-						continue;
-					}
-
-					// extra priority push!
-					int32_t tc = ta * 2;
-
-					// we presume that there are no soldiers in warehouse
-					// after long fights this tend to be true :)
-
-					if (tc > chance) {
-						target = wh;
-						chance = tc;
-						attackers = ta;
-						defenders = 0;
-					}
+					int32_t attack_soldiers = player_->find_attack_soldiers(wh->base_flag());
+					if (attack_soldiers < 1) {
+						continue;
+					}
+
+					const int32_t soldiers_difference = player_->find_attack_soldiers(wh->base_flag()) -
+					                                    wh->present_soldiers().size() +
+					                                    3;  //+3 is to boost attack here
+
+					if (soldiers_difference < minimal_difference)
+						continue;
+					if (soldiers_difference <= best_wh_score)
+						continue;
+
+					best_wh_target = wh;
+					best_wh_score = soldiers_difference;
 				}
 			}
-
-			// here we consider enemy soldiers in near buildings.
-			int32_t penalty;
-			if (defend_ready_enemies > 0) {
-				penalty = (defenders * 100) / 5 * (defend_ready_enemies * 100) / 10 * 10 / 100;
-			} else {
-				penalty = 0;
-			}
-
-			// Return if chance to win is too low
-			if ((chance - penalty / 100) < 2) {
-				continue;
-			}
-
-			game().send_player_enemyflagaction(target->base_flag(), pn, attackers);
-
-			any_attacked = true;
-			break;
-		}
-	}
-
-	//  Do not attack again too soon - returning soldiers must get healed first.
-	if (any_attacked) {
-		next_attack_consideration_due_ = (gametime % 40 + 10) * 1000 + gametime;
-	} else {
-		next_attack_consideration_due_ = (gametime % 80 + 10) * 1000 + gametime;
-	}
-
-	if (any_attacked) {
-		return true;
-	} else {
+		}
+	}
+
+	// we allways try to attack warehouse first
+	if (best_wh_target != nullptr && gametime % 2 == 0) {
+		// attacking with defenders + 4 soldiers
+		int32_t attackers = player_->find_attack_soldiers(best_wh_target->base_flag());
+
+		game().send_player_enemyflagaction(best_wh_target->base_flag(), pn, attackers);
+		last_attack_target_ = best_wh_target->get_position();
+		next_attack_consideration_due_ = (gametime % 10 + 10) * 1000 + gametime;
+		next_attack_waittime_ = 10;
+		return true;
+
+	} else if (best_ms_target != nullptr) {
+
+		// attacking with defenders + 4 soldiers
+		int32_t attackers = player_->find_attack_soldiers(best_ms_target->base_flag());
+		const int32_t defenders = best_ms_target->present_soldiers().size();
+		if (attackers > defenders + 10) {  // we need to leave meaningful count of soldiers
+			// for next attack
+			attackers = defenders + 6;
+		}
+
+		game().send_player_enemyflagaction(best_ms_target->base_flag(), pn, attackers);
+		last_attack_target_ = best_ms_target->get_position();
+		next_attack_consideration_due_ = (gametime % 10 + 10) * 1000 + gametime;
+		next_attack_waittime_ = 10;
+		return true;
+	} else {
+		next_attack_consideration_due_ = next_attack_waittime_ * 1000 + gametime;
+		last_attack_target_.x = std::numeric_limits<uint16_t>::max();
+		last_attack_target_.y = std::numeric_limits<uint16_t>::max();
 		return false;
 	}
 }

=== modified file 'src/ai/defaultai.h'
--- src/ai/defaultai.h	2014-10-08 18:06:06 +0000
+++ src/ai/defaultai.h	2014-10-17 19:46:00 +0000
@@ -143,7 +143,7 @@
 	bool check_militarysites(int32_t);
 	uint32_t get_stocklevel_by_hint(size_t);
 	uint32_t get_stocklevel(BuildingObserver&);
-	uint32_t get_stocklevel(size_t);  // count all direct outputs_
+	uint32_t get_stocklevel(Widelands::WareIndex);  // count all direct outputs_
 	void check_helpersites(int32_t);
 
 	int32_t recalc_with_border_range(const BuildableField&, int32_t);
@@ -229,7 +229,10 @@
 	uint16_t military_last_dismantle_;
 	int32_t military_last_build_;  // sometimes expansions just stops, this is time of last military
 	                               // building build
-	int32_t spots_;                // sum of buildable fields
+	Widelands::Coords
+	   last_attack_target_;         // flag to abuilding (position) that was attacked last time
+	int32_t next_attack_waittime_;  // second till the next attack consideration
+	int32_t spots_;                 // sum of buildable fields
 
 	std::unique_ptr<Notifications::Subscriber<Widelands::NoteFieldPossession>>
 	   field_possession_subscriber_;


Follow ups