ai.cpp

Go to the documentation of this file.
00001 /* $Id: ai.cpp 26324 2008-05-02 17:22:23Z boucman $ */
00002 /*
00003    Copyright (C) 2003 - 2008 by David White <dave@whitevine.net>
00004    Part of the Battle for Wesnoth Project http://www.wesnoth.org/
00005 
00006    This program is free software; you can redistribute it and/or modify
00007    it under the terms of the GNU General Public License version
00008    or at your option any later version2
00009    or at your option any later version.
00010    This program is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY.
00012 
00013    See the COPYING file for more details.
00014 */
00015 
00016 
00017 //! @file ai.cpp
00018 //! Artificial intelligence - The computer commands the enemy.
00019 
00020 #include "ai.hpp"
00021 #include "ai2.hpp"
00022 #include "ai_dfool.hpp"
00023 #ifdef HAVE_PYTHON
00024 #include "ai_python.hpp"
00025 #endif
00026 #include "actions.hpp"
00027 #include "callable_objects.hpp"
00028 #include "dialogs.hpp"
00029 #include "foreach.hpp"
00030 #include "formula_ai.hpp"
00031 #include "game_config.hpp"
00032 #include "game_events.hpp"
00033 #include "game_preferences.hpp"
00034 #include "gettext.hpp"
00035 #include "log.hpp"
00036 #include "menu_events.hpp"
00037 #include "replay.hpp"
00038 #include "statistics.hpp"
00039 #include "unit_display.hpp"
00040 #include "unit.hpp"
00041 #include "playturn.hpp"
00042 #include "wml_exception.hpp"
00043 
00044 #include <cassert>
00045 
00046 #define LOG_AI LOG_STREAM(info, ai)
00047 #define WRN_AI LOG_STREAM(warn, ai)
00048 #define ERR_AI LOG_STREAM(err, ai)
00049 
00050 //! A trivial ai that sits around doing absolutely nothing.
00051 class idle_ai : public ai_interface {
00052 public:
00053     idle_ai(info& i) : ai_interface(i) {}
00054     void play_turn() {
00055         game_events::fire("ai turn");
00056     }
00057 };
00058 
00059 //! Sample ai, with simple strategy.
00060 class sample_ai : public ai_interface {
00061 public:
00062     sample_ai(info& i) : ai_interface(i) {}
00063 
00064     void play_turn() {
00065         game_events::fire("ai turn");
00066         do_attacks();
00067         get_villages();
00068         do_moves();
00069         do_recruitment();
00070     }
00071 
00072 protected:
00073     void do_attacks() {
00074         std::map<location,paths> possible_moves;
00075         move_map srcdst, dstsrc;
00076         calculate_possible_moves(possible_moves,srcdst,dstsrc,false);
00077 
00078         for(unit_map::const_iterator i = get_info().units.begin(); i != get_info().units.end(); ++i) {
00079             if(current_team().is_enemy(i->second.side())) {
00080                 location adjacent_tiles[6];
00081                 get_adjacent_tiles(i->first,adjacent_tiles);
00082 
00083                 int best_defense = -1;
00084                 std::pair<location,location> best_movement;
00085 
00086                 for(size_t n = 0; n != 6; ++n) {
00087                     typedef move_map::const_iterator Itor;
00088                     std::pair<Itor,Itor> range = dstsrc.equal_range(adjacent_tiles[n]);
00089                     while(range.first != range.second) {
00090                         const location& dst = range.first->first;
00091                         const location& src = range.first->second;
00092                         const unit_map::const_iterator un = get_info().units.find(src);
00093 
00094                         const t_translation::t_terrain terrain = get_info().map.get_terrain(dst);
00095 
00096                         const int chance_to_hit = un->second.defense_modifier(terrain);
00097 
00098                         if(best_defense == -1 || chance_to_hit < best_defense) {
00099                             best_defense = chance_to_hit;
00100                             best_movement = *range.first;
00101                         }
00102 
00103                         ++range.first;
00104                     }
00105                 }
00106 
00107                 if(best_defense != -1) {
00108                     move_unit(best_movement.second,best_movement.first,possible_moves);
00109                     battle_context bc(get_info().map, get_info().teams,
00110                                       get_info().units, get_info().state,
00111                                       best_movement.first,
00112                                       i->first, -1, -1, current_team().aggression());
00113                     attack_enemy(best_movement.first,i->first,
00114                                  bc.get_attacker_stats().attack_num,
00115                                  bc.get_defender_stats().attack_num);
00116                     do_attacks();
00117                     return;
00118                 }
00119             }
00120         }
00121     }
00122 
00123     void get_villages() {
00124         std::map<location,paths> possible_moves;
00125         move_map srcdst, dstsrc;
00126         calculate_possible_moves(possible_moves,srcdst,dstsrc,false);
00127 
00128         for(move_map::const_iterator i = dstsrc.begin(); i != dstsrc.end(); ++i) {
00129             if(get_info().map.is_village(i->first) && current_team().owns_village(i->first) == false) {
00130                 move_unit(i->second,i->first,possible_moves);
00131                 get_villages();
00132                 return;
00133             }
00134         }
00135     }
00136 
00137     void do_moves() {
00138         unit_map::const_iterator leader;
00139         for(leader = get_info().units.begin(); leader != get_info().units.end(); ++leader) {
00140             if(leader->second.can_recruit() && current_team().is_enemy(leader->second.side())) {
00141                 break;
00142             }
00143         }
00144 
00145         if(leader == get_info().units.end())
00146             return;
00147 
00148         std::map<location,paths> possible_moves;
00149         move_map srcdst, dstsrc;
00150         calculate_possible_moves(possible_moves,srcdst,dstsrc,false);
00151 
00152         int closest_distance = -1;
00153         std::pair<location,location> closest_move;
00154 
00155         for(move_map::const_iterator i = dstsrc.begin(); i != dstsrc.end(); ++i) {
00156             const int distance = distance_between(i->first,leader->first);
00157             if(closest_distance == -1 || distance < closest_distance) {
00158                 closest_distance = distance;
00159                 closest_move = *i;
00160             }
00161         }
00162 
00163         if(closest_distance != -1) {
00164             move_unit(closest_move.second,closest_move.first,possible_moves);
00165             do_moves();
00166         }
00167     }
00168 
00169     void do_recruitment() {
00170         const std::set<std::string>& options = current_team().recruits();
00171     if (!options.empty()) {
00172             const int choice = (rand()%options.size());
00173                 std::set<std::string>::const_iterator i = options.begin();
00174                 std::advance(i,choice);
00175 
00176             const bool res = recruit(*i);
00177             if(res) {
00178                 do_recruitment();
00179             }
00180         }
00181     }
00182 };
00183 
00184 std::vector<std::string> get_available_ais()
00185 {
00186     std::vector<std::string> ais;
00187     ais.push_back("default");
00188     ais.push_back("sample_ai");
00189     //ais.push_back("idle_ai");
00190     ais.push_back("dfool_ai");
00191 #ifdef HAVE_PYTHON
00192     std::vector<std::string> scripts = python_ai::get_available_scripts();
00193     ais.insert(ais.end(), scripts.begin(), scripts.end());
00194 #endif
00195     return ais;
00196 }
00197 
00198 ai_interface* create_ai(const std::string& name, ai_interface::info& info)
00199 {
00200     // To add an AI of your own, put
00201     //      if(name == "my_ai")
00202     //          return new my_ai(info);
00203     // at the top of this function
00204 
00205     if(name == "sample_ai")
00206         return new sample_ai(info);
00207     else if(name == "idle_ai")
00208         return new idle_ai(info);
00209     else if(name == "formula_ai")
00210         return new formula_ai(info);
00211     else if(name == "dfool_ai")
00212         return new dfool::dfool_ai(info);
00213     //else if(name == "advanced_ai")
00214     //  return new advanced_ai(info);
00215     else if(name == "ai2")
00216         return new ai2(info);
00217     else if(name == "python_ai")
00218 #ifdef HAVE_PYTHON
00219         return new python_ai(info);
00220 #else
00221     {
00222         LOG_STREAM(err, ai) << "No Python AI support available in this Wesnoth build!\n";
00223         return new ai2(info);
00224     }
00225 #endif
00226     else if(name != "") {
00227         LOG_STREAM(err, ai) << "AI not found: '" << name << "'\n";
00228     }
00229 
00230     return new ai(info);
00231 }
00232 
00233 ai::ai(ai_interface::info& info) :
00234     ai_interface(info),
00235     defensive_position_cache_(),
00236     threats_found_(false),
00237     attacks_(),
00238     disp_(info.disp),
00239     map_(info.map),
00240     units_(info.units),
00241     teams_(info.teams),
00242     team_num_(info.team_num),
00243     state_(info.state),
00244     consider_combat_(true),
00245     additional_targets_(),
00246     unit_movement_scores_(),
00247     not_recommended_units_(),
00248     unit_combat_scores_(),
00249     keeps_(),
00250     avoid_(),
00251     unit_stats_cache_(),
00252     attack_depth_(0)
00253 {}
00254 
00255 bool ai::recruit_usage(const std::string& usage)
00256 {
00257     raise_user_interact();
00258 
00259     const int min_gold = 0;
00260 
00261     log_scope2(ai, "recruiting troops");
00262     LOG_AI << "recruiting '" << usage << "'\n";
00263 
00264     //make sure id, usage and cost are known for the coming evaluation of unit types
00265     unit_type_data::types().build_all(unit_type::HELP_INDEX);
00266 
00267     std::vector<std::string> options;
00268     bool found = false;
00269     // Find an available unit that can be recruited,
00270     // matches the desired usage type, and comes in under budget.
00271     const std::set<std::string>& recruits = current_team().recruits();
00272     for(std::map<std::string,unit_type>::const_iterator i =
00273         unit_type_data::types().begin(); i != unit_type_data::types().end(); ++i)
00274     {
00275         const std::string& name = i->second.id();
00276         // If usage is empty consider any unit.
00277         LOG_AI << name << " considered\n";
00278         if (i->second.usage() == usage || usage == "") {
00279             found = true;
00280             LOG_AI << name << " considered for " << usage << " recruitment\n";
00281             if (!recruits.count(name)) {
00282                 LOG_AI << name << " rejected, not in recruitment list\n";
00283                 continue;
00284             }
00285 
00286             if (current_team().gold() - i->second.cost() < min_gold) {
00287                 LOG_AI << name << " rejected, cost too high (cost: " << i->second.cost() << ", current gold: " << current_team().gold() <<", min_gold: " << min_gold << ")\n";
00288                 continue;
00289             }
00290 
00291             if (not_recommended_units_.count(name))
00292             {
00293                 LOG_AI << name << " rejected, bad terrain or combat\n";
00294                 continue;
00295             }
00296 
00297             LOG_AI << "recommending '" << name << "'\n";
00298             options.push_back(name);
00299         }
00300     }
00301 
00302     // From the available options, choose one at random
00303     if(options.empty() == false) {
00304         const int option = rand()%options.size();
00305         return recruit(options[option]);
00306     }
00307     if (found) {
00308         LOG_AI << "No available units to recruit that come under the price.\n";
00309     } else {
00310         WRN_AI << "Trying to recruit a: " << usage
00311             << " but no unit of that type (usage=) is available.\n";
00312     }
00313     return false;
00314 }
00315 
00316 bool ai_interface::recruit(const std::string& unit_name, location loc)
00317 {
00318     const std::set<std::string>& recruits = current_team().recruits();
00319 
00320     const std::set<std::string>::const_iterator i = recruits.find(unit_name);
00321     if(i == recruits.end()) {
00322         return false;
00323     }
00324 
00325     const int num = std::distance(recruits.begin(),i);
00326 
00327     // We have to add the recruit command now, because when the unit
00328     // is created it has to have the recruit command in the recorder
00329     // to be able to put random numbers into to generate unit traits.
00330     // However, we're not sure if the transaction will be successful,
00331     // so use a replay_undo object to cancel it if we don't get
00332     // a confirmation for the transaction.
00333     recorder.add_recruit(num,loc);
00334     replay_undo replay_guard(recorder);
00335 
00336     unit_type_data::unit_type_map::const_iterator u = unit_type_data::types().find(unit_name);
00337     if(u == unit_type_data::types().end()) {
00338         return false;
00339     }
00340 
00341     // Check if we have enough money
00342     if(current_team().gold() < u->second.cost()) {
00343         return false;
00344     }
00345     LOG_AI << "trying recruit: team=" << (info_.team_num) <<
00346         " type=" << unit_name <<
00347         " cost=" << u->second.cost() <<
00348         " loc=(" << loc << ')' <<
00349         " gold=" << (current_team().gold()) <<
00350         " (-> " << (current_team().gold()-u->second.cost()) << ")\n";
00351 
00352     unit new_unit(&info_.units,&info_.map,&info_.state,&info_.teams,&u->second,info_.team_num,true);
00353 
00354     // See if we can actually recruit (i.e. have enough room etc.)
00355     std::string recruit_err = recruit_unit(info_.map,info_.team_num,info_.units,new_unit,loc,false,preferences::show_ai_moves());
00356     if(recruit_err.empty()) {
00357 
00358         statistics::recruit_unit(new_unit);
00359         current_team().spend_gold(u->second.cost());
00360 
00361         // Confirm the transaction - i.e. don't undo recruitment
00362         replay_guard.confirm_transaction();
00363 
00364         raise_unit_recruited();
00365         const team_data data = calculate_team_data(current_team(),info_.team_num,info_.units);
00366         LOG_AI <<
00367         "recruit confirmed: team=" << (info_.team_num) <<
00368         " units=" << data.units <<
00369         " gold=" << data.gold <<
00370         ((data.net_income < 0) ? "" : "+") <<
00371         data.net_income << "\n";
00372         recorder.add_checksum_check(loc);
00373         return true;
00374     } else {
00375         const team_data data = calculate_team_data(current_team(),info_.team_num,info_.units);
00376         LOG_AI << recruit_err << "\n";
00377         LOG_AI <<
00378         "recruit UNconfirmed: team=" << (info_.team_num) <<
00379         " units=" << data.units <<
00380         " gold=" << data.gold <<
00381         ((data.net_income < 0) ? "" : "+") <<
00382         data.net_income << "\n";
00383         return false;
00384     }
00385 }
00386 
00387 void ai_interface::raise_user_interact()
00388 {
00389     const int interact_time = 30;
00390     const int time_since_interact = SDL_GetTicks() - last_interact_;
00391     if(time_since_interact < interact_time) {
00392         return;
00393     }
00394 
00395     user_interact_.notify_observers();
00396 
00397     last_interact_ = SDL_GetTicks();
00398 }
00399 
00400 void ai_interface::diagnostic(const std::string& msg)
00401 {
00402     if(game_config::debug) {
00403         info_.disp.set_diagnostic(msg);
00404     }
00405 }
00406 
00407 void ai_interface::log_message(const std::string& msg)
00408 {
00409     if(game_config::debug) {
00410         info_.disp.add_chat_message(time(NULL), "ai", info_. team_num, msg,
00411                 game_display::MESSAGE_PUBLIC, false);
00412     }
00413 }
00414 
00415 
00416 gamemap::location ai_interface::move_unit(location from, location to,
00417         std::map<location,paths>& possible_moves)
00418 {
00419     const location loc = move_unit_partial(from,to,possible_moves);
00420     const unit_map::iterator u = info_.units.find(loc);
00421     if(u != info_.units.end()) {
00422         if(u->second.movement_left()==u->second.total_movement()) {
00423             u->second.set_movement(0);
00424             u->second.set_state("not_moved","yes");
00425         } else {
00426             u->second.set_movement(0);
00427         }
00428     }
00429 
00430     return loc;
00431 }
00432 
00433 gamemap::location ai_interface::move_unit_partial(location from, location to,
00434         std::map<location,paths>& possible_moves)
00435 {
00436     LOG_AI << "ai_interface::move_unit " << from << " -> " << to << '\n';
00437 
00438     // Stop the user from issuing any commands while the unit is moving.
00439     const events::command_disabler disable_commands;
00440 
00441     log_scope2(ai, "move_unit");
00442     unit_map::iterator u_it = info_.units.find(from);
00443     if(u_it == info_.units.end()) {
00444         LOG_STREAM(err, ai) << "Could not find unit at " << from << '\n';
00445         assert(false);
00446         return location();
00447     }
00448 
00449     if(from == to) {
00450         LOG_AI << "moving unit at " << from << " on spot. resetting moves\n";
00451         return to;
00452     }
00453 
00454     const bool show_move = preferences::show_ai_moves();
00455 
00456     const std::map<location,paths>::iterator p_it = possible_moves.find(from);
00457 
00458     if(p_it != possible_moves.end()) {
00459         paths& p = p_it->second;
00460         std::map<location,paths::route>::iterator rt = p.routes.begin();
00461         for(; rt != p.routes.end(); ++rt) {
00462             if(rt->first == to) {
00463                 break;
00464             }
00465         }
00466 
00467         if(rt != p.routes.end()) {
00468             u_it->second.set_movement(rt->second.move_left);
00469 
00470             std::vector<location> steps = rt->second.steps;
00471 
00472             while(steps.empty() == false && (!(info_.units.find(to) == info_.units.end() || from == to))){
00473                     LOG_AI << "AI attempting illegal move. Attempting to move onto existing unit\n";
00474                     LOG_AI << "\t" << info_.units.find(to)->second.underlying_id() <<" already on " << to << "\n";
00475                     LOG_AI <<"\tremoving "<<*(steps.end()-1)<<"\n";
00476                     to = *(steps.end()-1);
00477                     steps.pop_back();
00478                     LOG_AI << "\tresetting to " << from << " -> " << to << '\n';
00479             }
00480 
00481             if(steps.size()>1) { // First step is starting hex
00482               unit_map::const_iterator utest=info_.units.find(*(steps.begin()+1));
00483               if(utest != info_.units.end() && current_team().is_enemy(utest->second.side())){
00484                 LOG_STREAM(err, ai) << "AI tried to move onto existing enemy unit at"<<*(steps.begin())<<"\n";
00485                 //              return(from);
00486               }
00487 
00488                 // Check if there are any invisible units that we uncover
00489                 for(std::vector<location>::iterator i = steps.begin()+1; i != steps.end(); ++i) {
00490                     location adj[6];
00491                     get_adjacent_tiles(*i,adj);
00492 
00493                     size_t n;
00494                     for(n = 0; n != 6; ++n) {
00495 
00496                         // See if there is an enemy unit next to this tile.
00497                         // If it's invisible, we need to stop: we're ambushed.
00498                         // If it's not, we must be a skirmisher, otherwise AI wouldn't try.
00499 
00500                         // Or would it?  If it doesn't cheat, it might...
00501                         const unit_map::const_iterator u = info_.units.find(adj[n]);
00502                         if (u != info_.units.end() && u->second.emits_zoc()
00503                             && current_team().is_enemy(u->second.side())) {
00504                             if (u->second.invisible(adj[n], info_.units, info_.teams)) {
00505                                 to = *i;
00506                                 steps.erase(i,steps.end());
00507                                 break;
00508                             } else {
00509                               if (!u_it->second.get_ability_bool("skirmisher",*i)){
00510                                 LOG_STREAM(err, ai) << "AI tried to skirmish with non-skirmisher\n";
00511                                 LOG_AI << "\tresetting destination from " <<to;
00512                                 to = *i;
00513                                 LOG_AI << " to " << to;
00514                                 steps.erase(i,steps.end());
00515                                 while(steps.empty() == false && (!(info_.units.find(to) == info_.units.end() || from == to))){
00516                                  to = *(steps.end()-1);
00517                                  steps.pop_back();
00518                                  LOG_AI << "\tresetting to " << from << " -> " << to << '\n';
00519                                 }
00520 
00521                                 break;
00522                               }
00523                             }
00524                         }
00525                     }
00526 
00527                     if(n != 6) {
00528                         u_it->second.set_movement(0); // Enter enemy ZoC, no movement left
00529                         break;
00530                     }
00531                 }
00532             }
00533 
00534             steps.push_back(to); // Add the destination to the steps
00535 
00536             if(show_move && unit_display::unit_visible_on_path(steps,
00537                         u_it->second, info_.units,info_.teams)) {
00538 
00539                 
00540                 info_.disp.display_unit_hex(from);
00541 
00542                 unit_map::iterator up = info_.units.find(u_it->first);
00543                 unit_display::move_unit(steps,up->second,info_.teams);
00544             }
00545         }
00546     }
00547 
00548     std::pair<gamemap::location,unit> *p = info_.units.extract(u_it->first);
00549 
00550     p->first = to;
00551     info_.units.add(p);
00552     p->second.set_standing(p->first);
00553     if(info_.map.is_village(to)) {
00554         // If a new village is captured, disallow any future movement.
00555         if (!info_.teams[info_.team_num-1].owns_village(to))
00556             info_.units.find(to)->second.set_movement(-1);
00557         get_village(to,info_.disp,info_.teams,info_.team_num-1,info_.units);
00558     }
00559 
00560     if(show_move) {
00561         info_.disp.invalidate(to);
00562         info_.disp.draw();
00563     }
00564 
00565     recorder.add_movement(from,to);
00566 
00567     game_events::fire("moveto",to);
00568 
00569     if((info_.teams.front().uses_fog() || info_.teams.front().uses_shroud()) &&
00570             !info_.teams.front().fogged(to)) {
00571         game_events::fire("sighted",to);
00572     }
00573 
00574     // would have to go via mousehandler to make this work:
00575     //info_.disp.unhighlight_reach();
00576     raise_unit_moved();
00577 
00578     return to;
00579 }
00580 
00581 bool ai::multistep_move_possible(const location& from,
00582     const location& to, const location& via,
00583     const std::map<location,paths>& possible_moves) const
00584 {
00585     const unit_map::const_iterator i = units_.find(from);
00586     if(i != units_.end()) {
00587         if(from != via && to != via && units_.count(via) == 0) {
00588             LOG_AI << "when seeing if leader can move from "
00589                 << from << " -> " << to
00590                 << " seeing if can detour to keep at " << via << '\n';
00591             const std::map<location,paths>::const_iterator moves = possible_moves.find(from);
00592             if(moves != possible_moves.end()) {
00593 
00594                 LOG_AI << "found leader moves..\n";
00595 
00596                 int move_left = 0;
00597 
00598                 // See if the unit can make it to 'via', and if it can,
00599                 // how much movement it will have left when it gets there.
00600                 const paths::routes_map::const_iterator itor = moves->second.routes.find(via);
00601                 if(itor != moves->second.routes.end()) {
00602                     move_left = itor->second.move_left;
00603                     LOG_AI << "can make it to keep with " << move_left << " movement left\n";
00604                     unit temp_unit(i->second);
00605                     temp_unit.set_movement(move_left);
00606                     const temporary_unit_placer unit_placer(units_,via,temp_unit);
00607                     const paths unit_paths(map_,units_,via,teams_,false,false,current_team());
00608 
00609                     LOG_AI << "found " << unit_paths.routes.size() << " moves for temp leader\n";
00610 
00611                     // See if this leader could make it back to the keep.
00612                     if(unit_paths.routes.count(to) != 0) {
00613                         LOG_AI << "can make it back to the keep\n";
00614                         return true;
00615                     }
00616                 }
00617             }
00618         }
00619     }
00620 
00621     return false;
00622 }
00623 
00624 gamemap::location ai::move_unit(location from, location to, std::map<location,paths>& possible_moves)
00625 {
00626     std::map<location,paths> temp_possible_moves;
00627     std::map<location,paths>* possible_moves_ptr = &possible_moves;
00628 
00629     const unit_map::const_iterator i = units_.find(from);
00630     if(i != units_.end() && i->second.can_recruit()) {
00631 
00632         // If the leader isn't on its keep, and we can move to the keep
00633         // and still make our planned movement, then try doing that.
00634         const gamemap::location& start_pos = nearest_keep(i->first);
00635 
00636         // If we can make it back to the keep and then to our original destination, do so.
00637         if(multistep_move_possible(from,to,start_pos,possible_moves)) {
00638             from = ai_interface::move_unit(from,start_pos,possible_moves);
00639             if(from != start_pos) {
00640                 return from;
00641             }
00642 
00643             const unit_map::iterator itor = units_.find(from);
00644             if(itor != units_.end()) {
00645                 // Just set the movement to one less than the maximum possible, since we know
00646                 // we can reach the destination, and we're going to move there immediately.
00647                 itor->second.set_movement(itor->second.total_movement()-1);
00648             }
00649 
00650             move_map srcdst, dstsrc;
00651             calculate_possible_moves(temp_possible_moves,srcdst,dstsrc,false);
00652             possible_moves_ptr = &temp_possible_moves;
00653         }
00654 
00655         do_recruitment();
00656     }
00657 
00658     if(units_.count(to) == 0 || from == to) {
00659         const location res = ai_interface::move_unit(from,to,*possible_moves_ptr);
00660         if(res != to) {
00661             // We've been ambushed; find the ambushing unit and attack them.
00662             adjacent_tiles_array locs;
00663             get_adjacent_tiles(res,locs.data());
00664             for(adjacent_tiles_array::const_iterator adj_i = locs.begin(); adj_i != locs.end(); ++adj_i) {
00665                 const unit_map::const_iterator itor = units_.find(*adj_i);
00666                 if(itor != units_.end() && current_team().is_enemy(itor->second.side()) &&
00667                    !itor->second.incapacitated()) {
00668                     battle_context bc(map_, teams_, units_, state_,
00669                                       res, *adj_i, -1, -1, current_team().aggression());
00670                     attack_enemy(res,itor->first,bc.get_attacker_stats().attack_num,bc.get_defender_stats().attack_num);
00671                     break;
00672                 }
00673             }
00674         }
00675 
00676         return res;
00677     } else {
00678         return from;
00679     }
00680 }
00681 
00682 bool ai::attack_close(const gamemap::location& loc) const
00683 {
00684     for(std::set<location>::const_iterator i = attacks_.begin(); i != attacks_.end(); ++i) {
00685         if(distance_between(*i,loc) < 4) {
00686             return true;
00687         }
00688     }
00689 
00690     return false;
00691 }
00692 
00693 void ai::attack_enemy(const location& attacking_unit, const location& target,
00694         int att_weapon, int def_weapon)
00695 {
00696     attacks_.insert(attacking_unit);
00697     ai_interface::attack_enemy(attacking_unit,target,att_weapon,def_weapon);
00698 }
00699 
00700 void ai_interface::calculate_possible_moves(std::map<location,paths>& res, move_map& srcdst,
00701         move_map& dstsrc, bool enemy, bool assume_full_movement,
00702         const std::set<gamemap::location>* remove_destinations) const
00703 {
00704   calculate_moves(info_.units,res,srcdst,dstsrc,enemy,assume_full_movement,remove_destinations);
00705 }
00706 
00707 void ai_interface::calculate_moves(const unit_map& units, std::map<location,paths>& res, move_map& srcdst,
00708         move_map& dstsrc, bool enemy, bool assume_full_movement,
00709          const std::set<gamemap::location>* remove_destinations,
00710         bool see_all
00711           ) const
00712 {
00713 
00714     for(unit_map::const_iterator un_it = units.begin(); un_it != units.end(); ++un_it) {
00715         // If we are looking for the movement of enemies, then this unit must be an enemy unit.
00716         // If we are looking for movement of our own units, it must be on our side.
00717         // If we are assuming full movement, then it may be a unit on our side, or allied.
00718         if((enemy && current_team().is_enemy(un_it->second.side()) == false) ||
00719            (!enemy && !assume_full_movement && un_it->second.side() != info_.team_num) ||
00720            (!enemy && assume_full_movement && current_team().is_enemy(un_it->second.side()))) {
00721             continue;
00722         }
00723         // Discount incapacitated units
00724         if(un_it->second.incapacitated()) {
00725             continue;
00726         }
00727 
00728         // We can't see where invisible enemy units might move.
00729         if(enemy && un_it->second.invisible(un_it->first,units,info_.teams) && !see_all) {
00730             continue;
00731         }
00732         // If it's an enemy unit, reset its moves while we do the calculations.
00733         unit* held_unit = const_cast<unit*>(&(un_it->second));
00734         const unit_movement_resetter move_resetter(*held_unit,enemy || assume_full_movement);
00735 
00736         // Insert the trivial moves of staying on the same location.
00737         if(un_it->second.movement_left() == un_it->second.total_movement()) {
00738             std::pair<location,location> trivial_mv(un_it->first,un_it->first);
00739             srcdst.insert(trivial_mv);
00740             dstsrc.insert(trivial_mv);
00741         }
00742         const bool teleports = un_it->second.get_ability_bool("teleport",un_it->first);
00743         res.insert(std::pair<gamemap::location,paths>(
00744                         un_it->first,paths(info_.map,units,
00745                      un_it->first,info_.teams,false,teleports,
00746                                     current_team(),0,see_all)));
00747     }
00748 
00749     for(std::map<location,paths>::iterator m = res.begin(); m != res.end(); ++m) {
00750         for(paths::routes_map::iterator rtit =
00751             m->second.routes.begin(); rtit != m->second.routes.end(); ++rtit) {
00752             const location& src = m->first;
00753             const location& dst = rtit->first;
00754 
00755             if(remove_destinations != NULL && remove_destinations->count(dst) != 0) {
00756                 continue;
00757             }
00758 
00759             bool friend_owns = false;
00760 
00761             // Don't take friendly villages
00762             if(!enemy && info_.map.is_village(dst)) {
00763                 for(size_t n = 0; n != info_.teams.size(); ++n) {
00764                     if(info_.teams[n].owns_village(dst)) {
00765                         if(n+1 != info_.team_num && current_team().is_enemy(n+1) == false) {
00766                             friend_owns = true;
00767                         }
00768 
00769                         break;
00770                     }
00771                 }
00772             }
00773 
00774             if(friend_owns) {
00775                 continue;
00776             }
00777 
00778             if(src != dst && units.find(dst) == units.end()) {
00779                 srcdst.insert(std::pair<location,location>(src,dst));
00780                 dstsrc.insert(std::pair<location,location>(dst,src));
00781             }
00782         }
00783     }
00784 }
00785 
00786 void ai::remove_unit_from_moves(const gamemap::location& loc, move_map& srcdst, move_map& dstsrc)
00787 {
00788     srcdst.erase(loc);
00789     for(move_map::iterator i = dstsrc.begin(); i != dstsrc.end(); ) {
00790         if(i->second == loc) {
00791             dstsrc.erase(i++);
00792         } else {
00793             ++i;
00794         }
00795     }
00796 }
00797 
00798 namespace {
00799 
00800 //! A structure for storing an item we're trying to protect.
00801 struct protected_item {
00802     protected_item(double value, int radius, const gamemap::location& loc) :
00803         value(value), radius(radius), loc(loc) {}
00804 
00805     double value;
00806     int radius;
00807     gamemap::location loc;
00808 };
00809 
00810 }
00811 
00812 
00813 void ai::find_threats()
00814 {
00815     if(threats_found_) {
00816         return;
00817     }
00818 
00819     threats_found_ = true;
00820 
00821     const config& parms = current_team().ai_parameters();
00822 
00823     std::vector<protected_item> items;
00824 
00825     // We want to protect our leader.
00826     const unit_map::const_iterator leader = find_leader(units_,team_num_);
00827     if(leader != units_.end()) {
00828         items.push_back(protected_item(
00829                     lexical_cast_default<double>(parms["protect_leader"], 1.0),
00830                     lexical_cast_default<int>(parms["protect_leader_radius"], 20),
00831                     leader->first));
00832     }
00833 
00834     // Look for directions to protect a specific location.
00835     const config::child_list& locations = parms.get_children("protect_location");
00836     for(config::child_list::const_iterator i = locations.begin(); i != locations.end(); ++i) {
00837         items.push_back(protected_item(
00838                     lexical_cast_default<double>((**i)["value"], 1.0),
00839                     lexical_cast_default<int>((**i)["radius"], 20),
00840                     gamemap::location(**i, &get_info().game_state_)));
00841     }
00842 
00843     // Look for directions to protect a unit.
00844     const config::child_list& protected_units = parms.get_children("protect_unit");
00845     for(config::child_list::const_iterator j = protected_units.begin(); j != protected_units.end(); ++j) {
00846 
00847         for(unit_map::const_iterator u = units_.begin(); u != units_.end(); ++u) {
00848             if(game_events::unit_matches_filter(u, *j)) {
00849                 items.push_back(protected_item(
00850                             lexical_cast_default<double>((**j)["value"], 1.0),
00851                             lexical_cast_default<int>((**j)["radius"], 20),
00852                             u->first));
00853             }
00854         }
00855     }
00856 
00857     // Iterate over all protected locations, and if enemy units
00858     // are within the protection radius, set them as hostile targets.
00859     for(std::vector<protected_item>::const_iterator k = items.begin(); k != items.end(); ++k) {
00860         const protected_item& item = *k;
00861 
00862         for(unit_map::const_iterator u = units_.begin(); u != units_.end(); ++u) {
00863             const int distance = distance_between(u->first,item.loc);
00864             if(current_team().is_enemy(u->second.side()) && distance < item.radius) {
00865                 add_target(target(u->first, item.value * double(item.radius-distance) /
00866                             double(item.radius),target::THREAT));
00867             }
00868         }
00869     }
00870 }
00871 
00872 void ai::play_turn()
00873 {
00874     // Protect against a memory over commitment:
00875     //! @todo Not in the mood to figure out the exact cause:
00876     // For some reason -1 hitpoints cause a segmentation fault.
00877     // If -1 hitpoints are sent, we crash :/
00878     try {
00879         consider_combat_ = true;
00880         game_events::fire("ai turn");
00881         do_move();
00882     } catch(std::bad_alloc) {
00883         lg::wml_error << "Memory exhausted - a unit has either a lot of hitpoints or a negative amount.\n";
00884     }
00885 }
00886 
00887 void ai::do_move()
00888 {
00889     log_scope2(ai, "doing ai move");
00890 
00891     invalidate_defensive_position_cache();
00892 
00893     raise_user_interact();
00894 
00895     typedef paths::route route;
00896 
00897     typedef std::map<location,paths> moves_map;
00898     moves_map possible_moves, enemy_possible_moves;
00899 
00900     move_map srcdst, dstsrc, enemy_srcdst, enemy_dstsrc;
00901 
00902     calculate_possible_moves(possible_moves,srcdst,dstsrc,false,false,&avoided_locations());
00903     calculate_possible_moves(enemy_possible_moves,enemy_srcdst,enemy_dstsrc,true);
00904 
00905     const bool passive_leader = utils::string_bool(current_team().ai_parameters()["passive_leader"]);
00906 
00907     if (passive_leader) {
00908         unit_map::iterator leader = find_leader(units_,team_num_);
00909         if(leader != units_.end()) {
00910             remove_unit_from_moves(leader->first,srcdst,dstsrc);
00911         }
00912     }
00913 
00914     // Execute goto-movements - first collect gotos in a list
00915     std::vector<gamemap::location> gotos;
00916 
00917     for(unit_map::iterator ui = units_.begin(); ui != units_.end(); ++ui) {
00918         if(ui->second.get_goto() == ui->first) {
00919             ui->second.set_goto(gamemap::location());
00920         } else if(ui->second.side() == team_num_ && map_.on_board(ui->second.get_goto())) {
00921             gotos.push_back(ui->first);
00922         }
00923     }
00924 
00925     for(std::vector<gamemap::location>::const_iterator g = gotos.begin(); g != gotos.end(); ++g) {
00926         unit_map::const_iterator ui = units_.find(*g);
00927         int closest_distance = -1;
00928         std::pair<location,location> closest_move;
00929         for(move_map::const_iterator i = dstsrc.begin(); i != dstsrc.end(); ++i) {
00930             if(i->second != ui->first) {
00931                 continue;
00932             }
00933             const int distance = distance_between(i->first,ui->second.get_goto());
00934             if(closest_distance == -1 || distance < closest_distance) {
00935                 closest_distance = distance;
00936                 closest_move = *i;
00937             }
00938         }
00939 
00940         if(closest_distance != -1) {
00941             move_unit(ui->first,closest_move.first,possible_moves);
00942         }
00943     }
00944 
00945 
00946     std::vector<attack_analysis> analysis;
00947 
00948     LOG_AI << "combat phase\n";
00949 
00950     if(consider_combat_) {
00951         LOG_AI << "combat...\n";
00952         consider_combat_ = do_combat(possible_moves,srcdst,dstsrc,enemy_srcdst,enemy_dstsrc);
00953         if(consider_combat_) {
00954             do_move();
00955             return;
00956         }
00957     }
00958 
00959     move_leader_to_goals(enemy_dstsrc);
00960 
00961     LOG_AI << "get villages phase\n";
00962 
00963     // Iterator could be invalidated by combat analysis or move_leader_to_goals.
00964     unit_map::iterator leader = find_leader(units_,team_num_);
00965 
00966     LOG_AI << "villages...\n";
00967     if(get_villages(possible_moves, dstsrc, enemy_dstsrc, leader)) {
00968         do_move();
00969         return;
00970     }
00971 
00972     LOG_AI << "healing...\n";
00973     const bool healed_unit = get_healing(possible_moves,srcdst,enemy_dstsrc);
00974     if(healed_unit) {
00975         do_move();
00976         return;
00977     }
00978 
00979     LOG_AI << "retreat phase\n";
00980 
00981     LOG_AI << "retreating...\n";
00982 
00983     leader = find_leader(units_,team_num_);
00984     const bool retreated_unit = retreat_units(possible_moves,srcdst,dstsrc,enemy_dstsrc,leader);
00985     if(retreated_unit) {
00986         do_move();
00987         return;
00988     }
00989 
00990     if(leader != units_.end()) {
00991         remove_unit_from_moves(leader->first,srcdst,dstsrc);
00992     }
00993 
00994     find_threats();
00995 
00996     LOG_AI << "move/targetting phase\n";
00997 
00998     const bool met_invisible_unit = move_to_targets(possible_moves,srcdst,dstsrc,enemy_dstsrc,leader);
00999     if(met_invisible_unit) {
01000         LOG_AI << "met_invisible_unit\n";
01001         do_move();
01002         return;
01003     }
01004 
01005     LOG_AI << "done move to targets\n";
01006 
01007     LOG_AI << "leader/recruitment phase\n";
01008 
01009     // Recruitment phase and leader movement phase.
01010     if(leader != units_.end()) {
01011 
01012         if(!passive_leader) {
01013             move_leader_to_keep(enemy_dstsrc);
01014         }
01015 
01016         do_recruitment();
01017 
01018         if(!passive_leader) {
01019             move_leader_after_recruit(srcdst,dstsrc,enemy_dstsrc);
01020         }
01021     }
01022 }
01023 
01024 bool ai::do_combat(std::map<gamemap::location,paths>& possible_moves, const move_map& srcdst,
01025         const move_map& dstsrc, const move_map& enemy_srcdst, const move_map& enemy_dstsrc)
01026 {
01027     int ticks = SDL_GetTicks();
01028 
01029     std::vector<attack_analysis> analysis = analyze_targets(srcdst, dstsrc,
01030             enemy_srcdst, enemy_dstsrc);
01031 
01032     int time_taken = SDL_GetTicks() - ticks;
01033     LOG_AI << "took " << time_taken << " ticks for " << analysis.size()
01034         << " positions. Analyzing...\n";
01035 
01036     ticks = SDL_GetTicks();
01037 
01038     const int max_sims = 50000;
01039     int num_sims = analysis.empty() ? 0 : max_sims/analysis.size();
01040     if(num_sims < 20)
01041         num_sims = 20;
01042     if(num_sims > 40)
01043         num_sims = 40;
01044 
01045     LOG_AI << "simulations: " << num_sims << "\n";
01046 
01047     const int max_positions = 30000;
01048     const int skip_num = analysis.size()/max_positions;
01049 
01050     std::vector<attack_analysis>::iterator choice_it = analysis.end();
01051     double choice_rating = -1000.0;
01052     for(std::vector<attack_analysis>::iterator it = analysis.begin();
01053             it != analysis.end(); ++it) {
01054 
01055         if(skip_num > 0 && ((it - analysis.begin())%skip_num) && it->movements.size() > 1)
01056             continue;
01057 
01058         const double rating = it->rating(current_team().aggression(),*this);
01059         LOG_AI << "attack option rated at " << rating << " ("
01060             << current_team().aggression() << ")\n";
01061 
01062         if(rating > choice_rating) {
01063             choice_it = it;
01064             choice_rating = rating;
01065         }
01066     }
01067 
01068     time_taken = SDL_GetTicks() - ticks;
01069     LOG_AI << "analysis took " << time_taken << " ticks\n";
01070 
01071     if(choice_rating > 0.0) {
01072         location from   = choice_it->movements[0].first;
01073         location to     = choice_it->movements[0].second;
01074         location target_loc = choice_it->target;
01075 
01076         // Never used:
01077         //      const unit_map::const_iterator tgt = units_.find(target_loc);
01078 
01079         const location arrived_at = move_unit(from,to,possible_moves);
01080         if(arrived_at != to || units_.find(to) == units_.end()) {
01081             LOG_STREAM(warn, ai) << "unit moving to attack has ended up unexpectedly at "
01082                                  << arrived_at << " when moving to " << to << " moved from "
01083                                  << from << '\n';
01084             return true;
01085         }
01086 
01087         // Recalc appropriate weapons here: AI uses approximations.
01088         battle_context bc(map_, teams_, units_, state_,
01089                           to, target_loc, -1, -1,
01090                           current_team().aggression());
01091         attack_enemy(to, target_loc, bc.get_attacker_stats().attack_num,
01092                 bc.get_defender_stats().attack_num);
01093 
01094         // If this is the only unit in the attack, and the target
01095         // is still alive, then also summon reinforcements
01096         if(choice_it->movements.size() == 1 && units_.count(target_loc)) {
01097             add_target(target(target_loc,3.0,target::BATTLE_AID));
01098         }
01099 
01100         return true;
01101 
01102     } else {
01103         return false;
01104     }
01105 }
01106 
01107 //! This function should be called to attack an enemy.
01108 //!
01109 //! @param u            The location of the attacking unit. (Note this shouldn't
01110 //!                     be a reference since attack::attack() can invalidate the
01111 //!                     unit_map and references to the map are also invalid then.)
01112 //! @param target       The location of the target unit. This unit must be in
01113 //!                     range of the attacking unit's weapon. (See note at param u.)
01114 //! @param weapon       The number of the weapon (0-based) which should be used
01115 //!                     by the attacker. (It must be a valid weapon of the attacker.)
01116 //! @param def_weapon   The number of the weapon (0-based) which should be used
01117 //!                     by the defender. (It must be a valid weapon of the defender.)
01118 void ai_interface::attack_enemy(const location u,
01119         const location target, int weapon, int def_weapon)
01120 {
01121     // Stop the user from issuing any commands while the unit is attacking
01122     const events::command_disabler disable_commands;
01123 
01124     if(info_.units.count(u) && info_.units.count(target)) {
01125         if(info_.units.find(target)->second.incapacitated()) {
01126             LOG_STREAM(err, ai) << "attempt to attack unit that is turned to stone\n";
01127             return;
01128         }
01129         if(!info_.units.find(u)->second.attacks_left()) {
01130             LOG_STREAM(err, ai) << "attempt to attack twice with the same unit\n";
01131             return;
01132         }
01133 
01134         if(weapon >= 0) {
01135             recorder.add_attack(u,target,weapon,def_weapon);
01136         }
01137         try {
01138             attack(info_.disp, info_.map, info_.teams, u, target, weapon, def_weapon,
01139                     info_.units, info_.state);
01140         }
01141         catch (end_level_exception&)
01142         {
01143             dialogs::advance_unit(info_.map,info_.units,u,info_.disp,true);
01144 
01145             const unit_map::const_iterator defender = info_.units.find(target);
01146             if(defender != info_.units.end()) {
01147                 const size_t defender_team = size_t(defender->second.side()) - 1;
01148                 if(defender_team < info_.teams.size()) {
01149                     dialogs::advance_unit(info_.map, info_.units,
01150                             target, info_.disp, !info_.teams[defender_team].is_human());
01151                 }
01152             }
01153 
01154             throw;
01155         }
01156         dialogs::advance_unit(info_.map,info_.units,u,info_.disp,true);
01157 
01158         const unit_map::const_iterator defender = info_.units.find(target);
01159         if(defender != info_.units.end()) {
01160             const size_t defender_team = size_t(defender->second.side()) - 1;
01161             if(defender_team < info_.teams.size()) {
01162                 dialogs::advance_unit(info_.map, info_.units,
01163                         target, info_.disp, !info_.teams[defender_team].is_human());
01164             }
01165         }
01166 
01167         check_victory(info_.units,info_.teams, info_.disp);
01168         raise_enemy_attacked();
01169     }
01170 }
01171 
01172 bool ai::get_healing(std::map<gamemap::location,paths>& possible_moves,
01173         const move_map& srcdst, const move_map& enemy_dstsrc)
01174 {
01175     // Find units in need of healing.
01176     unit_map::iterator u_it = units_.begin();
01177     for(; u_it != units_.end(); ++u_it) {
01178         unit& u = u_it->second;
01179 
01180         // If the unit is on our side, has lost as many or more than
01181         // 1/2 round worth of healing, and doesn't regenerate itself,
01182         // then try to find a vacant village for it to rest in.
01183         if(u.side() == team_num_ &&
01184            u.max_hitpoints() - u.hitpoints() >= game_config::poison_amount/2 &&
01185            !u.get_ability_bool("regenerate",u_it->first)) {
01186 
01187             // Look for the village which is the least vulnerable to enemy attack.
01188             typedef std::multimap<location,location>::const_iterator Itor;
01189             std::pair<Itor,Itor> it = srcdst.equal_range(u_it->first);
01190             double best_vulnerability = 100000.0;
01191             Itor best_loc = it.second;
01192             while(it.first != it.second) {
01193                 const location& dst = it.first->second;
01194                 if(map_.gives_healing(dst) && (units_.find(dst) == units_.end() || dst == u_it->first)) {
01195                     const double vuln = power_projection(it.first->first, enemy_dstsrc);
01196                     LOG_AI << "found village with vulnerability: " << vuln << "\n";
01197                     if(vuln < best_vulnerability || best_loc == it.second) {
01198                         best_vulnerability = vuln;
01199                         best_loc = it.first;
01200                         LOG_AI << "chose village " << dst << '\n';
01201                     }
01202                 }
01203 
01204                 ++it.first;
01205             }
01206 
01207             // If we have found an eligible village:
01208             if(best_loc != it.second) {
01209                 const location& src = best_loc->first;
01210                 const location& dst = best_loc->second;
01211 
01212                 LOG_AI << "moving unit to village for healing...\n";
01213 
01214                 move_unit(src,dst,possible_moves);
01215                 return true;
01216             }
01217         }
01218     }
01219 
01220     return false;
01221 }
01222 
01223 bool ai::should_retreat(const gamemap::location& loc, const unit_map::const_iterator un,
01224         const move_map& srcdst, const move_map& dstsrc, const move_map& enemy_dstsrc,
01225         double caution)
01226 {
01227     if(caution <= 0.0) {
01228         return false;
01229     }
01230 
01231     const double optimal_terrain = best_defensive_position(un->first, dstsrc,
01232             srcdst, enemy_dstsrc).chance_to_hit/100.0;
01233     const double proposed_terrain =
01234         un->second.defense_modifier(map_.get_terrain(loc))/100.0;
01235 
01236     // The 'exposure' is the additional % chance to hit
01237     // this unit receives from being on a sub-optimal defensive terrain.
01238     const double exposure = proposed_terrain - optimal_terrain;
01239 
01240     const double our_power = power_projection(loc,dstsrc);
01241     const double their_power = power_projection(loc,enemy_dstsrc);
01242     return caution*their_power*(1.0+exposure) > our_power;
01243 }
01244 
01245 bool ai::retreat_units(std::map<gamemap::location,paths>& possible_moves,
01246         const move_map& srcdst, const move_map& dstsrc,
01247         const move_map& enemy_dstsrc, unit_map::const_iterator leader)
01248 {
01249     // Get versions of the move map that assume that all units are at full movement
01250     std::map<gamemap::location,paths> dummy_possible_moves;
01251     move_map fullmove_srcdst;
01252     move_map fullmove_dstsrc;
01253     calculate_possible_moves(dummy_possible_moves, fullmove_srcdst, fullmove_dstsrc,
01254             false, true, &avoided_locations());
01255 
01256     gamemap::location leader_adj[6];
01257     if(leader != units_.end()) {
01258         get_adjacent_tiles(leader->first,leader_adj);
01259     }
01260 
01261     for(unit_map::iterator i = units_.begin(); i != units_.end(); ++i) {
01262         if(i->second.side() == team_num_ &&
01263                 i->second.movement_left() == i->second.total_movement() &&
01264                 unit_map::const_iterator(i) != leader &&
01265                 !i->second.incapacitated()) {
01266 
01267             // This unit still has movement left, and is a candidate to retreat.
01268             // We see the amount of power of each side on the situation,
01269             // and decide whether it should retreat.
01270             if(should_retreat(i->first, i, fullmove_srcdst, fullmove_dstsrc,
01271                         enemy_dstsrc, current_team().caution())) {
01272 
01273                 bool can_reach_leader = false;
01274 
01275                 // Time to retreat. Look for the place where the power balance
01276                 // is most in our favor.
01277                 // If we can't find anywhere where we like the power balance,
01278                 // just try to get to the best defensive hex.
01279                 typedef move_map::const_iterator Itor;
01280                 std::pair<Itor,Itor> itors = srcdst.equal_range(i->first);
01281                 gamemap::location best_pos, best_defensive(i->first);
01282                 double best_rating = 0.0;
01283                 int best_defensive_rating = i->second.defense_modifier(map_.get_terrain(i->first))
01284                     - (map_.is_village(i->first) ? 10 : 0);
01285                 while(itors.first != itors.second) {
01286 
01287                     if(leader != units_.end() && std::count(leader_adj,
01288                                 leader_adj + 6, itors.first->second)) {
01289 
01290                         can_reach_leader = true;
01291                         break;
01292                     }
01293 
01294                     // We rate the power balance of a hex based on our power projection
01295                     // compared to theirs, multiplying their power projection by their
01296                     // chance to hit us on the hex we're planning to flee to.
01297                     const gamemap::location& hex = itors.first->second;
01298                     const int defense = i->second.defense_modifier(map_.get_terrain(hex));
01299                     const double our_power = power_projection(hex,dstsrc);
01300                     const double their_power = power_projection(hex,enemy_dstsrc) * double(defense)/100.0;
01301                     const double rating = our_power - their_power;
01302                     if(rating > best_rating) {
01303                         best_pos = hex;
01304                         best_rating = rating;
01305                     }
01306 
01307                     // Give a bonus for getting to a village.
01308                     const int modified_defense = defense - (map_.is_village(hex) ? 10 : 0);
01309 
01310                     if(modified_defense < best_defensive_rating) {
01311                         best_defensive_rating = modified_defense;
01312                         best_defensive = hex;
01313                     }
01314 
01315                     ++itors.first;
01316                 }
01317 
01318                 // If the unit is in range of its leader, it should
01319                 // never retreat -- it has to defend the leader instead.
01320                 if(can_reach_leader) {
01321                     continue;
01322                 }
01323 
01324                 if(!best_pos.valid()) {
01325                     best_pos = best_defensive;
01326                 }
01327 
01328                 // If we can't move, we should be more aggressive in lashing out.
01329                 if (best_pos == i->first) {
01330                     if (i->second.attacks_left()) {
01331                         return desperate_attack(i->first);
01332                     }
01333                     return false;
01334                 }
01335 
01336                 if(best_pos.valid()) {
01337                     LOG_AI << "retreating '" << i->second.type_id() << "' " << i->first
01338                            << " -> " << best_pos << '\n';
01339                     move_unit(i->first,best_pos,possible_moves);
01340                     return true;
01341                 }
01342             }
01343         }
01344     }
01345 
01346     return false;
01347 }
01348 
01349 bool ai::move_to_targets(std::map<gamemap::location, paths>& possible_moves,
01350         move_map& srcdst, move_map& dstsrc, const move_map& enemy_dstsrc,
01351         unit_map::const_iterator leader)
01352 {
01353     LOG_AI << "finding targets...\n";
01354     std::vector<target> targets;
01355     for(;;) {
01356         if(targets.empty()) {
01357             targets = find_targets(leader,enemy_dstsrc);
01358             targets.insert(targets.end(),additional_targets_.begin(),
01359                                          additional_targets_.end());
01360             if(targets.empty()) {
01361                 return false;
01362             }
01363         }
01364 
01365         LOG_AI << "choosing move...\n";
01366         std::pair<location,location> move = choose_move(targets, srcdst,
01367                 dstsrc, enemy_dstsrc);
01368 
01369         for(std::vector<target>::const_iterator ittg = targets.begin();
01370                 ittg != targets.end(); ++ittg) {
01371             assert(map_.on_board(ittg->loc));
01372         }
01373 
01374         if(move.first.valid() == false) {
01375             break;
01376         }
01377 
01378         if(move.second.valid() == false) {
01379             return true;
01380         }
01381 
01382         LOG_AI << "move: " << move.first << " -> " << move.second << '\n';
01383 
01384         const location arrived_at = move_unit(move.first,move.second,possible_moves);
01385 
01386         // We didn't arrive at our intended destination.
01387         // We return true, meaning that the AI algorithm
01388         // should be recalculated from the start.
01389         if(arrived_at != move.second) {
01390             LOG_STREAM(warn, ai) << "didn't arrive at destination\n";
01391             return true;
01392         }
01393 
01394         const unit_map::const_iterator u_it = units_.find(arrived_at);
01395         // Event could have done anything: check
01396         if (u_it == units_.end() || u_it->second.incapacitated()) {
01397             LOG_STREAM(warn, ai) << "stolen or incapacitated\n";
01398         } else {
01399             // Search to see if there are any enemy units next to the tile
01400             // which really should be attacked now the move is done.
01401             gamemap::location adj[6];
01402             get_adjacent_tiles(arrived_at,adj);
01403             gamemap::location target;
01404 
01405             for(int n = 0; n != 6; ++n) {
01406                 const unit_map::iterator enemy = find_visible_unit(units_,adj[n],
01407                                                                    map_,
01408                                                                    teams_,current_team());
01409 
01410                 if(enemy != units_.end() &&
01411                    current_team().is_enemy(enemy->second.side()) && !enemy->second.incapacitated()) {
01412                     // Current behavior is to only make risk-free attacks.
01413                     battle_context bc(map_, teams_, units_, state_, arrived_at, adj[n], -1, -1, 100.0);
01414                     if (bc.get_defender_stats().damage == 0) {
01415                         attack_enemy(arrived_at, adj[n], bc.get_attacker_stats().attack_num,
01416                                 bc.get_defender_stats().attack_num);
01417                         break;
01418                     }
01419                 }
01420             }
01421         }
01422 
01423         // Don't allow any other units to move onto the tile
01424         // our unit just moved onto
01425         typedef move_map::iterator Itor;
01426         std::pair<Itor,Itor> del = dstsrc.equal_range(arrived_at);
01427         dstsrc.erase(del.first,del.second);
01428     }
01429 
01430     return false;
01431 }
01432 
01433 int ai::average_resistance_against(const unit_type& a, const unit_type& b) const
01434 {
01435     int weighting_sum = 0, defense = 0;
01436     const std::map<t_translation::t_terrain, size_t>& terrain =
01437         map_.get_weighted_terrain_frequencies();
01438 
01439     for (std::map<t_translation::t_terrain, size_t>::const_iterator j = terrain.begin(),
01440          j_end = terrain.end(); j != j_end; ++j)
01441     {
01442         // Use only reachable tiles when computing the average defense.
01443         if (a.movement_type().movement_cost(map_, j->first) < 99) {
01444             defense += a.movement_type().defense_modifier(map_, j->first) * j->second;
01445             weighting_sum += j->second;
01446         }
01447     }
01448 
01449     if (weighting_sum == 0) {
01450         // This unit can't move on this map, so just get the average weighted
01451         // of all available terrains. This still is a kind of silly
01452         // since the opponent probably can't recruit this unit and it's a static unit.
01453         for (std::map<t_translation::t_terrain, size_t>::const_iterator jj = terrain.begin(),
01454                 jj_end = terrain.end(); jj != jj_end; ++jj)
01455         {
01456             defense += a.movement_type().defense_modifier(map_, jj->first) * jj->second;
01457             weighting_sum += jj->second;
01458         }
01459     }
01460 
01461     if(weighting_sum != 0) {
01462         defense /= weighting_sum;
01463     } else {
01464         ERR_AI << "The weighting sum is 0 and is ignored.\n";
01465     }
01466 
01467     LOG_AI << "average defense of '" << a.id() << "': " << defense << "\n";
01468 
01469     int sum = 0, weight_sum = 0;
01470 
01471     // calculation of the average damage taken
01472     bool steadfast = a.has_ability_by_id("steadfast");
01473     bool living = !a.not_living();
01474     const std::vector<attack_type>& attacks = b.attacks();
01475     for (std::vector<attack_type>::const_iterator i = attacks.begin(),
01476          i_end = attacks.end(); i != i_end; ++i)
01477     {
01478         int resistance = a.movement_type().resistance_against(*i);
01479         // Apply steadfast resistance modifier.
01480         if (steadfast && resistance < 100)
01481             resistance = maximum<int>(resistance * 2 - 100, 50);
01482         // Do not look for filters or values, simply assume 70% if CTH is customized.
01483         int cth = i->get_special_bool("chance_to_hit", true) ? 70 : defense;
01484         int weight = i->damage() * i->num_attacks();
01485         // if cth == 0 the division will do 0/0 so don't execute this part
01486         if (living && cth != 0 && i->get_special_bool("poison", true)) {
01487             // Compute the probability of not poisoning the unit.
01488             int prob = 100;
01489             for (int j = 0; j < i->num_attacks(); ++j)
01490                 prob = prob * (100 - cth);
01491             // Assume poison works one turn.
01492             weight += game_config::poison_amount * (100 - prob) / 100;
01493         }
01494         sum += cth * resistance * weight * weight; // average damage * weight
01495         weight_sum += weight;
01496     }
01497 
01498     // normalize by HP
01499     sum /= maximum<int>(1,minimum<int>(a.hitpoints(),1000)); // avoid values really out of range
01500 
01501     // Catch division by zero here if the attacking unit
01502     // has zero attacks and/or zero damage.
01503     // If it has no attack at all, the ai shouldn't prefer
01504     // that unit anyway.
01505     if (weight_sum == 0) {
01506         return sum;
01507     }
01508     return sum/weight_sum;
01509 }
01510 
01511 int ai::compare_unit_types(const unit_type& a, const unit_type& b) const
01512 {
01513     const int a_effectiveness_vs_b = average_resistance_against(b,a);
01514     const int b_effectiveness_vs_a = average_resistance_against(a,b);
01515 
01516     LOG_AI << "comparison of '" << a.id() << " vs " << b.id() << ": "
01517               << a_effectiveness_vs_b << " - " << b_effectiveness_vs_a << " = "
01518               << (a_effectiveness_vs_b - b_effectiveness_vs_a) << "\n";
01519     return a_effectiveness_vs_b - b_effectiveness_vs_a;
01520 }
01521 
01522 void ai::analyze_potential_recruit_combat()
01523 {
01524     if(unit_combat_scores_.empty() == false ||
01525             utils::string_bool(current_team().ai_parameters()["recruitment_ignore_bad_combat"])) {
01526         return;
01527     }
01528 
01529     log_scope2(ai, "analyze_potential_recruit_combat()");
01530 
01531     // Records the best combat analysis for each usage type.
01532     std::map<std::string,int> best_usage;
01533 
01534     const std::set<std::string>& recruits = current_team().recruits();
01535     std::set<std::string>::const_iterator i;
01536     for(i = recruits.begin(); i != recruits.end(); ++i) {
01537         const unit_type_data::unit_type_map::const_iterator info = unit_type_data::types().find(*i);
01538         if(info == unit_type_data::types().end() || not_recommended_units_.count(*i)) {
01539             continue;
01540         }
01541 
01542         int score = 0, weighting = 0;
01543 
01544         for(unit_map::const_iterator j = units_.begin(); j != units_.end(); ++j) {
01545             if(j->second.can_recruit() || current_team().is_enemy(j->second.side()) == false) {
01546                 continue;
01547             }
01548 
01549             unit const &un = j->second;
01550             const unit_type_data::unit_type_map::const_iterator enemy_info = unit_type_data::types().find(un.type_id());
01551             VALIDATE((enemy_info != unit_type_data::types().end()), _("Unknown unit type : ") + un.type_id());
01552 
01553             int weight = un.cost() * un.hitpoints() / un.max_hitpoints();
01554             weighting += weight;
01555             score += compare_unit_types(info->second, enemy_info->second) * weight;
01556         }
01557 
01558         if(weighting != 0) {
01559             score /= weighting;
01560         }
01561 
01562         LOG_AI << "combat score of '" << *i << "': " << score << "\n";
01563         unit_combat_scores_[*i] = score;
01564 
01565         if(best_usage.count(info->second.usage()) == 0 ||
01566                 score > best_usage[info->second.usage()]) {
01567             best_usage[info->second.usage()] = score;
01568         }
01569     }
01570 
01571     // Recommend not to use units of a certain usage type
01572     // if they have a score more than 600 below
01573     // the best unit of that usage type.
01574     for(i = recruits.begin(); i != recruits.end(); ++i) {
01575         const unit_type_data::unit_type_map::const_iterator info = unit_type_data::types().find(*i);
01576         if(info == unit_type_data::types().end() || not_recommended_units_.count(*i)) {
01577             continue;
01578         }
01579 
01580         if(unit_combat_scores_[*i] + 600 < best_usage[info->second.usage()]) {
01581             LOG_AI << "recommending not to use '" << *i << "' because of poor combat performance "
01582                       << unit_combat_scores_[*i] << "/" << best_usage[info->second.usage()] << "\n";
01583             not_recommended_units_.insert(*i);
01584         }
01585     }
01586 }
01587 
01588 namespace {
01589 
01590 struct target_comparer_distance {
01591     target_comparer_distance(const gamemap::location& loc) : loc_(loc) {}
01592 
01593     bool operator()(const ai::target& a, const ai::target& b) const {
01594         return distance_between(a.loc,loc_) < distance_between(b.loc,loc_);
01595     }
01596 
01597 private:
01598     gamemap::location loc_;
01599 };
01600 
01601 }
01602 
01603 void ai::analyze_potential_recruit_movements()
01604 {
01605     if(unit_movement_scores_.empty() == false ||
01606             utils::string_bool(current_team().ai_parameters()["recruitment_ignore_bad_movement"])) {
01607         return;
01608     }
01609 
01610     const unit_map::const_iterator leader = find_leader(units_,team_num_);
01611     if(leader == units_.end()) {
01612         return;
01613     }
01614 
01615     const location& start = nearest_keep(leader->first);
01616     if(map_.on_board(start) == false) {
01617         return;
01618     }
01619 
01620     log_scope2(ai, "analyze_potential_recruit_movements()");
01621 
01622     const unsigned int max_targets = 5;
01623 
01624     const move_map srcdst, dstsrc;
01625     std::vector<target> targets = find_targets(leader,dstsrc);
01626     if(targets.size() > max_targets) {
01627         std::sort(targets.begin(),targets.end(),target_comparer_distance(start));
01628         targets.erase(targets.begin()+max_targets,targets.end());
01629     }
01630 
01631     const std::set<std::string>& recruits = current_team().recruits();
01632 
01633     LOG_AI << "targets: " << targets.size() << "\n";
01634 
01635     std::map<std::string,int> best_scores;
01636 
01637     for(std::set<std::string>::const_iterator i = recruits.begin(); i != recruits.end(); ++i) {
01638         const unit_type_data::unit_type_map::const_iterator info = unit_type_data::types().find(*i);
01639         if(info == unit_type_data::types().end()) {
01640             continue;
01641         }
01642 
01643         const unit temp_unit(&get_info().units,&get_info().map,
01644                 &get_info().state, &get_info().teams, &info->second, team_num_);
01645         // since we now use the ignore_units switch, no need to use a empty unit_map
01646         // unit_map units;
01647         // const temporary_unit_placer placer(units,start,temp_unit);
01648 
01649         // pathfinding ignoring other units and terrain defense
01650         const shortest_path_calculator calc(temp_unit,current_team(),get_info().units,teams_,map_,true,true);
01651 
01652         int cost = 0;
01653         int targets_reached = 0;
01654         int targets_missed = 0;
01655 
01656         for(std::vector<target>::const_iterator t = targets.begin(); t != targets.end(); ++t) {
01657             LOG_AI << "analyzing '" << *i << "' getting to target...\n";
01658             const paths::route& route = a_star_search(start, t->loc, 100.0, &calc,
01659                     get_info().map.w(), get_info().map.h());
01660 
01661             if(route.steps.empty() == false) {
01662                 LOG_AI << "made it: " << route.move_left << "\n";
01663                 cost += route.move_left;
01664                 ++targets_reached;
01665             } else {
01666                 LOG_AI << "failed\n";
01667                 ++targets_missed;
01668             }
01669         }
01670 
01671         if(targets_reached == 0 || targets_missed >= targets_reached*2) {
01672             unit_movement_scores_[*i] = 100000;
01673             not_recommended_units_.insert(*i);
01674         } else {
01675             const int average_cost = cost/targets_reached;
01676             const int score = (average_cost * (targets_reached+targets_missed))/targets_reached;
01677             unit_movement_scores_[*i] = score;
01678 
01679             const std::map<std::string,int>::const_iterator current_best = best_scores.find(temp_unit.usage());
01680             if(current_best == best_scores.end() || score < current_best->second) {
01681                 best_scores[temp_unit.usage()] = score;
01682             }
01683         }
01684     }
01685 
01686     for(std::map<std::string,int>::iterator j = unit_movement_scores_.begin();
01687             j != unit_movement_scores_.end(); ++j) {
01688 
01689         const unit_type_data::unit_type_map::const_iterator info =
01690             unit_type_data::types().find(j->first);
01691 
01692         if(info == unit_type_data::types().end()) {
01693             continue;
01694         }
01695 
01696         const int best_score = best_scores[info->second.usage()];
01697         if(best_score > 0) {
01698             j->second = (j->second*10)/best_score;
01699             if(j->second > 15) {
01700                 LOG_AI << "recommending against recruiting '" << j->first << "' (score: " << j->second << ")\n";
01701                 not_recommended_units_.insert(j->first);
01702             } else {
01703                 LOG_AI << "recommending recruit of '" << j->first << "' (score: " << j->second << ")\n";
01704             }
01705         }
01706     }
01707 
01708     if(not_recommended_units_.size() == unit_movement_scores_.size()) {
01709         not_recommended_units_.clear();
01710     }
01711 }
01712 
01713 void ai::do_recruitment()
01714 {
01715     const unit_map::const_iterator leader = find_leader(units_,team_num_);
01716     if(leader == units_.end()) {
01717         return;
01718     }
01719 
01720     const location& start_pos = nearest_keep(leader->first);
01721 
01722     analyze_potential_recruit_movements();
01723     analyze_potential_recruit_combat();
01724 
01725     size_t neutral_villages = 0;
01726 
01727     // We recruit the initial allocation of scouts
01728     // based on how many neutral villages there are
01729     // that are closer to us than to other keeps.
01730     const std::vector<location>& villages = map_.villages();
01731     for(std::vector<location>::const_iterator v = villages.begin(); v != villages.end(); ++v) {
01732         const int owner = village_owner(*v,teams_);
01733         if(owner == -1) {
01734             const size_t distance = distance_between(start_pos,*v);
01735 
01736             bool closest = true;
01737             for(std::vector<team>::const_iterator i = teams_.begin(); i != teams_.end(); ++i) {
01738                 const int index = i - teams_.begin() + 1;
01739                 const gamemap::location& loc = map_.starting_position(index);
01740                 if(loc != start_pos && distance_between(loc,*v) < distance) {
01741                     closest = false;
01742                     break;
01743                 }
01744             }
01745 
01746             if(closest) {
01747                 ++neutral_villages;
01748             }
01749         }
01750     }
01751 
01752     // The villages per scout is for a two-side battle,
01753     // accounting for all neutral villages on the map.
01754     // We only look at villages closer to us, so we halve it,
01755     // making us get twice as many scouts.
01756     const int villages_per_scout = current_team().villages_per_scout()/2;
01757 
01758     // Get scouts depending on how many neutral villages there are.
01759     int scouts_wanted = villages_per_scout > 0 ? neutral_villages/villages_per_scout : 0;
01760 
01761     LOG_AI << "scouts_wanted: " << neutral_villages << "/"
01762         << villages_per_scout << " = " << scouts_wanted << "\n";
01763 
01764     std::map<std::string,int> unit_types;
01765 
01766     for(unit_map::const_iterator u = units_.begin(); u != units_.end(); ++u) {
01767         if(u->second.side() == team_num_) {
01768             ++unit_types[u->second.usage()];
01769         }
01770     }
01771 
01772     LOG_AI << "we have " << unit_types["scout"] << " scouts already and we want "
01773         << scouts_wanted << " in total\n";
01774 
01775     while(unit_types["scout"] < scouts_wanted) {
01776         if(recruit_usage("scout") == false)
01777             break;
01778 
01779         ++unit_types["scout"];
01780     }
01781 
01782     std::vector<std::string> options = current_team().recruitment_pattern();
01783 
01784     // If there is no recruitment_pattern use "" which makes us consider
01785     // any unit available.
01786     if (options.empty()) {
01787         options.push_back("");
01788     }
01789     // Buy units as long as we have room and can afford it.
01790     while(recruit_usage(options[rand()%options.size()])) {
01791     }
01792 }
01793 
01794 void ai::move_leader_to_goals( const move_map& enemy_dstsrc)
01795 {
01796     const config* const goal = current_team().ai_parameters().child("leader_goal");
01797 
01798     if(goal == NULL) {
01799         LOG_AI << "No goal found\n";
01800         return;
01801     }
01802 
01803     const gamemap::location dst(*goal, &get_info().game_state_);
01804     if (!dst.valid()) {
01805         ERR_AI << "Invalid goal\n";
01806         return;
01807     }
01808 
01809     const unit_map::iterator leader = find_leader(units_,team_num_);
01810     if(leader == units_.end() || leader->second.incapacitated()) {
01811         WRN_AI << "Leader not found\n";
01812         return;
01813     }
01814 
01815     LOG_AI << "Doing recruitment before goals\n";
01816 
01817     do_recruitment();
01818 
01819     shortest_path_calculator calc(leader->second, current_team(), units_, teams_, map_);
01820     const paths::route route = a_star_search(leader->first, dst, 1000.0, &calc,
01821             get_info().map.w(), get_info().map.h());
01822     if(route.steps.empty()) {
01823         LOG_AI << "route empty";
01824         return;
01825     }
01826 
01827     const paths leader_paths(map_, units_, leader->first,
01828             teams_, false, false, current_team());
01829 
01830     std::map<gamemap::location,paths> possible_moves;
01831     possible_moves.insert(std::pair<gamemap::location,paths>(leader->first,leader_paths));
01832 
01833     gamemap::location loc;
01834     for(std::vector<gamemap::location>::const_iterator itor = route.steps.begin();
01835             itor != route.steps.end(); ++itor) {
01836 
01837         if(leader_paths.routes.count(*itor) == 1 &&
01838                 power_projection(*itor,enemy_dstsrc) < double(leader->second.hitpoints()/2)) {
01839             loc = *itor;
01840         }
01841     }
01842 
01843     if(loc.valid()) {
01844         LOG_AI << "Moving leader to goal\n";
01845         move_unit(leader->first,loc,possible_moves);
01846     }
01847 }
01848 
01849 void ai::move_leader_to_keep(const move_map& enemy_dstsrc)
01850 {
01851     const unit_map::iterator leader = find_leader(units_,team_num_);
01852     if(leader == units_.end() || leader->second.incapacitated()) {
01853         return;
01854     }
01855 
01856     // Find where the leader can move
01857     const paths leader_paths(map_, units_, leader->first,
01858             teams_, false, false, current_team());
01859     const gamemap::location& start_pos = nearest_keep(leader->first);
01860 
01861     std::map<gamemap::location,paths> possible_moves;
01862     possible_moves.insert(std::pair<gamemap::location,paths>(leader->first,leader_paths));
01863 
01864     // If the leader is not on his starting location, move him there.
01865     if(leader->first != start_pos) {
01866         const paths::routes_map::const_iterator itor = leader_paths.routes.find(start_pos);
01867         if(itor != leader_paths.routes.end() && units_.count(start_pos) == 0) {
01868             move_unit(leader->first,start_pos,possible_moves);
01869         } else {
01870             // Make a map of the possible locations the leader can move to,
01871             // ordered by the distance from the keep.
01872             std::multimap<int,gamemap::location> moves_toward_keep;
01873 
01874             // The leader can't move to his keep, try to move to the closest location
01875             // to the keep where there are no enemies in range.
01876             const int current_distance = distance_between(leader->first,start_pos);
01877             for(paths::routes_map::const_iterator i = leader_paths.routes.begin();
01878                     i != leader_paths.routes.end(); ++i) {
01879 
01880                 const int new_distance = distance_between(i->first,start_pos);
01881                 if(new_distance < current_distance) {
01882                     moves_toward_keep.insert(std::pair<int,gamemap::location>(new_distance,i->first));
01883                 }
01884             }
01885 
01886             // Find the first location which we can move to,
01887             // without the threat of enemies.
01888             for(std::multimap<int,gamemap::location>::const_iterator j = moves_toward_keep.begin();
01889                     j != moves_toward_keep.end(); ++j) {
01890 
01891                 if(enemy_dstsrc.count(j->second) == 0) {
01892                     move_unit(leader->first,j->second,possible_moves);
01893                     break;
01894                 }
01895             }
01896         }
01897     }
01898 }
01899 
01900 void ai::move_leader_after_recruit(const move_map& /*srcdst*/,
01901         const move_map& /*dstsrc*/, const move_map& enemy_dstsrc)
01902 {
01903     LOG_AI << "moving leader after recruit...\n";
01904 
01905     unit_map::iterator leader = find_leader(units_,team_num_);
01906     if(leader == units_.end() || leader->second.incapacitated()) {
01907         return;
01908     }
01909 
01910     const paths leader_paths(map_, units_, leader->first,
01911             teams_, false, false, current_team());
01912 
01913     std::map<gamemap::location,paths> possible_moves;
01914     possible_moves.insert(std::pair<gamemap::location,paths>(leader->first,leader_paths));
01915 
01916     if(current_team().gold() < 20 && is_accessible(leader->first,enemy_dstsrc) == false) {
01917         // See if we want to ward any enemy units off from getting our villages.
01918         for(move_map::const_iterator i = enemy_dstsrc.begin(); i != enemy_dstsrc.end(); ++i) {
01919 
01920             // If this is a village of ours, that an enemy can capture
01921             // on their turn, and which we might be able to reach in two turns.
01922             if(map_.is_village(i->first) && current_team().owns_village(i->first) &&
01923                 int(distance_between(i->first,leader->first)) <= leader->second.total_movement()*2) {
01924 
01925                 int current_distance = distance_between(i->first,leader->first);
01926                 location current_loc;
01927 
01928                 for(paths::routes_map::const_iterator j = leader_paths.routes.begin();
01929                         j != leader_paths.routes.end(); ++j) {
01930 
01931                     const int distance = distance_between(i->first,j->first);
01932                     if(distance < current_distance && is_accessible(j->first,enemy_dstsrc) == false) {
01933                         current_distance = distance;
01934                         current_loc = j->first;
01935                     }
01936                 }
01937 
01938                 // If this location is in range of the village,
01939                 // then we consider moving to it
01940                 if(current_loc.valid()) {
01941                     LOG_AI << "considering movement to " << str_cast(current_loc.x + 1)
01942                         << "," << str_cast(current_loc.y+1);
01943                     unit_map temp_units(current_loc,leader->second);
01944                     const paths p(map_,temp_units,current_loc,teams_,false,false,current_team());
01945 
01946                     if(p.routes.count(i->first)) {
01947                         move_unit(leader->first,current_loc,possible_moves);
01948                         return;
01949                     }
01950                 }
01951             }
01952         }
01953     }
01954 
01955     // See if any friendly leaders can make it to our keep.
01956     // If they can, then move off it, so that they can recruit if they want.
01957     if(nearest_keep(leader->first) == leader->first) {
01958         const location keep = leader->first;
01959         std::pair<gamemap::location,unit> *temp_leader;
01960 
01961         temp_leader = units_.extract(keep);
01962 
01963         bool friend_can_reach_keep = false;
01964 
01965         std::map<location,paths> friends_possible_moves;
01966         move_map friends_srcdst, friends_dstsrc;
01967         calculate_possible_moves(friends_possible_moves,friends_srcdst,friends_dstsrc,false,true);
01968         for(move_map::const_iterator i = friends_dstsrc.begin(); i != friends_dstsrc.end(); ++i) {
01969             if(i->first == keep) {
01970                 const unit_map::const_iterator itor = units_.find(i->second);
01971                 if(itor != units_.end() && itor->second.can_recruit()) {
01972                     friend_can_reach_keep = true;
01973                     break;
01974                 }
01975             }
01976         }
01977 
01978         units_.add(temp_leader);
01979 
01980         if(friend_can_reach_keep) {
01981             // Find a location for our leader to vacate the keep to
01982             location adj[6];
01983             get_adjacent_tiles(keep,adj);
01984             for(size_t n = 0; n != 6; ++n) {
01985                 // Vacate to the first location found that is on the board,
01986                 // our leader can move to, and no enemies can reach.
01987                 if(map_.on_board(adj[n]) &&
01988                         leader_paths.routes.count(adj[n]) != 0 &&
01989                         is_accessible(adj[n],enemy_dstsrc) == false) {
01990 
01991                     move_unit(keep,adj[n],possible_moves);
01992                     return;
01993                 }
01994             }
01995         }
01996     }
01997 
01998     // We didn't move: are we in trouble?
01999     leader = find_leader(units_,team_num_);
02000     if (!leader->second.has_moved() && leader->second.attacks_left()) {
02001         std::map<gamemap::location,paths> dummy_possible_moves;
02002         move_map fullmove_srcdst;
02003         move_map fullmove_dstsrc;
02004         calculate_possible_moves(dummy_possible_moves,fullmove_srcdst,fullmove_dstsrc,false,true,&avoided_locations());
02005 
02006         if (should_retreat(leader->first, leader, fullmove_srcdst, fullmove_dstsrc, enemy_dstsrc, 0.5)) {
02007             desperate_attack(leader->first);
02008         }
02009     }
02010 }
02011 
02012 bool ai::leader_can_reach_keep()
02013 {
02014     const unit_map::iterator leader = find_leader(units_,team_num_);
02015     if(leader == units_.end() || leader->second.incapacitated()) {
02016         return false;
02017     }
02018 
02019     const gamemap::location& start_pos = nearest_keep(leader->first);
02020     if(start_pos.valid() == false) {
02021         return false;
02022     }
02023 
02024     if(leader->first == start_pos) {
02025         return true;
02026     }
02027 
02028     // Find where the leader can move
02029     const paths leader_paths(map_,units_,leader->first,teams_,false,false,current_team());
02030 
02031 
02032     return leader_paths.routes.count(start_pos) > 0;
02033 }
02034 
02035 int ai::rate_terrain(const unit& u, const gamemap::location& loc)
02036 {
02037     const t_translation::t_terrain terrain = map_.get_terrain(loc);
02038     const int defense = u.defense_modifier(terrain);
02039     int rating = 100 - defense;
02040 
02041     const int healing_value = 10;
02042     const int friendly_village_value = 5;
02043     const int neutral_village_value = 10;
02044     const int enemy_village_value = 15;
02045 
02046     if(map_.gives_healing(terrain) && u.get_ability_bool("regenerates",loc) == false) {
02047         rating += healing_value;
02048     }
02049 
02050     if(map_.is_village(terrain)) {
02051         const int owner = village_owner(loc,teams_);
02052 
02053         if(owner + 1 == static_cast<int>(team_num_)) {
02054             rating += friendly_village_value;
02055         } else if(owner == -1) {
02056             rating += neutral_village_value;
02057         } else {
02058             rating += enemy_village_value;
02059         }
02060     }
02061 
02062     return rating;
02063 }
02064 
02065 const ai::defensive_position& ai::best_defensive_position(const gamemap::location& loc,
02066         const move_map& dstsrc, const move_map& srcdst, const move_map& enemy_dstsrc)
02067 {
02068     const unit_map::const_iterator itor = units_.find(loc);
02069     if(itor == units_.end()) {
02070         static defensive_position pos;
02071         pos.chance_to_hit = 0;
02072         pos.vulnerability = pos.support = 0;
02073         return pos;
02074     }
02075 
02076     const std::map<location,defensive_position>::const_iterator position =
02077         defensive_position_cache_.find(loc);
02078 
02079     if(position != defensive_position_cache_.end()) {
02080         return position->second;
02081     }
02082 
02083     defensive_position pos;
02084     pos.chance_to_hit = 100;
02085     pos.vulnerability = 10000.0;
02086     pos.support = 0.0;
02087 
02088     typedef move_map::const_iterator Itor;
02089     const std::pair<Itor,Itor> itors = srcdst.equal_range(loc);
02090     for(Itor i = itors.first; i != itors.second; ++i) {
02091         const int defense = itor->second.defense_modifier(map_.get_terrain(i->second));
02092         if(defense > pos.chance_to_hit) {
02093             continue;
02094         }
02095 
02096         const double vulnerability = power_projection(i->second,enemy_dstsrc);
02097         const double support = power_projection(i->second,dstsrc);
02098 
02099         if(defense < pos.chance_to_hit || support - vulnerability > pos.support - pos.vulnerability) {
02100             pos.loc = i->second;
02101             pos.chance_to_hit = defense;
02102             pos.vulnerability = vulnerability;
02103             pos.support = support;
02104         }
02105     }
02106 
02107     defensive_position_cache_.insert(std::pair<location,defensive_position>(loc,pos));
02108     return defensive_position_cache_[loc];
02109 }
02110 
02111 bool ai::is_accessible(const location& loc, const move_map& dstsrc) const
02112 {
02113     gamemap::location adj[6];
02114     get_adjacent_tiles(loc,adj);
02115     for(size_t n = 0; n != 6; ++n) {
02116         if(dstsrc.count(adj[n]) > 0) {
02117             return true;
02118         }
02119     }
02120 
02121     return dstsrc.count(loc) > 0;
02122 }
02123 
02124 
02125 const std::set<gamemap::location>& ai::keeps()
02126 {
02127     if(keeps_.empty()) {
02128         // Generate the list of keeps:
02129         // iterate over the entire map and find all keeps.
02130         for(size_t x = 0; x != size_t(map_.w()); ++x) {
02131             for(size_t y = 0; y != size_t(map_.h()); ++y) {
02132                 const gamemap::location loc(x,y);
02133                 if(map_.is_keep(loc)) {
02134                     gamemap::location adj[6];
02135                     get_adjacent_tiles(loc,adj);
02136                     for(size_t n = 0; n != 6; ++n) {
02137                         if(map_.is_castle(adj[n])) {
02138                             keeps_.insert(loc);
02139                             break;
02140                         }
02141                     }
02142                 }
02143             }
02144         }
02145     }
02146 
02147     return keeps_;
02148 }
02149 
02150 const gamemap::location& ai::nearest_keep(const gamemap::location& loc)
02151 {
02152     const std::set<gamemap::location>& keeps = this->keeps();
02153     if(keeps.empty()) {
02154         static const gamemap::location dummy;
02155         return dummy;
02156     }
02157 
02158     const gamemap::location* res = NULL;
02159     int closest = -1;
02160     for(std::set<gamemap::location>::const_iterator i = keeps.begin(); i != keeps.end(); ++i) {
02161         const int distance = distance_between(*i,loc);
02162         if(res == NULL || distance < closest) {
02163             closest = distance;
02164             res = &*i;
02165         }
02166     }
02167 
02168     return *res;
02169 }
02170 
02171 const std::set<gamemap::location>& ai::avoided_locations()
02172 {
02173     if(avoid_.empty()) {
02174         const config::child_list& avoids = current_team().ai_parameters().get_children("avoid");
02175         for(config::child_list::const_iterator a = avoids.begin(); a != avoids.end(); ++a) {
02176 
02177             const std::vector<location>& locs = parse_location_range((**a)["x"],(**a)["y"]);
02178             for(std::vector<location>::const_iterator i = locs.begin(); i != locs.end(); ++i) {
02179                 avoid_.insert(*i);
02180             }
02181         }
02182 
02183         if(avoid_.empty()) {
02184             avoid_.insert(location());
02185         }
02186     }
02187 
02188     return avoid_;
02189 }
02190 
02191 int ai::attack_depth()
02192 {
02193     if(attack_depth_ > 0) {
02194         return attack_depth_;
02195     }
02196 
02197     const config& parms = current_team().ai_parameters();
02198     attack_depth_ = maximum<int>(1,lexical_cast_default<int>(parms["attack_depth"],5));
02199     return attack_depth_;
02200 }
02201 
02202 namespace {
02203 template<typename Container>
02204 variant villages_from_set(const Container& villages,
02205                           const std::set<gamemap::location>* exclude=NULL) {
02206     std::vector<variant> vars;
02207     foreach(const gamemap::location& loc, villages) {
02208         if(exclude && exclude->count(loc)) {
02209             continue;
02210         }
02211         vars.push_back(variant(new location_callable(loc)));
02212     }
02213 
02214     return variant(&vars);
02215 }
02216 }
02217 
02218 variant ai_interface::get_value(const std::string& key) const
02219 {
02220     if(key == "turn") {
02221         return variant(get_info().state.turn());
02222     } else if(key == "units") {
02223         std::vector<variant> vars;
02224         for(unit_map::const_iterator i = info_.units.begin(); i != info_.units.end(); ++i) {
02225             vars.push_back(variant(new unit_callable(*i)));
02226         }
02227         return variant(&vars);
02228     } else if(key == "my_units") {
02229         std::vector<variant> vars;
02230         for(unit_map::const_iterator i = info_.units.begin(); i != info_.units.end(); ++i) {
02231             if(i->second.side() == info_.team_num) {
02232                 vars.push_back(variant(new unit_callable(*i)));
02233             }
02234         }
02235         return variant(&vars);
02236     } else if(key == "enemy_units") {
02237         std::vector<variant> vars;
02238         for(unit_map::const_iterator i = info_.units.begin(); i != info_.units.end(); ++i) {
02239             if(info_.teams[info_.team_num-1].is_enemy(i->second.side())) {
02240                 vars.push_back(variant(new unit_callable(*i)));
02241             }
02242         }
02243         return variant(&vars);
02244     } else if(key == "villages") {
02245         return villages_from_set(info_.map.villages());
02246     } else if(key == "my_villages") {
02247         return villages_from_set(current_team().villages());
02248     } else if(key == "enemy_and_unowned_villages") {
02249         return villages_from_set(info_.map.villages(), &current_team().villages());
02250     } else if(key == "map") {
02251         return variant(new gamemap_callable(info_.map));
02252     } else if(key == "teams") {
02253         std::vector<variant> vars;
02254         for(std::vector<team>::const_iterator i = info_.state.teams->begin(); i != info_.state.teams->end(); ++i) {
02255             vars.push_back(variant(new team_callable(*i)));
02256         }
02257         return variant(&vars);
02258     }
02259     return variant();
02260 }
02261 
02262 void ai_interface::get_inputs(std::vector<game_logic::formula_input>* inputs) const
02263 {
02264     using game_logic::FORMULA_READ_ONLY;
02265     inputs->push_back(game_logic::formula_input("turn", FORMULA_READ_ONLY));
02266     inputs->push_back(game_logic::formula_input("units", FORMULA_READ_ONLY));
02267     inputs->push_back(game_logic::formula_input("my_units", FORMULA_READ_ONLY));
02268     inputs->push_back(game_logic::formula_input("enemy_units", FORMULA_READ_ONLY));
02269     inputs->push_back(game_logic::formula_input("villages", FORMULA_READ_ONLY));
02270     inputs->push_back(game_logic::formula_input("my_villages", FORMULA_READ_ONLY));
02271     inputs->push_back(game_logic::formula_input("enemy_and_unowned_villages", FORMULA_READ_ONLY));
02272     inputs->push_back(game_logic::formula_input("map", FORMULA_READ_ONLY));
02273     inputs->push_back(game_logic::formula_input("teams", FORMULA_READ_ONLY));
02274 }
02275 
02276 variant ai::attack_analysis::get_value(const std::string& key) const
02277 {
02278     using namespace game_logic;
02279     if(key == "target") {
02280         return variant(new location_callable(target));
02281     } else if(key == "movements") {
02282         std::vector<variant> res;
02283         for(size_t n = 0; n != movements.size(); ++n) {
02284             map_formula_callable* item = new map_formula_callable(NULL);
02285             item->add("src", variant(new location_callable(movements[n].first)));
02286             item->add("dst", variant(new location_callable(movements[n].second)));
02287             res.push_back(variant(item));
02288         }
02289 
02290         return variant(&res);
02291     } else if(key == "units") {
02292         std::vector<variant> res;
02293         for(size_t n = 0; n != movements.size(); ++n) {
02294             res.push_back(variant(new location_callable(movements[n].first)));
02295         }
02296 
02297         return variant(&res);
02298     } else if(key == "target_value") {
02299         return variant(static_cast<int>(target_value*1000));
02300     } else if(key == "avg_losses") {
02301         return variant(static_cast<int>(avg_losses*1000));
02302     } else if(key == "chance_to_kill") {
02303         return variant(static_cast<int>(chance_to_kill*100));
02304     } else if(key == "avg_damage_inflicted") {
02305         return variant(static_cast<int>(avg_damage_inflicted));
02306     } else if(key == "target_starting_damage") {
02307         return variant(target_starting_damage);
02308     } else if(key == "avg_damage_taken") {
02309         return variant(static_cast<int>(avg_damage_taken));
02310     } else if(key == "resources_used") {
02311         return variant(static_cast<int>(resources_used));
02312     } else if(key == "terrain_quality") {
02313         return variant(static_cast<int>(terrain_quality));
02314     } else if(key == "alternative_terrain_quality") {
02315         return variant(static_cast<int>(alternative_terrain_quality));
02316     } else if(key == "vulnerability") {
02317         return variant(static_cast<int>(vulnerability));
02318     } else if(key == "support") {
02319         return variant(static_cast<int>(support));
02320     } else if(key == "leader_threat") {
02321         return variant(leader_threat);
02322     } else if(key == "uses_leader") {
02323         return variant(uses_leader);
02324     } else if(key == "is_surrounded") {
02325         return variant(is_surrounded);
02326     } else {
02327         return variant();
02328     }
02329 }
02330 
02331 void ai::attack_analysis::get_inputs(std::vector<game_logic::formula_input>* inputs) const
02332 {
02333     using namespace game_logic;
02334     inputs->push_back(formula_input("target", FORMULA_READ_ONLY));
02335     inputs->push_back(formula_input("movements", FORMULA_READ_ONLY));
02336     inputs->push_back(formula_input("units", FORMULA_READ_ONLY));
02337     inputs->push_back(formula_input("target_value", FORMULA_READ_ONLY));
02338     inputs->push_back(formula_input("avg_losses", FORMULA_READ_ONLY));
02339     inputs->push_back(formula_input("chance_to_kill", FORMULA_READ_ONLY));
02340     inputs->push_back(formula_input("avg_damage_inflicted", FORMULA_READ_ONLY));
02341     inputs->push_back(formula_input("target_starting_damage", FORMULA_READ_ONLY));
02342     inputs->push_back(formula_input("avg_damage_taken", FORMULA_READ_ONLY));
02343     inputs->push_back(formula_input("resources_used", FORMULA_READ_ONLY));
02344     inputs->push_back(formula_input("terrain_quality", FORMULA_READ_ONLY));
02345     inputs->push_back(formula_input("alternative_terrain_quality", FORMULA_READ_ONLY));
02346     inputs->push_back(formula_input("vulnerability", FORMULA_READ_ONLY));
02347     inputs->push_back(formula_input("support", FORMULA_READ_ONLY));
02348     inputs->push_back(formula_input("leader_threat", FORMULA_READ_ONLY));
02349     inputs->push_back(formula_input("uses_leader", FORMULA_READ_ONLY));
02350     inputs->push_back(formula_input("is_surrounded", FORMULA_READ_ONLY));
02351 }

Generated by doxygen 1.5.5 on 23 May 2008 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs