The Battle for Wesnoth  1.19.12+dev
ca_move_to_targets.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2009 - 2025
3  by Yurii Chernyi <terraninfo@terraninfo.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 /**
17  * @file
18  * Strategic movement routine, taken from default AI
19  */
20 
22 
23 #include "ai/actions.hpp"
24 #include "game_board.hpp"
25 #include "log.hpp"
26 #include "map/map.hpp"
27 #include "resources.hpp"
28 #include "units/unit.hpp"
29 #include "terrain/filter.hpp"
30 #include "pathfind/pathfind.hpp"
31 #include "pathfind/teleport.hpp"
32 
33 #include <deque>
34 
35 namespace ai {
36 
37 namespace ai_default_rca {
38 
39 static lg::log_domain log_ai_testing_ca_move_to_targets("ai/ca/move_to_targets");
40 #define DBG_AI LOG_STREAM(debug, log_ai_testing_ca_move_to_targets)
41 #define LOG_AI LOG_STREAM(info, log_ai_testing_ca_move_to_targets)
42 #define WRN_AI LOG_STREAM(warn, log_ai_testing_ca_move_to_targets)
43 #define ERR_AI LOG_STREAM(err, log_ai_testing_ca_move_to_targets)
44 
46 {
47  move_cost_calculator(const unit& u, const gamemap& map,
48  const unit_map& units, const move_map& enemy_dstsrc)
49  : unit_(u), map_(map), units_(units),
50  enemy_dstsrc_(enemy_dstsrc),
51  max_moves_(u.total_movement()),
52  avoid_enemies_(u.usage() == "scout")
53  {}
54 
55  double cost(const map_location& loc, const double) const
56  {
57  const t_translation::terrain_code terrain = map_[loc];
58 
59  const double move_cost = unit_.movement_cost(terrain);
60 
61  if(move_cost > max_moves_) // impassable
62  return getNoPathValue();
63 
64  double res = move_cost;
65  if(avoid_enemies_){
66  res *= 1.0 + enemy_dstsrc_.count(loc);
67  }
68 
69  //if there is a unit (even a friendly one) on this tile, we increase the cost to
70  //try discourage going through units, to thwart the 'single file effect'
71  if (units_.count(loc))
72  res *= 4.0;
73 
74  return res;
75  }
76 
77 private:
78  const unit& unit_;
79  const gamemap& map_;
80  const unit_map& units_;
82  const int max_moves_;
83  const bool avoid_enemies_;
84 };
85 
87 public:
89  :avoid_(context.get_avoid()), map_(resources::gameboard->map())
90  {
91  }
92 
93 bool operator()(const target &t){
94  if (!map_.on_board(t.loc)) {
95  DBG_AI << "removing target "<< t.loc << " due to it not on_board";
96  return true;
97  }
98 
99  if (t.value<=0) {
100  DBG_AI << "removing target "<< t.loc << " due to value<=0";
101  return true;
102  }
103 
104  if (avoid_.match(t.loc)) {
105  DBG_AI << "removing target "<< t.loc << " due to 'avoid' match";
106  return true;
107  }
108 
109  return false;
110 }
111 private:
112  const terrain_filter &avoid_;
113  const gamemap &map_;
114 
115 };
116 
118  : candidate_action(context,cfg)
119 {
120 }
121 
123 {
124 }
125 
127 {
128  return get_score();
129 }
130 
132 {
134  LOG_AI << "finding targets...";
135  std::vector<target> targets;
136  while(true) {
137  if(targets.empty()) {
138  targets = find_targets(get_enemy_dstsrc());
139  targets.insert(targets.end(),additional_targets().begin(),
140  additional_targets().end());
141  LOG_AI << "Found " << targets.size() << " targets";
142  if(targets.empty()) {
143  break;
144  }
145  }
146 
147  utils::erase_if(targets, remove_wrong_targets{*this});
148 
149  if(targets.empty()) {
150  break;
151  }
152 
153  LOG_AI << "choosing move with " << targets.size() << " targets";
154  std::pair<map_location,map_location> move = choose_move(targets);
155  LOG_AI << "choose_move ends with " << targets.size() << " targets";
156 
157  for(std::vector<target>::const_iterator ittg = targets.begin();
158  ittg != targets.end(); ++ittg) {
159  assert(resources::gameboard->map().on_board(ittg->loc));
160  }
161 
162  if(move.first.valid() == false || move.second.valid() == false) {
163  break;
164  }
165 
166  assert (resources::gameboard->map().on_board(move.first)
167  && resources::gameboard->map().on_board(move.second));
168 
169  LOG_AI << "move: " << move.first << " -> " << move.second;
170 
171  move_result_ptr move_ptr = execute_move_action(move.first,move.second,true);
172  if(!move_ptr->is_ok()) {
173  WRN_AI << "unexpected outcome of move";
174  break;
175  }
176  }
177 }
178 
179 // structure storing the maximal possible rating of a target
183  double max_rating;
184 };
185 
186 // compare maximal possible rating of targets
187 // we can be smarter about the equal case, but keep old behavior for the moment
189  bool operator()(const rated_target& a, const rated_target& b) const {
190  return a.max_rating > b.max_rating;
191  }
192 };
193 
195  const move_map& dstsrc, const move_map& enemy_dstsrc,
196  const pathfind::plain_route& rt)
197 {
198  double move_cost = rt.move_cost;
199 
200  if(move_cost > 0) {
201  // if this unit can move to that location this turn, it has a very very low cost
202  auto locRange = dstsrc.equal_range(tg.loc);
203  while (locRange.first != locRange.second) {
204  if (locRange.first->second == u->get_location()) {
205  move_cost = 0;
206  break;
207  }
208  ++locRange.first;
209  }
210  }
211 
212  double rating = tg.value;
213 
214  if(rating == 0)
215  return rating; // all following operations are only multiplications of 0
216 
217  // far target have a lower rating
218  if(move_cost > 0) {
219  rating /= move_cost;
220  }
221 
222  //for 'support' targets, they are rated much higher if we can get there within two turns,
223  //otherwise they are worthless to go for at all.
224  if(tg.type == ai_target::type::support) {
225  if (move_cost <= u->movement_left() * 2) {
226  rating *= 10.0;
227  } else {
228  rating = 0.0;
229  return rating;
230  }
231  }
232 
233  //scouts do not like encountering enemies on their paths
234  if (u->usage() == "scout") {
235  //scouts get a bonus for going after villages
236  if(tg.type == ai_target::type::village) {
237  rating *= get_scout_village_targeting();
238  }
239 
240  std::set<map_location> enemies_guarding;
241  enemies_along_path(rt.steps,enemy_dstsrc,enemies_guarding);
242  // note that an empty route means no guardian and thus optimal rating
243 
244  if(enemies_guarding.size() > 1) {
245  rating /= enemies_guarding.size();
246  } else {
247  //scouts who can travel on their route without coming in range of many enemies
248  //get a massive bonus, so that they can be processed first, and avoid getting
249  //bogged down in lots of grouping
250  rating *= 100;
251  }
252  }
253 
254  return rating;
255 }
256 
257 std::pair<map_location,map_location> move_to_targets_phase::choose_move(std::vector<target>& targets)
258 {
260 
262  unit_map &units_ = resources::gameboard->units();
263  const gamemap &map_ = resources::gameboard->map();
264 
266 
267  //take care of all the guardians first
268  for(u = units_.begin(); u != units_.end(); ++u) {
269  if (!(u->side() != get_side() || (u->can_recruit() && !is_keep_ignoring_leader(u->id())) || u->movement_left() <= 0 || u->incapacitated())) {
270  if (u->get_state("guardian")) {
271  LOG_AI << u->type_id() << " is guardian, staying still";
272  return std::pair(u->get_location(), u->get_location());
273  }
274  }
275  }
276 
277  //now find the first eligible remaining unit
278  for(u = units_.begin(); u != units_.end(); ++u) {
279  if (!(u->side() != get_side() || (u->can_recruit() && !is_keep_ignoring_leader(u->id())) || u->movement_left() <= 0 || u->incapacitated() || !is_allowed_unit(*u))) {
280  break;
281  }
282  }
283 
284  if(u == units_.end()) {
285  LOG_AI << "no eligible units found";
286  return std::pair<map_location,map_location>();
287  }
288 
289  const pathfind::plain_route dummy_route;
290  assert(dummy_route.steps.empty() && dummy_route.move_cost == 0);
291 
292  // We will sort all targets by a quick maximal possible rating,
293  // so we will be able to start real work by the most promising ones
294  // and if its real value is better than other maximal values
295  // then we can skip them.
296 
297  const move_map& dstsrc = get_dstsrc();
298  const move_map& enemy_dstsrc = get_enemy_dstsrc();
299  std::vector<rated_target> rated_targets;
300  for(std::vector<target>::iterator tg = targets.begin(); tg != targets.end(); ++tg) {
301  // passing a dummy route to have the maximal rating
302  double max_rating = rate_target(*tg, u, dstsrc, enemy_dstsrc, dummy_route);
303  rated_targets.emplace_back(tg, max_rating);
304  }
305 
306  //use stable_sort for the moment to preserve old AI behavior
307  std::stable_sort(rated_targets.begin(), rated_targets.end(), rated_target_comparer());
308 
309  const move_cost_calculator cost_calc(*u, map_, units_, enemy_dstsrc);
310 
311  pathfind::plain_route best_route;
312  unit_map::iterator best = units_.end();
313  double best_rating = -1.0;
314 
315  std::vector<rated_target>::iterator best_rated_target = rated_targets.end();
316 
317  std::vector<rated_target>::iterator rated_tg = rated_targets.begin();
318 
319  for(; rated_tg != rated_targets.end(); ++rated_tg) {
320  const target& tg = *(rated_tg->tg);
321 
322  LOG_AI << "Considering target at: " << tg.loc;
323  assert(map_.on_board(tg.loc));
324 
326 
327  // locStopValue controls how quickly we give up on the A* search, due
328  // to it seeming futile. Be very cautious about changing this value,
329  // as it can cause the AI to give up on searches and just do nothing.
330  const double locStopValue = 500.0;
332  pathfind::plain_route real_route = a_star_search(u->get_location(), tg.loc, locStopValue, cost_calc, map_.w(), map_.h(), &allowed_teleports);
333 
334  if(real_route.steps.empty()) {
335  LOG_AI << "Can't reach target: " << locStopValue << " = " << tg.value << "/" << best_rating;
336  continue;
337  }
338 
339  double real_rating = rate_target(tg, u, dstsrc, enemy_dstsrc, real_route);
340 
341  LOG_AI << tg.value << "/" << real_route.move_cost << " = " << real_rating;
342 
343  if(real_rating > best_rating){
344  best_rating = real_rating;
345  best_rated_target = rated_tg;
346  best_route = real_route;
347  best = u;
348  //prevent divivion by zero
349  //FIXME: stupid, should fix it at the division
350  if(best_rating == 0)
351  best_rating = 0.000000001;
352 
353  // if already higher than the maximal values of the next ratings
354  // (which are sorted, so only need to check the next one)
355  // then we have found the best target.
356  if(rated_tg+1 != rated_targets.end() && best_rating >= (rated_tg+1)->max_rating)
357  break;
358  }
359  }
360 
361  LOG_AI << "choose target...";
362 
363  if(best_rated_target == rated_targets.end()) {
364  LOG_AI << "no eligible targets found for unit at " << u->get_location();
365  return std::pair(u->get_location(), u->get_location());
366  }
367 
368  assert(best_rating >= 0);
369  std::vector<target>::iterator best_target = best_rated_target->tg;
370 
371  //if we have the 'simple_targeting' flag set, then we don't
372  //see if any other units can put a better bid forward for this
373  //target
374  bool simple_targeting = get_simple_targeting();
375 
376  if(simple_targeting == false) {
377  LOG_AI << "complex targeting...";
378  //now see if any other unit can put a better bid forward
379  for(++u; u != units_.end(); ++u) {
380  if (u->side() != get_side() || (u->can_recruit() && !is_keep_ignoring_leader(u->id())) ||
381  u->movement_left() <= 0 || u->get_state("guardian") ||
382  u->incapacitated() || !is_allowed_unit(*u))
383  {
384  continue;
385  }
386 
388 
389  const move_cost_calculator calc(*u, map_, units_, enemy_dstsrc);
390 
391  // locStopValue controls how quickly we give up on the A* search, due
392  // to it seeming futile. Be very cautious about changing this value,
393  // as it can cause the AI to give up on searches and just do nothing.
394  const double locStopValue = 500.0;
396  pathfind::plain_route cur_route = pathfind::a_star_search(u->get_location(), best_target->loc, locStopValue, calc, map_.w(), map_.h(), &allowed_teleports);
397 
398  if(cur_route.steps.empty()) {
399  continue;
400  }
401 
402  double rating = rate_target(*best_target, u, dstsrc, enemy_dstsrc, cur_route);
403 
404  if(best == units_.end() || rating > best_rating) {
405  best_rating = rating;
406  best = u;
407  best_route = cur_route;
408  }
409  }
410 
411  LOG_AI << "done complex targeting...";
412  } else {
413  u = units_.end();
414  }
415 
416  LOG_AI << "best unit: " << best->get_location();
417 
418  assert(best_target != targets.end());
419 
420  //if our target is a position to support, then we
421  //see if we can move to a position in support of this target
422  const move_map& srcdst = get_srcdst();
423  if(best_target->type == ai_target::type::support) {
424  LOG_AI << "support...";
425 
426  std::vector<map_location> locs;
427  access_points(srcdst, best->get_location(), best_target->loc, locs);
428 
429  if(locs.empty() == false) {
430  LOG_AI << "supporting unit at " << best_target->loc.wml_x() << "," << best_target->loc.wml_y();
431  map_location best_loc;
432  int best_defense = 0;
433  double best_vulnerability = 0.0;
434 
435  for(std::vector<map_location>::const_iterator i = locs.begin(); i != locs.end(); ++i) {
436  const int defense = best->defense_modifier(map_.get_terrain(*i));
437  //FIXME: suokko multiplied by 10 * get_caution(). ?
438  const double vulnerability = power_projection(*i,enemy_dstsrc);
439 
440  if(best_loc.valid() == false || defense < best_defense || (defense == best_defense && vulnerability < best_vulnerability)) {
441  best_loc = *i;
442  best_defense = defense;
443  best_vulnerability = vulnerability;
444  }
445  }
446 
447  LOG_AI << "returning support...";
448  return std::pair(best->get_location(), best_loc);
449  }
450  }
451 
452  std::map<map_location,pathfind::paths> dummy_possible_moves;
453  move_map fullmove_srcdst;
454  move_map fullmove_dstsrc;
455  calculate_possible_moves(dummy_possible_moves,fullmove_srcdst,fullmove_dstsrc,false,true);
456 
457  bool dangerous = false;
458 
459  if(get_grouping() != "no") {
460  LOG_AI << "grouping...";
461  const unit_map::const_iterator unit_at_target = units_.find(best_target->loc);
462  int movement = best->movement_left();
463 
464  const bool defensive_grouping = get_grouping() == "defensive";
465 
466  //we stop and consider whether the route to this
467  //target is dangerous, and whether we need to group some units to move in unison toward the target
468  //if any point along the path is too dangerous for our single unit, then we hold back
469  for(std::vector<map_location>::const_iterator i = best_route.steps.begin(); i != best_route.steps.end() && movement > 0; ++i) {
470 
471  //FIXME: suokko multiplied by 10 * get_caution(). ?
472  const double threat = power_projection(*i,enemy_dstsrc);
473  //FIXME: sukko doubled the power-projection them in the second test. ?
474  if ((threat >= best->hitpoints() && threat > power_projection(*i,fullmove_dstsrc)) ||
475  (i+1 >= best_route.steps.end()-1 && unit_at_target != units_.end() && current_team().is_enemy(unit_at_target->side()))) {
476  dangerous = true;
477  break;
478  }
479 
480  if(!defensive_grouping) {
481  movement -= best->movement_cost(map_.get_terrain(*i));
482  }
483  }
484 
485  LOG_AI << "done grouping...";
486  }
487 
488  if(dangerous) {
489  LOG_AI << "dangerous path";
490  std::set<map_location> group, enemies;
491  const map_location dst = form_group(best_route.steps,dstsrc,group);
492  enemies_along_path(best_route.steps,enemy_dstsrc,enemies);
493 
494  const double our_strength = compare_groups(group,enemies,best_route.steps);
495 
496  if(our_strength > 0.5 + get_caution()) {
497  LOG_AI << "moving group";
498  const bool res = move_group(dst,best_route.steps,group);
499  if(res) {
500  return std::pair<map_location,map_location>(map_location(1,1),map_location());
501  } else {
502  LOG_AI << "group didn't move " << group.size();
503 
504  //the group didn't move, so end the first unit in the group's turn, to prevent an infinite loop
505  return std::pair(best->get_location(), best->get_location());
506 
507  }
508  } else {
509  LOG_AI << "massing to attack " << best_target->loc.wml_x() << "," << best_target->loc.wml_y()
510  << " " << our_strength;
511 
512  const double value = best_target->value;
513  const map_location target_loc = best_target->loc;
514  const map_location loc = best->get_location();
515  const unit& un = *best;
516 
517  targets.erase(best_target);
518 
519  //find the best location to mass units at for an attack on the enemies
520  map_location best_loc;
521  double best_threat = 0.0;
522  int best_distance = 0;
523 
524  const double max_acceptable_threat = un.hitpoints() / 4.0;
525 
526  std::set<map_location> mass_locations;
527 
528  const auto itors = srcdst.equal_range(loc);
529  for(move_map::const_iterator i = itors.first; i != itors.second; ++i) {
530  const int distance = distance_between(target_loc,i->second);
531  const int defense = un.defense_modifier(map_.get_terrain(i->second));
532  //FIXME: suokko multiplied by 10 * get_caution(). ?
533  const double threat = (power_projection(i->second,enemy_dstsrc)*defense)/100;
534 
535  if(best_loc.valid() == false || (threat < std::max<double>(best_threat,max_acceptable_threat) && distance < best_distance)) {
536  best_loc = i->second;
537  best_threat = threat;
538  best_distance = distance;
539  }
540 
541  if(threat < max_acceptable_threat) {
542  mass_locations.insert(i->second);
543  }
544  }
545 
546  for(std::set<map_location>::const_iterator j = mass_locations.begin(); j != mass_locations.end(); ++j) {
547  if(*j != best_loc && distance_between(*j,best_loc) < 3) {
548  LOG_AI << "found mass-to-attack target... " << *j << " with value: " << value*4.0;
549  targets.emplace_back(*j,value*4.0,ai_target::type::mass);
550  best_target = targets.end() - 1;
551  }
552  }
553 
554  return std::pair<map_location,map_location>(loc,best_loc);
555  }
556  }
557 
558  for(std::vector<map_location>::reverse_iterator ri =
559  best_route.steps.rbegin(); ri != best_route.steps.rend(); ++ri) {
560 
561  //this is set to 'true' if we are hesitant to proceed because of enemy units,
562  //to rally troops around us.
563  bool is_dangerous = false;
564 
565  auto its = dstsrc.equal_range(*ri);
566  while(its.first != its.second) {
567  if (its.first->second == best->get_location()) {
568  if(!should_retreat(its.first->first,best,fullmove_srcdst,fullmove_dstsrc,enemy_dstsrc,
569  get_caution())) {
570  double value = best_target->value - best->cost() / 20.0;
571 
572  if(value > 0.0 && best_target->type != ai_target::type::mass) {
573  //there are enemies ahead. Rally troops around us to
574  //try to take the target
575  if(is_dangerous) {
576  LOG_AI << "found reinforcement target... " << its.first->first << " with value: " << value*2.0;
577  targets.emplace_back(its.first->first,value*2.0,ai_target::type::battle_aid);
578  }
579 
580  best_target->value = value;
581  } else {
582  targets.erase(best_target);
583  }
584 
585  LOG_AI << "Moving to " << its.first->first.wml_x() << "," << its.first->first.wml_y();
586 
587  return std::pair<map_location,map_location>(its.first->second,its.first->first);
588  } else {
589  LOG_AI << "dangerous!";
590  is_dangerous = true;
591  }
592  }
593 
594  ++its.first;
595  }
596  }
597 
598  if(best != units_.end()) {
599  LOG_AI << "Could not make good move, staying still";
600 
601  //this sounds like the road ahead might be dangerous, and that's why we don't advance.
602  //create this as a target, attempting to rally units around
603  targets.emplace_back(best->get_location(), best_target->value);
604  best_target = targets.end() - 1;
605  return std::pair(best->get_location(), best->get_location());
606  }
607 
608  LOG_AI << "Could not find anywhere to move!";
609  return std::pair<map_location,map_location>();
610 }
611 
612 void move_to_targets_phase::access_points(const move_map& srcdst, const map_location& u, const map_location& dst, std::vector<map_location>& out)
613 {
614  unit_map &units_ = resources::gameboard->units();
615  const gamemap &map_ = resources::gameboard->map();
616  const unit_map::const_iterator u_it = units_.find(u);
617  if(u_it == units_.end()) {
618  return;
619  }
620 
621  // unit_map single_unit(u_it->first, u_it->second);
622 
623  const auto locs = srcdst.equal_range(u);
624  for(move_map::const_iterator i = locs.first; i != locs.second; ++i) {
625  const map_location& loc = i->second;
626  if (static_cast<int>(distance_between(loc,dst)) <= u_it->total_movement()) {
628  const pathfind::teleport_map allowed_teleports = pathfind::get_teleport_locations(*u_it, current_team());
629  pathfind::plain_route rt = a_star_search(loc, dst, u_it->total_movement(), calc, map_.w(), map_.h(), &allowed_teleports);
630  if(rt.steps.empty() == false) {
631  out.push_back(loc);
632  }
633  }
634  }
635 }
636 
637 double move_to_targets_phase::compare_groups(const std::set<map_location>& our_group, const std::set<map_location>& their_group, const std::vector<map_location>& battlefield) const
638 {
639  const double a = rate_group(our_group,battlefield);
640  const double b = std::max<double>(rate_group(their_group,battlefield),0.01);
641  return a/b;
642 }
643 
644 void move_to_targets_phase::enemies_along_path(const std::vector<map_location>& route, const move_map& dstsrc, std::set<map_location>& res)
645 {
646  for(std::vector<map_location>::const_iterator i = route.begin(); i != route.end(); ++i) {
647  for(const map_location& adj : get_adjacent_tiles(*i)) {
648  const auto itors = dstsrc.equal_range(adj);
649  for(move_map::const_iterator j = itors.first; j != itors.second; ++j) {
650  res.insert(j->second);
651  }
652  }
653  }
654 }
655 
656 map_location move_to_targets_phase::form_group(const std::vector<map_location>& route, const move_map& dstsrc, std::set<map_location>& res)
657 {
658  unit_map &units_ = resources::gameboard->units();
659  if(route.empty()) {
660  return map_location();
661  }
662 
663  std::vector<map_location>::const_iterator i;
664  for(i = route.begin(); i != route.end(); ++i) {
665  if(units_.count(*i) > 0) {
666  continue;
667  }
668 
669  std::size_t n = 0, nunits = res.size();
670 
671  const auto itors = dstsrc.equal_range(*i);
672  for(move_map::const_iterator j = itors.first; j != itors.second; ++j) {
673  if(res.count(j->second) != 0) {
674  ++n;
675  } else {
676  const unit_map::const_iterator un = units_.find(j->second);
677  if(un == units_.end() || (un->can_recruit() && !is_keep_ignoring_leader(un->id())) || un->movement_left() < un->total_movement()) {
678  continue;
679  }
680 
681  res.insert(j->second);
682  }
683  }
684 
685  //if not all our units can reach this position.
686  if(n < nunits) {
687  break;
688  }
689  }
690 
691  if(i != route.begin()) {
692  --i;
693  }
694 
695  return *i;
696 }
697 
698 bool move_to_targets_phase::move_group(const map_location& dst, const std::vector<map_location>& route, const std::set<map_location>& units)
699 {
700  unit_map &units_ = resources::gameboard->units();
701  const gamemap &map_ = resources::gameboard->map();
702 
703  const std::vector<map_location>::const_iterator itor = std::find(route.begin(),route.end(),dst);
704  if(itor == route.end()) {
705  return false;
706  }
707 
708  LOG_AI << "group has " << units.size() << " members";
709 
710  map_location next;
711 
712  std::size_t direction = 0;
713 
714  //find the direction the group is moving in
715  if(itor+1 != route.end()) {
716  next = *(itor+1);
717  } else if(itor != route.begin()) {
718  next = *(itor-1);
719  }
720 
721  if(next.valid()) {
722  const auto adj = get_adjacent_tiles(dst);
723  direction = std::distance(adj.begin(), std::find(adj.begin(), adj.end(), next));
724  }
725 
726  std::deque<map_location> preferred_moves;
727  preferred_moves.push_back(dst);
728 
729  std::map<map_location,pathfind::paths> possible_moves;
730  move_map srcdst, dstsrc;
731  calculate_possible_moves(possible_moves,srcdst,dstsrc,false);
732 
733  bool gamestate_changed = false;
734 
735  for(std::set<map_location>::const_iterator i = units.begin(); i != units.end(); ++i) {
736  const unit_map::const_iterator un = units_.find(*i);
737  if(un == units_.end()) {
738  continue;
739  }
740 
741  map_location best_loc;
742  int best_defense = -1;
743  for(std::deque<map_location>::const_iterator j = preferred_moves.begin(); j != preferred_moves.end(); ++j) {
744  if(units_.count(*j)) {
745  continue;
746  }
747 
748  const auto itors = dstsrc.equal_range(*j);
749  move_map::const_iterator m;
750  for(m = itors.first; m != itors.second; ++m) {
751  if(m->second == *i) {
752  break;
753  }
754  }
755 
756  if(m == itors.second) {
757  continue;
758  }
759 
760  int defense = un->defense_modifier(map_.get_terrain(*j));
761  if(best_loc.valid() == false || defense < best_defense) {
762  best_loc = *j;
763  best_defense = defense;
764  }
765  }
766 
767  if(best_loc.valid()) {
768  move_result_ptr move_res = execute_move_action(*i,best_loc);
769  gamestate_changed |= move_res->is_gamestate_changed();
770 
771  //if we were ambushed or something went wrong, abort the group's movement.
772  if (!move_res->is_ok()) {
773  return gamestate_changed;
774  }
775 
776  preferred_moves.erase(std::find(preferred_moves.begin(),preferred_moves.end(),best_loc));
777 
778  //find locations that are 'perpendicular' to the direction of movement for further units to move to.
779  const auto adj = get_adjacent_tiles(best_loc);
780  for(std::size_t n = 0; n < adj.size(); ++n) {
781  if(n != direction && ((n+3)%6) != direction && map_.on_board(adj[n]) &&
782  units_.count(adj[n]) == 0 && std::count(preferred_moves.begin(),preferred_moves.end(),adj[n]) == 0) {
783  preferred_moves.push_front(adj[n]);
784  LOG_AI << "added moves: " << adj[n].wml_x() << "," << adj[n].wml_y();
785  }
786  }
787  } else {
788  LOG_AI << "Could not move group member to any of " << preferred_moves.size() << " locations";
789  }
790  }
791 
792  return gamestate_changed;
793 }
794 
795 double move_to_targets_phase::rate_group(const std::set<map_location>& group, const std::vector<map_location>& battlefield) const
796 {
797  unit_map &units_ = resources::gameboard->units();
798  const gamemap &map_ = resources::gameboard->map();
799 
800  double strength = 0.0;
801  for(std::set<map_location>::const_iterator i = group.begin(); i != group.end(); ++i) {
802  const unit_map::const_iterator u = units_.find(*i);
803  if(u == units_.end()) {
804  continue;
805  }
806 
807  const unit &un = *u;
808 
809  int defense = 0;
810  for(std::vector<map_location>::const_iterator j = battlefield.begin(); j != battlefield.end(); ++j) {
811  defense += un.defense_modifier(map_.get_terrain(*j));
812  }
813 
814  defense /= battlefield.size();
815 
816  int best_attack = 0;
817  for(const attack_type& a : un.attacks()) {
818  const int attack_strength = a.num_attacks() * a.damage();
819  best_attack = std::max<int>(attack_strength, best_attack);
820  }
821 
822  const int rating = (defense*best_attack*un.hitpoints())/(100*un.max_hitpoints());
823  strength += static_cast<double>(rating);
824  }
825 
826  return strength;
827 }
828 
830  const move_map& srcdst, const move_map& dstsrc, const move_map& enemy_dstsrc,
831  double caution)
832 {
833  if(caution <= 0.0) {
834  return false;
835  }
836 
837  double optimal_terrain = best_defensive_position(un->get_location(), dstsrc,
838  srcdst, enemy_dstsrc).chance_to_hit/100.0;
839  const double proposed_terrain =
840  un->defense_modifier(resources::gameboard->map().get_terrain(loc))/100.0;
841 
842  // The 'exposure' is the additional % chance to hit
843  // this unit receives from being on a sub-optimal defensive terrain.
844  const double exposure = proposed_terrain - optimal_terrain;
845 
846  const double our_power = power_projection(loc,dstsrc);
847  const double their_power = power_projection(loc,enemy_dstsrc);
848  return caution*their_power*(1.0+exposure) > our_power;
849 }
850 
851 } // end of namespace ai_default_rca
852 
853 } // end of namespace ai
map_location loc
Definition: move.cpp:172
Managing the AI-Game interaction - AI actions and their results.
double t
Definition: astarsearch.cpp:63
#define LOG_AI
#define DBG_AI
#define WRN_AI
Strategic movement routine, for experimentation.
void access_points(const move_map &srcdst, const map_location &u, const map_location &dst, std::vector< map_location > &out)
map_location form_group(const std::vector< map_location > &route, const move_map &dstsrc, std::set< map_location > &res)
void enemies_along_path(const std::vector< map_location > &route, const move_map &dstsrc, std::set< map_location > &res)
bool move_group(const map_location &dst, const std::vector< map_location > &route, const std::set< map_location > &units)
bool should_retreat(const map_location &loc, const unit_map::const_iterator &un, const move_map &srcdst, const move_map &dstsrc, const move_map &enemy_dstsrc, double caution)
double rate_group(const std::set< map_location > &group, const std::vector< map_location > &battlefield) const
virtual double evaluate()
Evaluate the candidate action, resetting the internal state of the action.
double compare_groups(const std::set< map_location > &our_group, const std::set< map_location > &their_group, const std::vector< map_location > &battlefield) const
move_to_targets_phase(rca_context &context, const config &cfg)
double rate_target(const target &tg, const unit_map::iterator &u, const move_map &dstsrc, const move_map &enemy_dstsrc, const pathfind::plain_route &rt)
rate a target, but can also return the maximal possible rating by passing a dummy route
virtual void execute()
Execute the candidate action.
std::pair< map_location, map_location > choose_move(std::vector< target > &targets)
remove_wrong_targets(const readonly_context &context)
bool is_allowed_unit(const unit &u) const
Flag indicating whether unit may be used by this candidate action.
Definition: rca.cpp:88
double get_score() const
Get the usual score of the candidate action without re-evaluation.
Definition: rca.cpp:73
virtual std::vector< target > find_targets(const move_map &enemy_dstsrc)
Definition: contexts.hpp:187
virtual const std::vector< target > & additional_targets() const
Definition: contexts.hpp:167
virtual double get_caution() const override
Definition: contexts.hpp:591
virtual const team & current_team() const override
Definition: contexts.hpp:450
virtual bool is_keep_ignoring_leader(const std::string &id) const override
Definition: contexts.hpp:761
virtual const move_map & get_dstsrc() const override
Definition: contexts.hpp:596
virtual std::string get_grouping() const override
Definition: contexts.hpp:631
virtual void raise_user_interact() const override
Definition: contexts.hpp:519
virtual bool get_simple_targeting() const override
Definition: contexts.hpp:736
virtual const move_map & get_srcdst() const override
Definition: contexts.hpp:716
const defensive_position & best_defensive_position(const map_location &unit, const move_map &dstsrc, const move_map &srcdst, const move_map &enemy_dstsrc) const override
Definition: contexts.hpp:530
virtual void calculate_possible_moves(std::map< map_location, pathfind::paths > &possible_moves, move_map &srcdst, move_map &dstsrc, bool enemy, bool assume_full_movement=false, const terrain_filter *remove_destinations=nullptr) const override
Definition: contexts.hpp:497
virtual double power_projection(const map_location &loc, const move_map &dstsrc) const override
Function which finds how much 'power' a side can attack a certain location with.
Definition: contexts.hpp:681
virtual double get_scout_village_targeting() const override
Definition: contexts.hpp:731
virtual const move_map & get_enemy_dstsrc() const override
Definition: contexts.hpp:601
virtual move_result_ptr execute_move_action(const map_location &from, const map_location &to, bool remove_movement=true, bool unreach_is_ok=false) override
Definition: contexts.hpp:898
virtual side_number get_side() const override
Get the side number.
Definition: contexts.hpp:396
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:158
virtual const unit_map & units() const override
Definition: game_board.hpp:107
virtual const gamemap & map() const override
Definition: game_board.hpp:97
terrain_code get_terrain(const map_location &loc) const
Looks up terrain at a particular location.
Definition: map.cpp:301
int w() const
Effective map width.
Definition: map.hpp:50
int h() const
Effective map height.
Definition: map.hpp:53
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition: map.cpp:384
Encapsulates the map of the game.
Definition: map.hpp:172
Container associating units to locations.
Definition: map.hpp:98
unit_iterator end()
Definition: map.hpp:428
std::size_t count(const map_location &loc) const
Definition: map.hpp:413
unit_iterator find(std::size_t id)
Definition: map.cpp:302
unit_iterator begin()
Definition: map.hpp:418
unit_iterator find_leader(int side)
Definition: map.cpp:320
This class represents a single unit of a specific type.
Definition: unit.hpp:133
std::size_t i
Definition: function.cpp:1030
int max_hitpoints() const
The max number of hitpoints this unit can have.
Definition: unit.hpp:521
int hitpoints() const
The current number of hitpoints this unit has.
Definition: unit.hpp:515
int defense_modifier(const t_translation::terrain_code &terrain) const
The unit's defense on a given terrain.
Definition: unit.cpp:1733
attack_itors attacks()
Gets an iterator over this unit's attacks.
Definition: unit.hpp:942
int movement_cost(const t_translation::terrain_code &terrain) const
Get the unit's movement cost on a particular terrain.
Definition: unit.hpp:1496
void get_adjacent_tiles(const map_location &a, map_location *res)
Function which, given a location, will place all adjacent locations in res.
Definition: location.cpp:512
std::size_t distance_between(const map_location &a, const map_location &b)
Function which gives the number of hexes between two tiles (i.e.
Definition: location.cpp:583
Standard logging facilities (interface).
#define log_scope2(domain, description)
Definition: log.hpp:276
static lg::log_domain log_ai_testing_ca_move_to_targets("ai/ca/move_to_targets")
A small explanation about what's going on here: Each action has access to two game_info objects First...
Definition: actions.cpp:59
std::multimap< map_location, map_location > move_map
The standard way in which a map of possible moves is recorded.
Definition: game_info.hpp:43
std::shared_ptr< move_result > move_result_ptr
Definition: game_info.hpp:85
plain_route a_star_search(const map_location &src, const map_location &dst, double stop_at, const cost_calculator &calc, const std::size_t width, const std::size_t height, const teleport_map *teleports, bool border)
const teleport_map get_teleport_locations(const unit &u, const team &viewing_team, bool see_all, bool ignore_units, bool check_vision)
Definition: teleport.cpp:251
game_board * gameboard
Definition: resources.cpp:20
void erase_if(Container &container, const Predicate &predicate)
Convenience wrapper for using std::remove_if on a container.
Definition: general.hpp:106
auto * find(Container &container, const Value &value)
Convenience wrapper for using find on a container without needing to comare to end()
Definition: general.hpp:140
std::shared_ptr< move > move_ptr
Definition: typedefs.hpp:68
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
This module contains various pathfinding functions and utilities.
rect dst
Location on the final composed sheet.
double cost(const map_location &loc, const double) const
move_cost_calculator(const unit &u, const gamemap &map, const unit_map &units, const move_map &enemy_dstsrc)
bool operator()(const rated_target &a, const rated_target &b) const
rated_target(const std::vector< target >::iterator &t, double r)
std::vector< target >::iterator tg
map_location loc
Definition: contexts.hpp:33
ai_target::type type
Definition: contexts.hpp:36
double value
Definition: contexts.hpp:34
Encapsulates the map of the game.
Definition: location.hpp:45
bool valid() const
Definition: location.hpp:110
static double getNoPathValue()
Definition: pathfind.hpp:65
Structure which holds a single route between one location and another.
Definition: pathfind.hpp:133
std::vector< map_location > steps
Definition: pathfind.hpp:135
int move_cost
Movement cost for reaching the end of the route.
Definition: pathfind.hpp:137
A terrain string which is converted to a terrain is a string with 1 or 2 layers the layers are separa...
Definition: translation.hpp:49
static map_location::direction n
#define b