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 ¤t_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