ai/default/attack.cpp

Go to the documentation of this file.
00001 /* $Id: attack.cpp 52543 2012-01-07 20:31:31Z crab $ */
00002 /*
00003    Copyright (C) 2003 - 2012 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 as published by
00008    the Free Software Foundation; either version 2 of the License, or
00009    (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
00018  * Calculate & analyze attacks of the default ai
00019  */
00020 
00021 #include "../../global.hpp"
00022 
00023 #include "ai.hpp"
00024 #include "../actions.hpp"
00025 #include "../manager.hpp"
00026 
00027 #include "../../attack_prediction.hpp"
00028 #include "foreach.hpp"
00029 #include "../../game_config.hpp"
00030 #include "../../log.hpp"
00031 #include "../../map.hpp"
00032 #include "../../team.hpp"
00033 
00034 static lg::log_domain log_ai("ai/attack");
00035 #define LOG_AI LOG_STREAM(info, log_ai)
00036 #define ERR_AI LOG_STREAM(err, log_ai)
00037 
00038 namespace ai {
00039 
00040 void attack_analysis::analyze(const gamemap& map, unit_map& units,
00041                   const readonly_context& ai_obj,
00042                                   const move_map& dstsrc, const move_map& srcdst,
00043                                   const move_map& enemy_dstsrc, double aggression)
00044 {
00045     const unit_map::const_iterator defend_it = units.find(target);
00046     assert(defend_it != units.end());
00047 
00048     // See if the target is a threat to our leader or an ally's leader.
00049     map_location adj[6];
00050     get_adjacent_tiles(target,adj);
00051     size_t tile;
00052     for(tile = 0; tile != 6; ++tile) {
00053         const unit_map::const_iterator leader = units.find(adj[tile]);
00054         if(leader != units.end() && leader->can_recruit() && !ai_obj.current_team().is_enemy(leader->side())) {
00055             break;
00056         }
00057     }
00058 
00059     leader_threat = (tile != 6);
00060     uses_leader = false;
00061 
00062     target_value = defend_it->cost();
00063     target_value += (double(defend_it->experience())/
00064                      double(defend_it->max_experience()))*target_value;
00065     target_starting_damage = defend_it->max_hitpoints() -
00066                              defend_it->hitpoints();
00067 
00068     // Calculate the 'alternative_terrain_quality' -- the best possible defensive values
00069     // the attacking units could hope to achieve if they didn't attack and moved somewhere.
00070     // This is used for comparative purposes, to see just how vulnerable the AI is
00071     // making itself.
00072     alternative_terrain_quality = 0.0;
00073     double cost_sum = 0.0;
00074     for(size_t i = 0; i != movements.size(); ++i) {
00075         const unit_map::const_iterator att = units.find(movements[i].first);
00076         const double cost = att->cost();
00077         cost_sum += cost;
00078         alternative_terrain_quality += cost*ai_obj.best_defensive_position(movements[i].first,dstsrc,srcdst,enemy_dstsrc).chance_to_hit;
00079     }
00080     alternative_terrain_quality /= cost_sum*100;
00081 
00082     avg_damage_inflicted = 0.0;
00083     avg_damage_taken = 0.0;
00084     resources_used = 0.0;
00085     terrain_quality = 0.0;
00086     avg_losses = 0.0;
00087     chance_to_kill = 0.0;
00088 
00089     double def_avg_experience = 0.0;
00090     double first_chance_kill = 0.0;
00091 
00092     double prob_dead_already = 0.0;
00093     assert(!movements.empty());
00094     std::vector<std::pair<map_location,map_location> >::const_iterator m;
00095 
00096     battle_context *prev_bc = NULL;
00097     const combatant *prev_def = NULL;
00098 
00099     for (m = movements.begin(); m != movements.end(); ++m) {
00100         // We fix up units map to reflect what this would look like.
00101         unit *up = units.extract(m->first);
00102         up->set_location(m->second);
00103         units.insert(up);
00104         double m_aggression = aggression;
00105 
00106         if (up->can_recruit()) {
00107             uses_leader = true;
00108             // FIXME: suokko's r29531 omitted this line
00109             leader_threat = false;
00110             m_aggression = ai_obj.get_leader_aggression();
00111         }
00112 
00113         bool from_cache = false;
00114         battle_context *bc;
00115 
00116         // This cache is only about 99% correct, but speeds up evaluation by about 1000 times.
00117         // We recalculate when we actually attack.
00118         std::map<std::pair<map_location, const unit_type *>,std::pair<battle_context_unit_stats,battle_context_unit_stats> >::iterator usc;
00119         const unit_type *up_type = up->type();
00120         if(up_type) {
00121             usc = ai_obj.unit_stats_cache().find(std::pair<map_location, const unit_type *>(target, up_type));
00122         } else {
00123             usc = ai_obj.unit_stats_cache().end();
00124         }
00125         // Just check this attack is valid for this attacking unit (may be modified)
00126         if (usc != ai_obj.unit_stats_cache().end() &&
00127                 usc->second.first.attack_num <
00128                 static_cast<int>(up->attacks().size())) {
00129 
00130             from_cache = true;
00131             bc = new battle_context(usc->second.first, usc->second.second);
00132         } else {
00133             bc = new battle_context(units, m->second, target, -1, -1, m_aggression, prev_def);
00134         }
00135         const combatant &att = bc->get_attacker_combatant(prev_def);
00136         const combatant &def = bc->get_defender_combatant(prev_def);
00137 
00138         delete prev_bc;
00139         prev_bc = bc;
00140         prev_def = &bc->get_defender_combatant(prev_def);
00141 
00142         if (!from_cache && up_type) {
00143             ai_obj.unit_stats_cache().insert(std::pair<std::pair<map_location, const unit_type *>,std::pair<battle_context_unit_stats,battle_context_unit_stats> >
00144                                             (std::pair<map_location, const unit_type *>(target, up_type),
00145                                              std::pair<battle_context_unit_stats,battle_context_unit_stats>(bc->get_attacker_stats(),
00146                                                                                                               bc->get_defender_stats())));
00147         }
00148 
00149         // Note we didn't fight at all if defender is already dead.
00150         double prob_fought = (1.0 - prob_dead_already);
00151 
00152         /** @todo 1.9 add combatant.prob_killed */
00153         double prob_killed = def.hp_dist[0] - prob_dead_already;
00154         prob_dead_already = def.hp_dist[0];
00155 
00156         double prob_died = att.hp_dist[0];
00157         double prob_survived = (1.0 - prob_died) * prob_fought;
00158 
00159         double cost = up->cost();
00160         const bool on_village = map.is_village(m->second);
00161         // Up to double the value of a unit based on experience
00162         cost += (double(up->experience()) / up->max_experience())*cost;
00163         resources_used += cost;
00164         avg_losses += cost * prob_died;
00165 
00166         // add half of cost for poisoned unit so it might get chance to heal
00167         avg_losses += cost * up->get_state(unit::STATE_POISONED) /2;
00168 
00169         // Double reward to emphasize getting onto villages if they survive.
00170         if (on_village) {
00171             avg_damage_taken -= game_config::poison_amount*2 * prob_survived;
00172         }
00173 
00174         terrain_quality += (double(bc->get_defender_stats().chance_to_hit)/100.0)*cost * (on_village ? 0.5 : 1.0);
00175 
00176         double advance_prob = 0.0;
00177         // The reward for advancing a unit is to get a 'negative' loss of that unit
00178         if (!up->advances_to().empty()) {
00179             int xp_for_advance = up->max_experience() - up->experience();
00180 
00181             // See bug #6272... in some cases, unit already has got enough xp to advance,
00182             // but hasn't (bug elsewhere?).  Can cause divide by zero.
00183             if (xp_for_advance <= 0)
00184                 xp_for_advance = 1;
00185 
00186             int fight_xp = defend_it->level();
00187             int kill_xp = game_config::kill_xp(fight_xp);
00188 
00189             if (fight_xp >= xp_for_advance) {
00190                 advance_prob = prob_fought;
00191                 avg_losses -= up->cost() * prob_fought;
00192             } else if (kill_xp >= xp_for_advance) {
00193                 advance_prob = prob_killed;
00194                 avg_losses -= up->cost() * prob_killed;
00195                 // The reward for getting a unit closer to advancement
00196                 // (if it didn't advance) is to get the proportion of
00197                 // remaining experience needed, and multiply it by
00198                 // a quarter of the unit cost.
00199                 // This will cause the AI to heavily favor
00200                 // getting xp for close-to-advance units.
00201                 avg_losses -= up->cost() * 0.25 *
00202                     fight_xp * (prob_fought - prob_killed)
00203                     / xp_for_advance;
00204             } else {
00205                 avg_losses -= up->cost() * 0.25 *
00206                     (kill_xp * prob_killed + fight_xp * (prob_fought - prob_killed))
00207                     / xp_for_advance;
00208             }
00209 
00210             // The reward for killing with a unit that plagues
00211             // is to get a 'negative' loss of that unit.
00212             if (bc->get_attacker_stats().plagues) {
00213                 avg_losses -= prob_killed * up->cost();
00214             }
00215         }
00216 
00217         // If we didn't advance, we took this damage.
00218         avg_damage_taken += (up->hitpoints() - att.average_hp()) * (1.0 - advance_prob);
00219 
00220         /**
00221          * @todo 1.9: attack_prediction.cpp should understand advancement
00222          * directly.  For each level of attacker def gets 1 xp or
00223          * kill_experience.
00224          */
00225         int fight_xp = up->level();
00226         int kill_xp = game_config::kill_xp(fight_xp);
00227         def_avg_experience += fight_xp * (1.0 - att.hp_dist[0]) + kill_xp * att.hp_dist[0];
00228         if (m == movements.begin()) {
00229             first_chance_kill = def.hp_dist[0];
00230         }
00231     }
00232 
00233     if (!defend_it->advances_to().empty() &&
00234         def_avg_experience >= defend_it->max_experience() - defend_it->experience()) {
00235         // It's likely to advance: only if we can kill with first blow.
00236         chance_to_kill = first_chance_kill;
00237         // Negative average damage (it will advance).
00238         avg_damage_inflicted = defend_it->hitpoints() - defend_it->max_hitpoints();
00239     } else {
00240         chance_to_kill = prev_def->hp_dist[0];
00241         avg_damage_inflicted = defend_it->hitpoints() - prev_def->average_hp(map.gives_healing(defend_it->get_location()));
00242     }
00243 
00244     delete prev_bc;
00245     terrain_quality /= resources_used;
00246 
00247     // Restore the units to their original positions.
00248     for (m = movements.begin(); m != movements.end(); ++m) {
00249         units.move(m->second, m->first);
00250     }
00251 }
00252 
00253 bool attack_analysis::attack_close(const map_location& loc) const
00254 {
00255     std::set<map_location> &attacks = manager::get_ai_info().recent_attacks;
00256     for(std::set<map_location>::const_iterator i = attacks.begin(); i != attacks.end(); ++i) {
00257         if(distance_between(*i,loc) < 4) {
00258             return true;
00259         }
00260     }
00261 
00262     return false;
00263 }
00264 
00265 
00266 double attack_analysis::rating(double aggression, const readonly_context& ai_obj) const
00267 {
00268     if(leader_threat) {
00269         aggression = 1.0;
00270     }
00271 
00272     if(uses_leader) {
00273         aggression = ai_obj.get_leader_aggression();
00274     }
00275 
00276     double value = chance_to_kill*target_value - avg_losses*(1.0-aggression);
00277 
00278     if(terrain_quality > alternative_terrain_quality) {
00279         // This situation looks like it might be a bad move:
00280         // we are moving our attackers out of their optimal terrain
00281         // into sub-optimal terrain.
00282         // Calculate the 'exposure' of our units to risk.
00283 
00284 #ifdef SUOKKO
00285         //FIXME: this code was in sukko's r29531  Correct?
00286         const double exposure_mod = uses_leader ? ai_obj.current_team().caution()* 8.0 : ai_obj.current_team().caution() * 4.0;
00287         const double exposure = exposure_mod*resources_used*((terrain_quality - alternative_terrain_quality)/10)*vulnerability/std::max<double>(0.01,support);
00288 #else
00289         const double exposure_mod = uses_leader ? 2.0 : ai_obj.get_caution();
00290         const double exposure = exposure_mod*resources_used*(terrain_quality - alternative_terrain_quality)*vulnerability/std::max<double>(0.01,support);
00291 #endif
00292         LOG_AI << "attack option has base value " << value << " with exposure " << exposure << ": "
00293             << vulnerability << "/" << support << " = " << (vulnerability/std::max<double>(support,0.1)) << "\n";
00294         value -= exposure*(1.0-aggression);
00295     }
00296 
00297     // Prefer to attack already damaged targets.
00298     value += ((target_starting_damage/3 + avg_damage_inflicted) - (1.0-aggression)*avg_damage_taken)/10.0;
00299 
00300        // If the unit is surrounded and there is no support,
00301        // or if the unit is surrounded and the average damage is 0,
00302        // the unit skips its sanity check and tries to break free as good as possible.
00303        if(!is_surrounded || (support != 0 && avg_damage_taken != 0))
00304        {
00305                // Sanity check: if we're putting ourselves at major risk,
00306                // and have no chance to kill, and we're not aiding our allies
00307                // who are also attacking, then don't do it.
00308                if(vulnerability > 50.0 && vulnerability > support*2.0
00309                && chance_to_kill < 0.02 && aggression < 0.75
00310                && !attack_close(target)) {
00311                        return -1.0;
00312                }
00313         }
00314 
00315     if(!leader_threat && vulnerability*terrain_quality > 0.0) {
00316         value *= support/(vulnerability*terrain_quality);
00317     }
00318 
00319     value /= ((resources_used/2) + (resources_used/2)*terrain_quality);
00320 
00321     if(leader_threat) {
00322         value *= 5.0;
00323     }
00324 
00325     LOG_AI << "attack on " << target << ": attackers: " << movements.size()
00326         << " value: " << value << " chance to kill: " << chance_to_kill
00327         << " damage inflicted: " << avg_damage_inflicted
00328         << " damage taken: " << avg_damage_taken
00329         << " vulnerability: " << vulnerability
00330         << " support: " << support
00331         << " quality: " << terrain_quality
00332         << " alternative quality: " << alternative_terrain_quality << "\n";
00333 
00334     return value;
00335 }
00336 
00337 } //end of namespace ai
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines

Generated by doxygen 1.7.1 on Tue May 22 2012 01:03:37 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs