1 /*
2    Copyright (C) 2009 - 2018 by Yurii Chernyi <terraninfo@terraninfo.net>
3    Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2 of the License, or
8    (at your option) any later version.
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY.
11 
12    See the COPYING file for more details.
13 */
14 
15 /**
16  * Default AI (Testing)
17  * @file
18  */
19 
20 #include "ai/default/ca.hpp"
21 #include "ai/actions.hpp"
22 #include "ai/manager.hpp"
23 #include "ai/composite/engine.hpp"
24 #include "ai/composite/rca.hpp"
25 #include "ai/composite/stage.hpp"
26 #include "game_board.hpp"
27 #include "game_classification.hpp"
28 #include "game_data.hpp"
29 #include "log.hpp"
30 #include "map/map.hpp"
31 #include "resources.hpp"
32 #include "team.hpp"
33 #include "units/unit.hpp"
34 #include "pathfind/pathfind.hpp"
35 #include "pathfind/teleport.hpp"
36 
37 #include <numeric>
38 #include <boost/dynamic_bitset.hpp>
39 
40 #include <SDL2/SDL_timer.h>
41 
42 static lg::log_domain log_ai_testing_ai_default("ai/ca/testing_ai_default");
43 #define DBG_AI_TESTING_AI_DEFAULT LOG_STREAM(debug, log_ai_testing_ai_default)
44 #define LOG_AI_TESTING_AI_DEFAULT LOG_STREAM(info, log_ai_testing_ai_default)
45 #define WRN_AI_TESTING_AI_DEFAULT LOG_STREAM(warn, log_ai_testing_ai_default)
46 #define ERR_AI_TESTING_AI_DEFAULT LOG_STREAM(err, log_ai_testing_ai_default)
47 
48 
49 namespace ai {
50 
51 namespace ai_default_rca {
52 
53 //==============================================================
54 
goto_phase(rca_context & context,const config & cfg)55 goto_phase::goto_phase( rca_context &context, const config &cfg )
56 	: candidate_action(context,cfg)
57 	, move_()
58 {
59 }
60 
~goto_phase()61 goto_phase::~goto_phase()
62 {
63 }
64 
evaluate()65 double goto_phase::evaluate()
66 {
67 	// Execute goto-movements - first collect gotos in a list
68 	std::vector<map_location> gotos;
69 	unit_map &units_ = resources::gameboard->units();
70 	const gamemap &map_ = resources::gameboard->map();
71 
72 	for(unit_map::iterator ui = units_.begin(); ui != units_.end(); ++ui) {
73 		if (ui->get_goto() == ui->get_location()) {
74 			ui->set_goto(map_location());
75 		} else if (ui->side() == get_side() && map_.on_board(ui->get_goto())) {
76 			gotos.push_back(ui->get_location());
77 		}
78 	}
79 
80 	for(std::vector<map_location>::const_iterator g = gotos.begin(); g != gotos.end(); ++g) {
81 		unit_map::const_iterator ui = units_.find(*g);
82 		// passive_leader: never moves or attacks
83 		if(ui->can_recruit() && get_passive_leader() && !get_passive_leader_shares_keep()){
84 			continue;//@todo: only bail out if goto is on keep
85 		}
86 		// end of passive_leader
87 
88 		const pathfind::shortest_path_calculator calc(*ui, current_team(), resources::gameboard->teams(), resources::gameboard->map());
89 
90 		const pathfind::teleport_map allowed_teleports = pathfind::get_teleport_locations(*ui, current_team());
91 
92 		pathfind::plain_route route;
93 		route = pathfind::a_star_search(ui->get_location(), ui->get_goto(), 10000.0, calc, map_.w(), map_.h(), &allowed_teleports);
94 
95 		if (!route.steps.empty()){
96 			move_ = check_move_action(ui->get_location(), route.steps.back(), true, true);
97 		} else {
98 			// there is no direct path (yet)
99 			// go to the nearest hex instead.
100 			// maybe a door will open later or something
101 
102 			int closest_distance = -1;
103 			std::pair<map_location,map_location> closest_move;
104 			for(move_map::const_iterator i = get_dstsrc().begin(); i != get_dstsrc().end(); ++i) {
105 				if(i->second != ui->get_location()) {
106 						continue;
107 				}
108 				int distance = distance_between(i->first,ui->get_goto());
109 				if(closest_distance == -1 || distance < closest_distance) {
110 					closest_distance = distance;
111 					closest_move = *i;
112 				}
113 			}
114 			if(closest_distance != -1) {
115 				move_ = check_move_action(ui->get_location(), closest_move.first);
116 			} else {
117 				continue;
118 			}
119 		}
120 
121 		if (move_->is_ok()) {
122 			return get_score();
123 		}
124 	}
125 
126 	return BAD_SCORE;
127 }
128 
execute()129 void goto_phase::execute()
130 {
131 	if (!move_) {
132 		return;
133 	}
134 
135 	move_->execute();
136 	if (!move_->is_ok()){
137 		LOG_AI_TESTING_AI_DEFAULT << get_name() << "::execute not ok" << std::endl;
138 	}
139 
140 	// In some situations, a theoretically possible path is blocked by allies,
141 	// resulting in the unit not moving. In this case, we remove all remaining
142 	// movement from the unit in order to prevent blacklisting of the CA.
143 	if (!move_->is_gamestate_changed()){
144 		LOG_AI_TESTING_AI_DEFAULT << get_name() << "::execute did not move unit; removing moves instead" << std::endl;
145 		stopunit_result_ptr stopunit = check_stopunit_action(move_->get_unit_location(), true, false);
146 		stopunit->execute();
147 	}
148 }
149 
150 
151 //==============================================================
152 
combat_phase(rca_context & context,const config & cfg)153 combat_phase::combat_phase( rca_context &context, const config &cfg )
154 	: candidate_action(context,cfg),best_analysis_(),choice_rating_(-1000.0)
155 {
156 }
157 
~combat_phase()158 combat_phase::~combat_phase()
159 {
160 }
161 
evaluate()162 double combat_phase::evaluate()
163 {
164 	std::vector<std::string> options = get_recruitment_pattern();
165 
166 	choice_rating_ = -1000.0;
167 	int ticks = SDL_GetTicks();
168 
169 	const std::vector<attack_analysis> analysis = get_attacks(); //passive_leader: in aspect_attacks::analyze_targets()
170 
171 	int time_taken = SDL_GetTicks() - ticks;
172 	LOG_AI_TESTING_AI_DEFAULT << "took " << time_taken << " ticks for " << analysis.size()
173 		<< " positions. Analyzing...\n";
174 
175 	ticks = SDL_GetTicks();
176 
177 	const int max_sims = 50000;
178 	int num_sims = analysis.empty() ? 0 : max_sims/analysis.size();
179 	if(num_sims < 20)
180 		num_sims = 20;
181 	if(num_sims > 40)
182 		num_sims = 40;
183 
184 	LOG_AI_TESTING_AI_DEFAULT << "simulations: " << num_sims << "\n";
185 
186 	const int max_positions = 30000;
187 	const int skip_num = analysis.size()/max_positions;
188 
189 	std::vector<attack_analysis>::const_iterator choice_it = analysis.end();
190 	for(std::vector<attack_analysis>::const_iterator it = analysis.begin();
191 			it != analysis.end(); ++it) {
192 
193 		if(skip_num > 0 && ((it - analysis.begin())%skip_num) && it->movements.size() > 1)
194 			continue;
195 
196 		const double rating = it->rating(get_aggression(),*this);
197 		LOG_AI_TESTING_AI_DEFAULT << "attack option rated at " << rating << " ("
198 					  << (it->uses_leader ? get_leader_aggression() : get_aggression()) << ")\n";
199 
200 		if(rating > choice_rating_) {
201 			choice_it = it;
202 			choice_rating_ = rating;
203 		}
204 	}
205 
206 	time_taken = SDL_GetTicks() - ticks;
207 	LOG_AI_TESTING_AI_DEFAULT << "analysis took " << time_taken << " ticks\n";
208 
209 
210 	// suokko tested the rating against current_team().caution()
211 	// Bad mistake -- the AI became extremely reluctant to attack anything.
212 	// Documenting this in case someone has this bright idea again...*don't*...
213 	if(choice_rating_ > 0.0) {
214 		best_analysis_ = *choice_it;
215 		return get_score();
216 	} else {
217 		return BAD_SCORE;
218 	}
219 }
220 
execute()221 void combat_phase::execute()
222 {
223 	assert(choice_rating_ > 0.0);
224 	map_location from   = best_analysis_.movements[0].first;
225 	map_location to     = best_analysis_.movements[0].second;
226 	map_location target_loc = best_analysis_.target;
227 
228 	if (from!=to) {
229 		move_result_ptr move_res = execute_move_action(from,to,false);
230 		if (!move_res->is_ok()) {
231 			LOG_AI_TESTING_AI_DEFAULT << get_name() << "::execute not ok, move failed" << std::endl;
232 			return;
233 		}
234 	}
235 
236 	attack_result_ptr attack_res = check_attack_action(to, target_loc, -1);
237 	if (!attack_res->is_ok()) {
238 		LOG_AI_TESTING_AI_DEFAULT << get_name() << "::execute not ok, attack cancelled" << std::endl;
239 	} else {
240 		attack_res->execute();
241 		if (!attack_res->is_ok()) {
242 			LOG_AI_TESTING_AI_DEFAULT << get_name() << "::execute not ok, attack failed" << std::endl;
243 		}
244 	}
245 
246 }
247 
248 //==============================================================
249 
move_leader_to_goals_phase(rca_context & context,const config & cfg)250 move_leader_to_goals_phase::move_leader_to_goals_phase( rca_context &context, const config &cfg )
251 	: candidate_action(context,cfg), auto_remove_(), dst_(), id_(), move_()
252 {
253 }
254 
~move_leader_to_goals_phase()255 move_leader_to_goals_phase::~move_leader_to_goals_phase()
256 {
257 }
258 
evaluate()259 double move_leader_to_goals_phase::evaluate()
260 {
261 
262 	const config &goal = get_leader_goal();
263 	//passive leader can reach a goal
264 	if (!goal) {
265 		LOG_AI_TESTING_AI_DEFAULT << get_name() << "No goal found\n";
266 		return BAD_SCORE;
267 	}
268 
269 	if (goal.empty()) {
270 		LOG_AI_TESTING_AI_DEFAULT << get_name() << "Empty goal found\n";
271 		return BAD_SCORE;
272 	}
273 
274 	double max_risk = goal["max_risk"].to_double(1 - get_caution());
275 	auto_remove_ = goal["auto_remove"].to_bool();
276 
277 	dst_ = map_location(goal, resources::gamedata);
278 	if (!dst_.valid()) {
279 		ERR_AI_TESTING_AI_DEFAULT << "Invalid goal: "<<std::endl<<goal;
280 		return BAD_SCORE;
281 	}
282 
283 	const unit_map::iterator leader = resources::gameboard->units().find_leader(get_side());
284 	if (!leader.valid() || leader->incapacitated()) {
285 		WRN_AI_TESTING_AI_DEFAULT << "Leader not found" << std::endl;
286 		return BAD_SCORE;
287 	}
288 
289 	id_ = goal["id"].str();
290 	if (leader->get_location() == dst_) {
291 		//goal already reached
292 		if (auto_remove_ && !id_.empty()) {
293 			remove_goal(id_);
294 		} else {
295 			move_ = check_move_action(leader->get_location(), leader->get_location(), !auto_remove_);//we do full moves if we don't want to remove goal
296 			if (move_->is_ok()) {
297 				return get_score();
298 			} else {
299 				return BAD_SCORE;
300 			}
301 		}
302 	}
303 
304 	pathfind::shortest_path_calculator calc(*leader, current_team(), resources::gameboard->teams(), resources::gameboard->map());
305 	const pathfind::teleport_map allowed_teleports = pathfind::get_teleport_locations(*leader, current_team());
306 	pathfind::plain_route route = a_star_search(leader->get_location(), dst_, 1000.0, calc,
307 			resources::gameboard->map().w(), resources::gameboard->map().h(), &allowed_teleports);
308 	if(route.steps.empty()) {
309 		LOG_AI_TESTING_AI_DEFAULT << "route empty";
310 		return BAD_SCORE;
311 	}
312 
313 	const pathfind::paths leader_paths(*leader, false, true, current_team());
314 
315 	std::map<map_location,pathfind::paths> possible_moves;
316 	possible_moves.emplace(leader->get_location(), leader_paths);
317 
318 	map_location loc;
319 	for (const map_location &l : route.steps)
320 	{
321 		if (leader_paths.destinations.contains(l) &&
322 		    power_projection(l, get_enemy_dstsrc()) < leader->hitpoints() * max_risk)
323 		{
324 			loc = l;
325 		}
326 	}
327 
328 	if(loc.valid()) {
329 		move_ = check_move_action(leader->get_location(), loc, false);
330 		if (move_->is_ok()) {
331 			return get_score();
332 		}
333 	}
334 	return BAD_SCORE;
335 
336 }
337 
execute()338 void move_leader_to_goals_phase::execute()
339 {
340 	move_->execute();
341 	if (!move_->is_ok()){
342 		LOG_AI_TESTING_AI_DEFAULT << get_name() << "::execute not ok" << std::endl;
343 	}
344 	if (move_->get_unit_location()==dst_) {
345 		//goal already reached
346 		if (auto_remove_ && !id_.empty()) {
347 			remove_goal(id_);
348 		}
349 	}
350 }
351 
remove_goal(const std::string & id)352 void move_leader_to_goals_phase::remove_goal(const std::string &id)
353 {
354 	config mod_ai;
355 	mod_ai["side"] = get_side();
356 	mod_ai["path"] = "aspect[leader_goal].facet["+id+"]";
357 	mod_ai["action"] = "delete";
358 	manager::get_singleton().modify_active_ai_for_side(get_side(), mod_ai);
359 }
360 
361 //==============================================================
362 
move_leader_to_keep_phase(rca_context & context,const config & cfg)363 move_leader_to_keep_phase::move_leader_to_keep_phase( rca_context &context, const config &cfg )
364 	: candidate_action(context,cfg),move_()
365 {
366 
367 }
368 
~move_leader_to_keep_phase()369 move_leader_to_keep_phase::~move_leader_to_keep_phase()
370 {
371 
372 }
373 
evaluate()374 double move_leader_to_keep_phase::evaluate()
375 {
376 	if (get_leader_ignores_keep()) {
377 		return BAD_SCORE;
378 	}
379 	if (get_passive_leader() && !get_passive_leader_shares_keep()) {
380 		return BAD_SCORE;
381 	}
382 
383 	// 1. Collect all leaders in a list
384 	// 2. Get the suitable_keep for each leader
385 	// 3. Choose the leader with the nearest suitable_keep (and which still have moves)
386 	// 4. If leader can reach this keep in 1 turn -> set move_ to there
387 	// 5. If not -> Calculate the best move_ (use a-star search)
388 	// 6. Save move_ for execution
389 
390 	// 1.
391 	const unit_map &units_ = resources::gameboard->units();
392 	const std::vector<unit_map::const_iterator> leaders = units_.find_leaders(get_side());
393 	if (leaders.empty()) {
394 		return BAD_SCORE;
395 	}
396 
397 	// 2. + 3.
398 	const unit* best_leader = nullptr;
399 	map_location best_keep;
400 	int shortest_distance = 99999;
401 
402 	for (const unit_map::const_iterator& leader : leaders) {
403 		if (leader->incapacitated() || leader->movement_left() == 0) {
404 			continue;
405 		}
406 
407 		// Find where the leader can move
408 		const ai::moves_map &possible_moves = get_possible_moves();
409 		const ai::moves_map::const_iterator& p_it = possible_moves.find(leader->get_location());
410 		if (p_it == possible_moves.end()) {
411 			return BAD_SCORE;
412 		}
413 		const pathfind::paths leader_paths = p_it->second;
414 
415 		const map_location& keep = suitable_keep(leader->get_location(), leader_paths);
416 		if (keep == map_location::null_location() || keep == leader->get_location()) {
417 			continue;
418 		}
419 
420 		const pathfind::shortest_path_calculator calc(*leader, current_team(), resources::gameboard->teams(), resources::gameboard->map());
421 
422 		const pathfind::teleport_map allowed_teleports = pathfind::get_teleport_locations(*leader, current_team());
423 
424 		pathfind::plain_route route;
425 		route = pathfind::a_star_search(leader->get_location(), keep, 10000.0, calc, resources::gameboard->map().w(), resources::gameboard->map().h(), &allowed_teleports);
426 
427 		if (!route.steps.empty() || route.move_cost < shortest_distance) {
428 			best_leader = &(*leader);
429 			best_keep = keep;
430 			shortest_distance = route.move_cost;
431 		}
432 	}
433 
434 	if (best_leader == nullptr) {
435 		return BAD_SCORE;
436 	}
437 
438 	// 4.
439 	const unit* leader = best_leader;
440 	const map_location keep = best_keep;
441 	const pathfind::paths leader_paths(*leader, false, true, current_team());
442 	const pathfind::shortest_path_calculator calc(*leader, current_team(), resources::gameboard->teams(), resources::gameboard->map());
443 	const pathfind::teleport_map allowed_teleports = pathfind::get_teleport_locations(*leader, current_team());
444 
445 	if (leader_paths.destinations.contains(keep) && units_.count(keep) == 0) {
446 		move_ = check_move_action(leader->get_location(), keep, false);
447 		if (move_->is_ok()) {
448 			return get_score();
449 		}
450 	}
451 
452 	// 5.
453 	// The leader can't move to his keep, try to move to the closest location
454 	// to the keep where there are no enemies in range.
455 	// Make a map of the possible locations the leader can move to,
456 	// ordered by the distance from the keep.
457 	typedef std::multimap<int, map_location> ordered_locations;
458 	ordered_locations moves_toward_keep;
459 
460 	pathfind::plain_route route;
461 	route = pathfind::a_star_search(leader->get_location(), keep, 10000.0, calc, resources::gameboard->map().w(), resources::gameboard->map().h(), &allowed_teleports);
462 
463 	// find next hop
464 	map_location next_hop = map_location::null_location();
465 	int next_hop_cost = 0;
466 	for (const map_location& step : route.steps) {
467 		if (leader_paths.destinations.contains(step) && units_.count(step) == 0) {
468 			next_hop = step;
469 			next_hop_cost += leader->movement_cost(resources::gameboard->map().get_terrain(step));
470 		}
471 	}
472 	if (next_hop == map_location::null_location()) {
473 		return BAD_SCORE;
474 	}
475 	//define the next hop to have the lowest cost (0)
476 	moves_toward_keep.emplace(0, next_hop);
477 
478 	for (const pathfind::paths::step &dest : leader_paths.destinations) {
479 		if (!units_.find(dest.curr).valid()) {
480 			route = pathfind::a_star_search(dest.curr, next_hop, 10000.0, calc,
481 					resources::gameboard->map().w(), resources::gameboard->map().h(), &allowed_teleports);
482 			if (route.move_cost < next_hop_cost) {
483 				moves_toward_keep.emplace(route.move_cost, dest.curr);
484 			}
485 		}
486 	}
487 
488 	// Find the first location which we can move to,
489 	// without the threat of enemies.
490 	for (const ordered_locations::value_type& pair : moves_toward_keep) {
491 		const map_location& loc = pair.second;
492 		if (get_enemy_dstsrc().count(loc) == 0) {
493 			move_ = check_move_action(leader->get_location(), loc, true);
494 			if (move_->is_ok()) {
495 				return get_score();
496 			}
497 		}
498 	}
499 	return BAD_SCORE;
500 }
501 
execute()502 void move_leader_to_keep_phase::execute()
503 {
504 	move_->execute();
505 	if (!move_->is_ok()) {
506 		LOG_AI_TESTING_AI_DEFAULT <<  get_name() <<"::execute not ok" << std::endl;
507 	}
508 }
509 
510 //==============================================================
511 
get_villages_phase(rca_context & context,const config & cfg)512 get_villages_phase::get_villages_phase( rca_context &context, const config &cfg )
513 	: candidate_action(context,cfg)
514 	, keep_loc_()
515 	, leader_loc_()
516 	, best_leader_loc_()
517 	, debug_(false)
518 	, moves_()
519 {
520 }
521 
~get_villages_phase()522 get_villages_phase::~get_villages_phase()
523 {
524 }
525 
evaluate()526 double get_villages_phase::evaluate()
527 {
528 	moves_.clear();
529 	unit_map::const_iterator leader = resources::gameboard->units().find_leader(get_side());
530 	get_villages(get_dstsrc(),get_enemy_dstsrc(),leader);
531 	if (!moves_.empty()) {
532 		return get_score();
533 	}
534 	return BAD_SCORE;
535 }
536 
537 
execute()538 void get_villages_phase::execute()
539 {
540 	unit_map &units_ = resources::gameboard->units();
541 	unit_map::const_iterator leader = units_.find_leader(get_side());
542 	// Move all the units to get villages, however move the leader last,
543 	// so that the castle will be cleared if it wants to stop to recruit along the way.
544 	std::pair<map_location,map_location> leader_move;
545 
546 	for(tmoves::const_iterator i = moves_.begin(); i != moves_.end(); ++i) {
547 
548 		if(leader != units_.end() && leader->get_location() == i->second) {
549 			leader_move = *i;
550 		} else {
551 			if (resources::gameboard->find_visible_unit(i->first, current_team()) == units_.end()) {
552 				move_result_ptr move_res = execute_move_action(i->second,i->first,true);
553 				if (!move_res->is_ok()) {
554 					return;
555 				}
556 
557 				const map_location loc = move_res->get_unit_location();
558 				leader = units_.find_leader(get_side());
559 				const unit_map::const_iterator new_unit = units_.find(loc);
560 
561 				if (new_unit != units_.end() &&
562 				    power_projection(i->first, get_enemy_dstsrc()) >= new_unit->hitpoints() / 4.0)
563 				{
564 					LOG_AI_TESTING_AI_DEFAULT << "found support target... " << new_unit->get_location() << '\n';
565 					//FIXME: suokko tweaked the constant 1.0 to the formula:
566 					//25.0* current_team().caution() * power_projection(loc,enemy_dstsrc) / new_unit->second.hitpoints()
567 					//Is this an improvement?
568 
569 					///@todo 1.7 check if this an improvement
570 					//add_target(target(new_unit->first,1.0,target::SUPPORT));
571 				}
572 			}
573 		}
574 	}
575 
576 	if(leader_move.second.valid()) {
577 		if((resources::gameboard->find_visible_unit(leader_move.first , current_team()) == units_.end())
578 		   && resources::gameboard->map().is_village(leader_move.first)) {
579 			move_result_ptr move_res = execute_move_action(leader_move.second,leader_move.first,true);
580 			if (!move_res->is_ok()) {
581 				return;
582 			}
583 		}
584 	}
585 
586 	return;
587 }
588 
get_villages(const move_map & dstsrc,const move_map & enemy_dstsrc,unit_map::const_iterator & leader)589 void get_villages_phase::get_villages(
590 		const move_map& dstsrc, const move_map& enemy_dstsrc,
591 		unit_map::const_iterator &leader)
592 {
593 	DBG_AI_TESTING_AI_DEFAULT << "deciding which villages we want...\n";
594 	unit_map &units_ = resources::gameboard->units();
595 	const int ticks = SDL_GetTicks();
596 	best_leader_loc_ = map_location::null_location();
597 	if(leader != units_.end()) {
598 		keep_loc_ = nearest_keep(leader->get_location());
599 		leader_loc_ = leader->get_location();
600 	} else {
601 		keep_loc_ = map_location::null_location();
602 		leader_loc_ = map_location::null_location();
603 	}
604 
605 	debug_ = !lg::debug().dont_log(log_ai_testing_ai_default);
606 
607 	// Find our units who can move.
608 	treachmap reachmap;
609 	for(unit_map::const_iterator u_itor = units_.begin();
610 			u_itor != units_.end(); ++u_itor) {
611 		if(u_itor->can_recruit() && get_passive_leader()){
612 			continue;
613 		}
614 		if(u_itor->side() == get_side() && u_itor->movement_left()) {
615 			reachmap.emplace(u_itor->get_location(),	std::vector<map_location>());
616 		}
617 	}
618 
619 
620 	DBG_AI_TESTING_AI_DEFAULT << reachmap.size() << " units found who can try to capture a village.\n";
621 
622 	find_villages(reachmap, moves_, dstsrc, enemy_dstsrc);
623 
624 	treachmap::iterator itor = reachmap.begin();
625 	while(itor != reachmap.end()) {
626 		if(itor->second.empty()) {
627 			itor = remove_unit(reachmap, moves_, itor);
628 		} else {
629 			++itor;
630 		}
631 	}
632 
633 	if(!reachmap.empty()) {
634 		DBG_AI_TESTING_AI_DEFAULT << reachmap.size() << " units left after removing the ones who "
635 			"can't reach a village, send the to the dispatcher.\n";
636 
637 		dump_reachmap(reachmap);
638 
639 		dispatch(reachmap, moves_);
640 	} else {
641 		DBG_AI_TESTING_AI_DEFAULT << "No more units left after removing the ones who can't reach a village.\n";
642 	}
643 
644 	LOG_AI_TESTING_AI_DEFAULT << "Village assignment done: " << (SDL_GetTicks() - ticks)
645 		<< " ms, resulted in " << moves_.size() << " units being dispatched.\n";
646 
647 }
648 
find_villages(treachmap & reachmap,tmoves & moves,const std::multimap<map_location,map_location> & dstsrc,const std::multimap<map_location,map_location> & enemy_dstsrc)649 void get_villages_phase::find_villages(
650 	treachmap& reachmap,
651 	tmoves& moves,
652 	const std::multimap<map_location,map_location>& dstsrc,
653 	const std::multimap<map_location,map_location>& enemy_dstsrc)
654 
655 {
656 	std::map<map_location, double> vulnerability;
657 
658 	const bool passive_leader = get_passive_leader();
659 
660 	size_t min_distance = 100000;
661 	const gamemap &map_ = resources::gameboard->map();
662 	std::vector<team> &teams_ = resources::gameboard->teams();
663 
664 	// When a unit is dispatched we need to make sure we don't
665 	// dispatch this unit a second time, so store them here.
666 	std::vector<map_location> dispatched_units;
667 	for(std::multimap<map_location, map_location>::const_iterator
668 			j = dstsrc.begin();
669 			j != dstsrc.end(); ++j) {
670 
671 		const map_location &current_loc = j->first;
672 
673 		if(j->second == leader_loc_) {
674 			if(passive_leader) {
675 				continue;
676 			}
677 
678 			const size_t distance = distance_between(keep_loc_, current_loc);
679 			if(distance < min_distance) {
680 				min_distance = distance;
681 				best_leader_loc_ = current_loc;
682 			}
683 		}
684 
685 		if(std::find(dispatched_units.begin(), dispatched_units.end(),
686 				j->second) != dispatched_units.end()) {
687 			continue;
688 		}
689 
690 		if(map_.is_village(current_loc) == false) {
691 			continue;
692 		}
693 
694 		bool want_village = true, owned = false;
695 		for(size_t n = 0; n != teams_.size(); ++n) {
696 			owned = teams_[n].owns_village(current_loc);
697 			if(owned && !current_team().is_enemy(n+1)) {
698 				want_village = false;
699 			}
700 
701 			if(owned) {
702 				break;
703 			}
704 		}
705 
706 		if(want_village == false) {
707 			continue;
708 		}
709 
710 		// If it is a neutral village, and we have no leader,
711 		// then the village is of no use to us, and we don't want it.
712 		if(!owned && leader_loc_ == map_location::null_location()) {
713 			continue;
714 		}
715 
716 		double threat = 0.0;
717 		const std::map<map_location,double>::const_iterator vuln = vulnerability.find(current_loc);
718 		if(vuln != vulnerability.end()) {
719 			threat = vuln->second;
720 		} else {
721 			threat = power_projection(current_loc,enemy_dstsrc);
722 			vulnerability.emplace(current_loc, threat);
723 		}
724 
725 		const unit_map::const_iterator u = resources::gameboard->units().find(j->second);
726 		if (u == resources::gameboard->units().end() || u->get_state("guardian")) {
727 			continue;
728 		}
729 
730 		const unit  &un = *u;
731 		//FIXME: suokko turned this 2:1 to 1.5:1.0.
732 		//and dropped the second term of the multiplication.  Is that better?
733 		//const double threat_multipler = (current_loc == leader_loc?2:1) * current_team().caution() * 10;
734 		if(un.hitpoints() < (threat*2*un.defense_modifier(map_.get_terrain(current_loc)))/100) {
735 			continue;
736 		}
737 
738 		// If the next and previous destination differs from our current destination,
739 		// we're the only one who can reach the village -> dispatch.
740 		std::multimap<map_location, map_location>::const_iterator next = j;
741 		++next; // j + 1 fails
742 		const bool at_begin = (j == dstsrc.begin());
743 		std::multimap<map_location, map_location>::const_iterator prev = j; //FIXME seems not to work
744 		if(!at_begin) {
745 			--prev;
746 		}
747 #if 1
748 		if((next == dstsrc.end() || next->first != current_loc)
749 				&& (at_begin || prev->first != current_loc)) {
750 
751 			move_result_ptr move_check_res = check_move_action(j->second,j->first,true);
752 			if (move_check_res->is_ok()) {
753 				DBG_AI_TESTING_AI_DEFAULT << "Dispatched unit at " << j->second << " to village " << j->first << '\n';
754 				moves.emplace_back(j->first, j->second);
755 			}
756 			reachmap.erase(j->second);
757 			dispatched_units.push_back(j->second);
758 			continue;
759 		}
760 #endif
761 		reachmap[j->second].push_back(current_loc);
762 	}
763 
764 	DBG_AI_TESTING_AI_DEFAULT << moves.size() << " units already dispatched, "
765 		<< reachmap.size() << " left to evaluate.\n";
766 }
767 
dispatch(treachmap & reachmap,tmoves & moves)768 void get_villages_phase::dispatch(treachmap& reachmap, tmoves& moves)
769 {
770 	DBG_AI_TESTING_AI_DEFAULT << "Starting simple dispatch.\n";
771 
772 	// we now have a list with units with the villages they can reach.
773 	// keep trying the following steps as long as one of them changes
774 	// the state.
775 	// 1. Dispatch units who can reach 1 village (if more units can reach that
776 	//    village only one can capture it, so use the first in the list.)
777 	// 2. Villages which can only be reached by one unit get that unit dispatched
778 	//    to them.
779 	size_t village_count = 0;
780 	bool dispatched = true;
781 	while(dispatched) {
782 		dispatched = false;
783 
784 		if(dispatch_unit_simple(reachmap, moves)) {
785 			dispatched = true;
786 		} else {
787 			if(reachmap.empty()) {
788 				DBG_AI_TESTING_AI_DEFAULT << "dispatch_unit_simple() found a final solution.\n";
789 				break;
790 			} else {
791 				DBG_AI_TESTING_AI_DEFAULT << "dispatch_unit_simple() couldn't dispatch more units.\n";
792 			}
793 		}
794 
795 		if(dispatch_village_simple(reachmap, moves, village_count)) {
796 			dispatched = true;
797 		} else {
798 			if(reachmap.empty()) {
799 				DBG_AI_TESTING_AI_DEFAULT << "dispatch_village_simple() found a final solution.\n";
800 				break;
801 			} else {
802 				DBG_AI_TESTING_AI_DEFAULT << "dispatch_village_simple() couldn't dispatch more units.\n";
803 			}
804 		}
805 
806 		if(!reachmap.empty() && dispatched) {
807 			DBG_AI_TESTING_AI_DEFAULT << reachmap.size() << " unit(s) left restarting simple dispatching.\n";
808 
809 			dump_reachmap(reachmap);
810 		}
811 	}
812 
813 	if(reachmap.empty()) {
814 		DBG_AI_TESTING_AI_DEFAULT << "No units left after simple dispatcher.\n";
815 		return;
816 	}
817 
818 	DBG_AI_TESTING_AI_DEFAULT << reachmap.size() << " units left for complex dispatch with "
819 		<< village_count << " villages left.\n";
820 
821 	dump_reachmap(reachmap);
822 
823 	dispatch_complex(reachmap, moves, village_count);
824 }
825 
826 // Returns		need further processing
827 // false		Nothing has been modified or no units left
dispatch_unit_simple(treachmap & reachmap,tmoves & moves)828 bool get_villages_phase::dispatch_unit_simple(treachmap& reachmap, tmoves& moves)
829 {
830 	bool result = false;
831 
832 	treachmap::iterator itor = reachmap.begin();
833 	while(itor != reachmap.end()) {
834 		if(itor->second.size() == 1) {
835 			const map_location village = itor->second[0];
836 			result = true;
837 
838 			DBG_AI_TESTING_AI_DEFAULT << "Dispatched unit at " << itor->first << " to village " << village << '\n';
839 			moves.emplace_back(village, itor->first);
840 			reachmap.erase(itor++);
841 
842 			if(remove_village(reachmap, moves, village)) {
843 				itor = reachmap.begin();
844 			}
845 
846 		} else {
847 			++itor;
848 		}
849 	}
850 
851 	// Test special cases.
852 	if(reachmap.empty()) {
853 		// We're done.
854 		return false;
855 	}
856 
857 	if(reachmap.size() == 1) {
858 		// One unit left.
859 		DBG_AI_TESTING_AI_DEFAULT << "Dispatched _last_ unit at " << reachmap.begin()->first
860 			<< " to village " << reachmap.begin()->second[0] << '\n';
861 
862 		moves.emplace_back(reachmap.begin()->second[0], reachmap.begin()->first);
863 
864 		reachmap.clear();
865 		// We're done.
866 		return false;
867 	}
868 
869 	return result;
870 }
871 
dispatch_village_simple(treachmap & reachmap,tmoves & moves,size_t & village_count)872 bool get_villages_phase::dispatch_village_simple(
873 	treachmap& reachmap, tmoves& moves, size_t& village_count)
874 {
875 
876 	bool result = false;
877 	bool dispatched = true;
878 	while(dispatched) {
879 		dispatched = false;
880 
881 		// build the reverse map
882 		std::map<map_location /*village location*/,
883 			std::vector<map_location /* units that can reach it*/>>reversemap;
884 
885 		treachmap::const_iterator itor = reachmap.begin();
886 		for(;itor != reachmap.end(); ++itor) {
887 
888 			for(std::vector<map_location>::const_iterator
889 					v_itor = itor->second.begin();
890 					v_itor != itor->second.end(); ++v_itor) {
891 
892 				reversemap[*v_itor].push_back(itor->first);
893 
894 			}
895 		}
896 
897 		village_count = reversemap.size();
898 
899 		itor = reversemap.begin();
900 		while(itor != reversemap.end()) {
901 			if(itor->second.size() == 1) {
902 				// One unit can reach this village.
903 				const map_location village = itor->first;
904 				dispatched = true;
905 				result = true;
906 
907 				DBG_AI_TESTING_AI_DEFAULT << "Dispatched unit at " << itor->second[0] << " to village " << itor->first << '\n';
908 				moves.emplace_back(itor->first, itor->second[0]);
909 
910 				reachmap.erase(itor->second[0]);
911 				remove_village(reachmap, moves, village);
912 				// Get can go to some trouble to remove the unit from the other villages
913 				// instead we abort this loop end do a full rebuild on the map.
914 				break;
915 			} else {
916 				++itor;
917 			}
918 		}
919 	}
920 
921 	return result;
922 }
923 
remove_village(treachmap & reachmap,tmoves & moves,const map_location & village)924 bool get_villages_phase::remove_village(
925 	treachmap& reachmap, tmoves& moves, const map_location& village)
926 {
927 	bool result = false;
928 	treachmap::iterator itor = reachmap.begin();
929 	while(itor != reachmap.end()) {
930 		itor->second.erase(std::remove(itor->second.begin(), itor->second.end(), village), itor->second.end());
931 		if(itor->second.empty()) {
932 			result = true;
933 			itor = remove_unit(reachmap, moves, itor);
934 		} else {
935 			++itor;
936 		}
937 	}
938 	return result;
939 }
940 
remove_unit(treachmap & reachmap,tmoves & moves,treachmap::iterator unit)941 get_villages_phase::treachmap::iterator get_villages_phase::remove_unit(
942 	treachmap& reachmap, tmoves& moves, treachmap::iterator unit)
943 {
944 	assert(unit->second.empty());
945 
946 	if(unit->first == leader_loc_ && best_leader_loc_ != map_location::null_location()) {
947 		DBG_AI_TESTING_AI_DEFAULT << "Dispatch leader at " << leader_loc_ << " closer to the keep at "
948 			<< best_leader_loc_ << '\n';
949 
950 		moves.emplace_back(best_leader_loc_, leader_loc_);
951 	}
952 
953 	reachmap.erase(unit++);
954 	return unit;
955 }
956 
dispatch_complex(treachmap & reachmap,tmoves & moves,const size_t village_count)957 void get_villages_phase::dispatch_complex(
958 	treachmap& reachmap, tmoves& moves, const size_t village_count)
959 {
960 	// ***** ***** Init and dispatch if every unit can reach every village.
961 
962 	const size_t unit_count = reachmap.size();
963 	// The maximum number of villages we can capture with the available units.
964 	const size_t max_result = unit_count < village_count ? unit_count : village_count;
965 
966 	assert(unit_count >= 2 && village_count >= 2);
967 
968 	// Every unit can reach every village.
969 	if(unit_count == 2 && village_count == 2) {
970 		DBG_AI_TESTING_AI_DEFAULT << "Every unit can reach every village for 2 units, dispatch them.\n";
971 		full_dispatch(reachmap, moves);
972 		return;
973 	}
974 
975 	std::vector<map_location> units(unit_count);
976 	std::vector<size_t> villages_per_unit(unit_count);
977 	std::vector<map_location> villages;
978 	std::vector<size_t> units_per_village(village_count);
979 
980 	// We want to test the units, the ones who can reach the least
981 	// villages first so this is our lookup map.
982 	std::multimap<size_t /* villages_per_unit value*/,
983 		size_t /*villages_per_unit index*/> unit_lookup;
984 
985 	std::vector</*unit*/boost::dynamic_bitset</*village*/>> matrix(reachmap.size(), boost::dynamic_bitset<>(village_count));
986 
987 	treachmap::const_iterator itor = reachmap.begin();
988 	for(size_t u = 0; u < unit_count; ++u, ++itor) {
989 		units[u] = itor->first;
990 		villages_per_unit[u] = itor->second.size();
991 		unit_lookup.emplace(villages_per_unit[u], u);
992 
993 		assert(itor->second.size() >= 2);
994 
995 		for(size_t v = 0; v < itor->second.size(); ++v) {
996 
997 			size_t v_index;
998 			// find the index of the v in the villages
999 			std::vector<map_location>::const_iterator v_itor =
1000 				std::find(villages.begin(), villages.end(), itor->second[v]);
1001 			if(v_itor == villages.end()) {
1002 				v_index = villages.size(); // will be the last element after push_back.
1003 				villages.push_back(itor->second[v]);
1004 			} else {
1005 				v_index = v_itor - villages.begin();
1006 			}
1007 
1008 			units_per_village[v_index]++;
1009 
1010 			matrix[u][v_index] = true;
1011 		}
1012 	}
1013 	for(std::vector<size_t>::const_iterator upv_it = units_per_village.begin();
1014 			upv_it != units_per_village.end(); ++upv_it) {
1015 
1016 		assert(*upv_it >=2);
1017 	}
1018 
1019 	if(debug_) {
1020 		// Print header
1021 		std::cerr << "Reach matrix:\n\nvillage";
1022 		size_t u, v;
1023 		for(v = 0; v < village_count; ++v) {
1024 			std::cerr << '\t' << villages[v];
1025 		}
1026 		std::cerr << "\ttotal\nunit\n";
1027 
1028 		// Print data
1029 		for(u = 0; u < unit_count; ++u) {
1030 			std::cerr << units[u];
1031 
1032 			for(v = 0; v < village_count; ++v) {
1033 				std::cerr << '\t' << matrix[u][v];
1034 			}
1035 			std::cerr << "\t" << villages_per_unit[u] << '\n';
1036 		}
1037 
1038 		// Print footer
1039 		std::cerr << "total";
1040 		for(v = 0; v < village_count; ++v) {
1041 			std::cerr << '\t' << units_per_village[v];
1042 		}
1043 		std::cerr << '\n';
1044 	}
1045 
1046 	// Test the special case, everybody can reach all villages
1047 	const bool reach_all = ((village_count == unit_count)
1048 		&& (std::accumulate(villages_per_unit.begin(), villages_per_unit.end(), size_t())
1049 		== (village_count * unit_count)));
1050 
1051 	if(reach_all) {
1052 		DBG_AI_TESTING_AI_DEFAULT << "Every unit can reach every village, dispatch them\n";
1053 		full_dispatch(reachmap, moves);
1054 		reachmap.clear();
1055 		return;
1056 	}
1057 
1058 	// ***** ***** Find a square
1059 	std::multimap<size_t /* villages_per_unit value*/, size_t /*villages_per_unit index*/>
1060 		::const_iterator src_itor =  unit_lookup.begin();
1061 
1062 	while(src_itor != unit_lookup.end() && src_itor->first == 2) {
1063 
1064 		for(std::multimap<size_t, size_t>::const_iterator
1065 				dst_itor = unit_lookup.begin();
1066 				dst_itor != unit_lookup.end(); ++ dst_itor) {
1067 
1068 			// avoid comparing us with ourselves.
1069 			if(src_itor == dst_itor) {
1070 				continue;
1071 			}
1072 
1073 			boost::dynamic_bitset<> result = matrix[src_itor->second] & matrix[dst_itor->second];
1074 			size_t matched = result.count();
1075 
1076 			// we found a  solution, dispatch
1077 			if(matched == 2) {
1078 				// Collect data
1079 				size_t first = result.find_first();
1080 				size_t second = result.find_next(first);
1081 
1082 				const map_location village1 = villages[first];
1083 				const map_location village2 = villages[second];
1084 
1085 				const bool perfect = (src_itor->first == 2 &&
1086 					dst_itor->first == 2 &&
1087 					units_per_village[first] == 2 &&
1088 					units_per_village[second] == 2);
1089 
1090 				// Dispatch
1091 				DBG_AI_TESTING_AI_DEFAULT << "Found a square.\nDispatched unit at " << units[src_itor->second]
1092 						<< " to village " << village1 << '\n';
1093 				moves.emplace_back(village1, units[src_itor->second]);
1094 
1095 				DBG_AI_TESTING_AI_DEFAULT << "Dispatched unit at " << units[dst_itor->second]
1096 						<< " to village " << village2 << '\n';
1097 				moves.emplace_back(village2, units[dst_itor->second]);
1098 
1099 				// Remove the units
1100 				reachmap.erase(units[src_itor->second]);
1101 				reachmap.erase(units[dst_itor->second]);
1102 
1103 				// Evaluate and start correct function.
1104 				if(perfect) {
1105 					// We did a perfect dispatch 2 units who could visit 2 villages.
1106 					// This means we didn't change the assertion for this functions
1107 					// so call ourselves recursively, and finish afterwards.
1108 					DBG_AI_TESTING_AI_DEFAULT << "Perfect dispatch, do complex again.\n";
1109 					dispatch_complex(reachmap, moves, village_count - 2);
1110 					return;
1111 				} else {
1112 					// We did a not perfect dispatch but we did modify things
1113 					// so restart dispatching.
1114 					DBG_AI_TESTING_AI_DEFAULT << "NON Perfect dispatch, do dispatch again.\n";
1115 					remove_village(reachmap, moves, village1);
1116 					remove_village(reachmap, moves, village2);
1117 					dispatch(reachmap, moves);
1118 					return;
1119 				}
1120 			}
1121 		}
1122 
1123 		++src_itor;
1124 	}
1125 
1126 	// ***** ***** Do all permutations.
1127 	// Now walk through all possible permutations
1128 	// - test whether the suggestion is possible
1129 	// - does it result in max_villages
1130 	//   - dispatch and ready
1131 	// - is it's result better as the last best
1132 	//   - store
1133 	std::vector<std::pair<map_location, map_location>> best_result;
1134 
1135 	// Bruteforcing all possible permutations can result in a slow game.
1136 	// So there needs to be a balance between the best possible result and
1137 	// not too slow. From the test (at the end of the file) a good number is
1138 	// picked. In general we shouldn't reach this point too often if we do
1139 	// there are a lot of villages which are unclaimed and a lot of units
1140 	// to claim them.
1141 	const size_t max_options = 8;
1142 	if(unit_count >= max_options && village_count >= max_options) {
1143 
1144 		DBG_AI_TESTING_AI_DEFAULT << "Too many units " << unit_count << " and villages "
1145 			<< village_count<<" found, evaluate only the first "
1146 			<< max_options << " options;\n";
1147 
1148 		std::vector<size_t> perm (max_options, 0);
1149 		for(size_t i =0; i < max_options; ++i) {
1150 			perm[i] = i;
1151 		}
1152 		while(std::next_permutation(perm.begin(), perm.end())) {
1153 
1154 			// Get result for current permutation.
1155 			std::vector<std::pair<map_location,map_location>> result;
1156 			for(size_t u = 0; u < max_options; ++u) {
1157 				if(matrix[u][perm[u]]) {
1158 					result.emplace_back(villages[perm[u]], units[u]);
1159 
1160 				}
1161 			}
1162 			if(result.size() == max_result) {
1163 				best_result.swap(result);
1164 				break;
1165 			}
1166 
1167 			if(result.size() > best_result.size()) {
1168 				best_result.swap(result);
1169 			}
1170 		}
1171 		// End of loop no optimal found, assign the best
1172 		moves.insert(moves.end(), best_result.begin(), best_result.end());
1173 
1174 		// Clean up the reachmap for dispatched units.
1175 		for(const auto& unit_village_pair : best_result) {
1176 			reachmap.erase(unit_village_pair.second);
1177 		}
1178 
1179 		// Try to dispatch whatever is left
1180 		dispatch(reachmap, moves);
1181 		return;
1182 
1183 	} else if(unit_count <= village_count) {
1184 
1185 		DBG_AI_TESTING_AI_DEFAULT << "Unit major\n";
1186 
1187 		std::vector<size_t> perm (unit_count, 0);
1188 		for(size_t i =0; i < unit_count; ++i) {
1189 			perm[i] = i;
1190 		}
1191 		while(std::next_permutation(perm.begin(), perm.end())) {
1192 			// Get result for current permutation.
1193 			std::vector<std::pair<map_location,map_location>> result;
1194 			for(size_t u = 0; u < unit_count; ++u) {
1195 				if(matrix[u][perm[u]]) {
1196 					result.emplace_back(villages[perm[u]], units[u]);
1197 
1198 				}
1199 			}
1200 			if(result.size() == max_result) {
1201 				moves.insert(moves.end(), result.begin(), result.end());
1202 				reachmap.clear();
1203 				return;
1204 			}
1205 
1206 			if(result.size() > best_result.size()) {
1207 				best_result.swap(result);
1208 			}
1209 		}
1210 		// End of loop no optimal found, assign the best
1211 		moves.insert(moves.end(), best_result.begin(), best_result.end());
1212 
1213 		// clean up the reachmap we need to test whether the leader is still there
1214 		// and if so remove him manually to get him dispatched.
1215 		for(const auto& unit_village_pair : best_result) {
1216 			reachmap.erase(unit_village_pair.second);
1217 		}
1218 		treachmap::iterator unit = reachmap.find(leader_loc_);
1219 		if(unit != reachmap.end()) {
1220 			unit->second.clear();
1221 			remove_unit(reachmap, moves, unit);
1222 		}
1223 		reachmap.clear();
1224 
1225 	} else {
1226 
1227 		DBG_AI_TESTING_AI_DEFAULT << "Village major\n";
1228 
1229 		std::vector<size_t> perm (village_count, 0);
1230 		for(size_t i =0; i < village_count; ++i) {
1231 			perm[i] = i;
1232 		}
1233 		while(std::next_permutation(perm.begin(), perm.end())) {
1234 			// Get result for current permutation.
1235 			std::vector<std::pair<map_location,map_location>> result;
1236 			for(size_t v = 0; v < village_count; ++v) {
1237 				if(matrix[perm[v]][v]) {
1238 					result.emplace_back(villages[v], units[perm[v]]);
1239 
1240 				}
1241 			}
1242 			if(result.size() == max_result) {
1243 				moves.insert(moves.end(), result.begin(), result.end());
1244 				reachmap.clear();
1245 				return;
1246 			}
1247 
1248 			if(result.size() > best_result.size()) {
1249 				best_result.swap(result);
1250 			}
1251 		}
1252 		// End of loop no optimal found, assigne the best
1253 		moves.insert(moves.end(), best_result.begin(), best_result.end());
1254 
1255 		// clean up the reachmap we need to test whether the leader is still there
1256 		// and if so remove him manually to get him dispatched.
1257 		for(const auto& unit_village_pair : best_result) {
1258 			reachmap.erase(unit_village_pair.second);
1259 		}
1260 		treachmap::iterator unit = reachmap.find(leader_loc_);
1261 		if(unit != reachmap.end()) {
1262 			unit->second.clear();
1263 			remove_unit(reachmap, moves, unit);
1264 		}
1265 		reachmap.clear();
1266 	}
1267 }
1268 
full_dispatch(treachmap & reachmap,tmoves & moves)1269 void get_villages_phase::full_dispatch(treachmap& reachmap, tmoves& moves)
1270 {
1271 	treachmap::const_iterator itor = reachmap.begin();
1272 	for(size_t i = 0; i < reachmap.size(); ++i, ++itor) {
1273 		DBG_AI_TESTING_AI_DEFAULT << "Dispatched unit at " << itor->first
1274 				<< " to village " << itor->second[i] << '\n';
1275 		moves.emplace_back(itor->second[i], itor->first);
1276 	}
1277 }
1278 
dump_reachmap(treachmap & reachmap)1279 void get_villages_phase::dump_reachmap(treachmap& reachmap)
1280 {
1281 	if(!debug_) {
1282 		return;
1283 	}
1284 
1285 	for(treachmap::const_iterator itor =
1286 			reachmap.begin(); itor != reachmap.end(); ++itor) {
1287 
1288 		std::cerr << "Reachlist for unit at " << itor->first;
1289 
1290 		if(itor->second.empty()) {
1291 			std::cerr << "\tNone";
1292 		}
1293 
1294 		for(std::vector<map_location>::const_iterator
1295 				v_itor = itor->second.begin();
1296 				v_itor != itor->second.end(); ++v_itor) {
1297 
1298 			std::cerr << '\t' << *v_itor;
1299 		}
1300 		std::cerr << '\n';
1301 
1302 	}
1303 }
1304 
1305 //==============================================================
1306 
get_healing_phase(rca_context & context,const config & cfg)1307 get_healing_phase::get_healing_phase( rca_context &context, const config &cfg )
1308 	: candidate_action(context,cfg),move_()
1309 {
1310 }
1311 
~get_healing_phase()1312 get_healing_phase::~get_healing_phase()
1313 {
1314 }
1315 
evaluate()1316 double get_healing_phase::evaluate()
1317 {
1318 	// Find units in need of healing.
1319 	unit_map &units_ = resources::gameboard->units();
1320 	unit_map::iterator u_it = units_.begin();
1321 	for(; u_it != units_.end(); ++u_it) {
1322 		unit &u = *u_it;
1323 
1324 		if(u.can_recruit() && get_passive_leader()){
1325 			continue;
1326 		}
1327 
1328 		// If the unit is on our side, has lost as many or more than
1329 		// 1/2 round worth of healing, and doesn't regenerate itself,
1330 		// then try to find a vacant village for it to rest in.
1331 		if(u.side() == get_side() &&
1332 		   (u.max_hitpoints() - u.hitpoints() >= game_config::poison_amount/2
1333 		   || u.get_state(unit::STATE_POISONED)) &&
1334 		    !u.get_ability_bool("regenerate", *resources::gameboard))
1335 		{
1336 			// Look for the village which is the least vulnerable to enemy attack.
1337 			typedef std::multimap<map_location,map_location>::const_iterator Itor;
1338 			std::pair<Itor,Itor> it = get_srcdst().equal_range(u_it->get_location());
1339 			double best_vulnerability = 100000.0;
1340 			// Make leader units more unlikely to move to vulnerable villages
1341 			const double leader_penalty = (u.can_recruit()?2.0:1.0);
1342 			Itor best_loc = it.second;
1343 			while(it.first != it.second) {
1344 				const map_location& dst = it.first->second;
1345 				if (resources::gameboard->map().gives_healing(dst) && (units_.find(dst) == units_.end() || dst == u_it->get_location())) {
1346 					const double vuln = power_projection(dst, get_enemy_dstsrc());
1347 					DBG_AI_TESTING_AI_DEFAULT << "found village with vulnerability: " << vuln << "\n";
1348 					if(vuln < best_vulnerability) {
1349 						best_vulnerability = vuln;
1350 						best_loc = it.first;
1351 						DBG_AI_TESTING_AI_DEFAULT << "chose village " << dst << '\n';
1352 					}
1353 				}
1354 
1355 				++it.first;
1356 			}
1357 
1358 			// If we have found an eligible village,
1359 			// and we can move there without expecting to get whacked next turn:
1360 			if(best_loc != it.second && best_vulnerability*leader_penalty < u.hitpoints()) {
1361 				move_ = check_move_action(best_loc->first,best_loc->second,true);
1362 				if (move_->is_ok()) {
1363 					return get_score();
1364 				}
1365 			}
1366 		}
1367 	}
1368 
1369 	return BAD_SCORE;
1370 }
1371 
execute()1372 void get_healing_phase::execute()
1373 {
1374 	LOG_AI_TESTING_AI_DEFAULT << "moving unit to village for healing...\n";
1375 	move_->execute();
1376 	if (!move_->is_ok()){
1377 		LOG_AI_TESTING_AI_DEFAULT << get_name() << "::execute not ok" << std::endl;
1378 	}
1379 }
1380 
1381 //==============================================================
1382 
retreat_phase(rca_context & context,const config & cfg)1383 retreat_phase::retreat_phase( rca_context &context, const config &cfg )
1384 	: candidate_action(context,cfg), move_()
1385 {
1386 }
1387 
~retreat_phase()1388 retreat_phase::~retreat_phase()
1389 {
1390 }
1391 
evaluate()1392 double retreat_phase::evaluate()
1393 {
1394 
1395 
1396 	// Get versions of the move map that assume that all units are at full movement
1397 	const unit_map& units_ = resources::gameboard->units();
1398 
1399 	//unit_map::const_iterator leader = units_.find_leader(get_side());
1400 	std::vector<unit_map::const_iterator> leaders = units_.find_leaders(get_side());
1401 	std::map<map_location,pathfind::paths> dummy_possible_moves;
1402 
1403 	move_map fullmove_srcdst;
1404 	move_map fullmove_dstsrc;
1405 	calculate_possible_moves(dummy_possible_moves, fullmove_srcdst, fullmove_dstsrc,
1406 			false, true, &get_avoid());
1407 
1408 	/*adjacent_loc_array_t leader_adj;
1409 	if(leader != units_.end()) {
1410 		get_adjacent_tiles(leader->get_location(), leader_adj.data());
1411 	}*/
1412 	//int leader_adj_count = 0;
1413 	std::vector<map_location> leaders_adj_v;
1414 	for (unit_map::const_iterator leader : leaders) {
1415 		adjacent_loc_array_t tmp_leader_adj;
1416 		get_adjacent_tiles(leader->get_location(), tmp_leader_adj.data());
1417 		for (map_location &loc : tmp_leader_adj) {
1418 			bool found = false;
1419 			for (map_location &new_loc : leaders_adj_v) {
1420 				if(new_loc == loc){
1421 					found = true;
1422 					break;
1423 				}
1424 			}
1425 			if(!found){
1426 				leaders_adj_v.push_back(loc);
1427 			}
1428 		}
1429 	}
1430 	//leader_adj_count = leaders_adj_v.size();
1431 
1432 
1433 	for(unit_map::const_iterator i = units_.begin(); i != units_.end(); ++i) {
1434 		if (i->side() == get_side() &&
1435 		    i->movement_left() == i->total_movement() &&
1436 		    //leaders.find(*i) == leaders.end() && //unit_map::const_iterator(i) != leader &&
1437 		    std::find(leaders.begin(), leaders.end(), i) == leaders.end() &&
1438 		    !i->incapacitated())
1439 		{
1440 			// This unit still has movement left, and is a candidate to retreat.
1441 			// We see the amount of power of each side on the situation,
1442 			// and decide whether it should retreat.
1443 			if(should_retreat(i->get_location(), i, fullmove_srcdst, fullmove_dstsrc, get_caution())) {
1444 
1445 				bool can_reach_leader = false;
1446 
1447 				// Time to retreat. Look for the place where the power balance
1448 				// is most in our favor.
1449 				// If we can't find anywhere where we like the power balance,
1450 				// just try to get to the best defensive hex.
1451 				typedef move_map::const_iterator Itor;
1452 				std::pair<Itor,Itor> itors = get_srcdst().equal_range(i->get_location());
1453 				map_location best_pos, best_defensive(i->get_location());
1454 
1455 				double best_rating = -1000.0;
1456 				int best_defensive_rating = i->defense_modifier(resources::gameboard->map().get_terrain(i->get_location()))
1457 					- (resources::gameboard->map().is_village(i->get_location()) ? 10 : 0);
1458 				while(itors.first != itors.second) {
1459 
1460 					//if(leader != units_.end() && std::count(leader_adj,
1461 					//			leader_adj + 6, itors.first->second)) {
1462 					if(std::find(leaders_adj_v.begin(), leaders_adj_v.end(), itors.first->second) != leaders_adj_v.end()){
1463 
1464 						can_reach_leader = true;
1465 						break;
1466 					}
1467 
1468 					// We rate the power balance of a hex based on our power projection
1469 					// compared to theirs, multiplying their power projection by their
1470 					// chance to hit us on the hex we're planning to flee to.
1471 					const map_location& hex = itors.first->second;
1472 					const int defense = i->defense_modifier(resources::gameboard->map().get_terrain(hex));
1473 					const double our_power = power_projection(hex,get_dstsrc());
1474 					const double their_power = power_projection(hex,get_enemy_dstsrc()) * static_cast<double>(defense)/100.0;
1475 					const double rating = our_power - their_power;
1476 					if(rating > best_rating) {
1477 						best_pos = hex;
1478 						best_rating = rating;
1479 					}
1480 
1481 					// Give a bonus for getting to a village.
1482 					const int modified_defense = defense - (resources::gameboard->map().is_village(hex) ? 10 : 0);
1483 
1484 					if(modified_defense < best_defensive_rating) {
1485 						best_defensive_rating = modified_defense;
1486 						best_defensive = hex;
1487 					}
1488 
1489 					++itors.first;
1490 				}
1491 
1492 				// If the unit is in range of its leader, it should
1493 				// never retreat -- it has to defend the leader instead.
1494 				if(can_reach_leader) {
1495 					continue;
1496 				}
1497 
1498 				if(!best_pos.valid()) {
1499 					best_pos = best_defensive;
1500 				}
1501 
1502 				if(best_pos.valid()) {
1503 					move_ = check_move_action(i->get_location(), best_pos, true);
1504 					if (move_->is_ok()) {
1505 						return get_score();
1506 					}
1507 				}
1508 			}
1509 		}
1510 	}
1511 
1512 	return BAD_SCORE;
1513 }
1514 
execute()1515 void retreat_phase::execute()
1516 {
1517 	move_->execute();
1518 	if (!move_->is_ok()){
1519 		LOG_AI_TESTING_AI_DEFAULT << get_name() << "::execute not ok" << std::endl;
1520 	}
1521 }
1522 
1523 
1524 
should_retreat(const map_location & loc,const unit_map::const_iterator & un,const move_map & srcdst,const move_map & dstsrc,double caution)1525 bool retreat_phase::should_retreat(const map_location& loc, const unit_map::const_iterator& un,  const move_map &srcdst, const move_map &dstsrc, double caution)
1526 {
1527 	const move_map &enemy_dstsrc = get_enemy_dstsrc();
1528 
1529 	if(caution <= 0.0) {
1530 		return false;
1531 	}
1532 
1533 	double optimal_terrain = best_defensive_position(un->get_location(), dstsrc,
1534 			srcdst, enemy_dstsrc).chance_to_hit/100.0;
1535 	const double proposed_terrain =
1536 		un->defense_modifier(resources::gameboard->map().get_terrain(loc)) / 100.0;
1537 
1538 	// The 'exposure' is the additional % chance to hit
1539 	// this unit receives from being on a sub-optimal defensive terrain.
1540 	const double exposure = proposed_terrain - optimal_terrain;
1541 
1542 	const double our_power = power_projection(loc,dstsrc);
1543 	const double their_power = power_projection(loc,enemy_dstsrc);
1544 	return caution*their_power*(1.0+exposure) > our_power;
1545 }
1546 
1547 
1548 //==============================================================
1549 
leader_control_phase(rca_context & context,const config & cfg)1550 leader_control_phase::leader_control_phase( rca_context &context, const config &cfg )
1551 	: candidate_action(context,cfg)
1552 {
1553 }
1554 
1555 
~leader_control_phase()1556 leader_control_phase::~leader_control_phase()
1557 {
1558 }
1559 
evaluate()1560 double leader_control_phase::evaluate()
1561 {
1562 	ERR_AI_TESTING_AI_DEFAULT << get_name() << ": evaluate - not yet implemented" << std::endl;
1563 	return BAD_SCORE;
1564 }
1565 
1566 
1567 
execute()1568 void leader_control_phase::execute()
1569 {
1570 	ERR_AI_TESTING_AI_DEFAULT << get_name() << ": execute - not yet implemented" << std::endl;
1571 }
1572 
1573 //==============================================================
1574 
leader_shares_keep_phase(rca_context & context,const config & cfg)1575 leader_shares_keep_phase::leader_shares_keep_phase( rca_context &context, const config &cfg )
1576 	:candidate_action(context, cfg)
1577 {
1578 }
1579 
~leader_shares_keep_phase()1580 leader_shares_keep_phase::~leader_shares_keep_phase()
1581 {
1582 }
1583 
evaluate()1584 double leader_shares_keep_phase::evaluate()
1585 {
1586 	if(get_passive_leader() && !get_passive_leader_shares_keep()){
1587 		return BAD_SCORE;
1588 	}
1589 	bool allied_leaders_available = false;
1590 	for(team &tmp_team : resources::gameboard->teams()) {
1591 		if(!current_team().is_enemy(tmp_team.side())){
1592 			std::vector<unit_map::unit_iterator> allied_leaders = resources::gameboard->units().find_leaders(get_side());
1593 			if (!allied_leaders.empty()){
1594 				allied_leaders_available = true;
1595 				break;
1596 			}
1597 		}
1598 	}
1599 	if(allied_leaders_available){
1600 		return get_score();
1601 	}
1602 	return BAD_SCORE;
1603 }
1604 
execute()1605 void leader_shares_keep_phase::execute()
1606 {
1607 	//get all AI leaders
1608 	std::vector<unit_map::unit_iterator> ai_leaders = resources::gameboard->units().find_leaders(get_side());
1609 
1610 	//calculate all possible moves (AI + allies)
1611 	typedef std::map<map_location, pathfind::paths> path_map;
1612 	path_map possible_moves;
1613 	move_map friends_srcdst, friends_dstsrc;
1614 	calculate_moves(resources::gameboard->units(), possible_moves, friends_srcdst, friends_dstsrc, false, true);
1615 
1616 	//check for each ai leader if he should move away from his keep
1617 	for (unit_map::unit_iterator &ai_leader : ai_leaders) {
1618 		if(!ai_leader.valid()) {
1619 			//This can happen if wml killed or moved a leader during a movement events of another leader
1620 			continue;
1621 		}
1622 		//only if leader is on a keep
1623 		const map_location &keep = ai_leader->get_location();
1624 		if ( !resources::gameboard->map().is_keep(keep) ) {
1625 			continue;
1626 		}
1627 		map_location recruit_loc = pathfind::find_vacant_castle(*ai_leader);
1628 		if(!resources::gameboard->map().on_board(recruit_loc)){
1629 			continue;
1630 		}
1631 		bool friend_can_reach_keep = false;
1632 
1633 		//for each leader, check if he's allied and can reach our keep
1634 		for(path_map::const_iterator i = possible_moves.begin(); i != possible_moves.end(); ++i){
1635 			const unit_map::const_iterator itor = resources::gameboard->units().find(i->first);
1636 			assert(itor.valid());
1637 			team &leader_team = resources::gameboard->get_team(itor->side());
1638 			if(itor != resources::gameboard->units().end() && itor->can_recruit() && itor->side() != get_side() && (leader_team.total_income() + leader_team.gold() > leader_team.minimum_recruit_price())){
1639 				pathfind::paths::dest_vect::const_iterator tokeep = i->second.destinations.find(keep);
1640 				if(tokeep != i->second.destinations.end()){
1641 					friend_can_reach_keep = true;
1642 					break;
1643 				}
1644 			}
1645 		}
1646 		//if there's no allied leader who can reach the keep, check next ai leader
1647 		if(friend_can_reach_keep){
1648 			//determine the best place the ai leader can move to
1649 			map_location best_move;
1650 			int defense_modifier = 100;
1651 			for(pathfind::paths::dest_vect::const_iterator i = possible_moves[keep].destinations.begin()
1652 					; i != possible_moves[keep].destinations.end()
1653 					; ++i){
1654 
1655 				//calculate_moves() above uses max. moves -> need to check movement_left of leader here
1656 				if(distance_between(i->curr, keep) <= 3
1657 						&& static_cast<int>(distance_between(i->curr, keep)) <= ai_leader->movement_left()){
1658 
1659 					int tmp_def_mod = ai_leader->defense_modifier(resources::gameboard->map().get_terrain(i->curr));
1660 					if(tmp_def_mod < defense_modifier){
1661 						defense_modifier = tmp_def_mod;
1662 						best_move = i->curr;
1663 					}
1664 				}
1665 			}
1666 			//only move if there's a place with a good defense
1667 			if(defense_modifier < 100){
1668 				move_result_ptr move = check_move_action(keep, best_move, true);
1669 				if(move->is_ok()){
1670 					move->execute();
1671 					if (!move->is_ok()){
1672 						LOG_AI_TESTING_AI_DEFAULT << get_name() << "::execute not ok" << std::endl;
1673 					}else{
1674 						ai_leader->set_goto(keep);
1675 					}
1676 					// This is needed for sides with multiple leaders, in case a WML event does something
1677 					// or to account for a leader having previously been moved by this CA execution
1678 					possible_moves.clear();
1679 					calculate_moves(resources::gameboard->units(), possible_moves, friends_srcdst, friends_dstsrc, false, true);
1680 				}else{
1681 					LOG_AI_TESTING_AI_DEFAULT << get_name() << "::execute not ok" << std::endl;
1682 				}
1683 			}
1684 		}
1685 		ai_leader->remove_movement_ai();
1686 	}
1687 	// re-get the AI leaders, in case an event did something
1688 	ai_leaders = resources::gameboard->units().find_leaders(get_side());
1689 	for(unit_map::unit_iterator &leader : ai_leaders) {
1690 		leader->remove_movement_ai();
1691 	}
1692 	//ERR_AI_TESTING_AI_DEFAULT << get_name() << ": evaluate - not yet implemented" << std::endl;
1693 }
1694 
1695 
1696 //==============================================================
1697 
1698 
1699 } //end of namespace testing_ai_default
1700 
1701 } //end of namespace ai
1702