ai_attack.cpp

Go to the documentation of this file.
00001 /* $Id: ai_attack.cpp 26083 2008-04-25 01:55:51Z soliton $ */
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 //! @file ai_attack.cpp
00017 //! Calculate & analyse attacks.
00018 
00019 #include "global.hpp"
00020 
00021 #include "ai.hpp"
00022 #include "attack_prediction.hpp"
00023 #include "game_config.hpp"
00024 #include "gamestatus.hpp"
00025 #include "log.hpp"
00026 
00027 #include <cassert>
00028 
00029 #define LOG_AI LOG_STREAM(info, ai)
00030 
00031 const int max_positions = 10000;
00032 
00033 //! Analyze possibility of attacking target on 'loc'.
00034 void ai::do_attack_analysis(
00035                      const location& loc,
00036                      const move_map& srcdst, const move_map& dstsrc,
00037                      const move_map& fullmove_srcdst, const move_map& fullmove_dstsrc,
00038                      const move_map& enemy_srcdst, const move_map& enemy_dstsrc,
00039                      const location* tiles, bool* used_locations,
00040                      std::vector<location>& units,
00041                      std::vector<attack_analysis>& result,
00042                      attack_analysis& cur_analysis
00043                     )
00044 {
00045     // This function is called fairly frequently, so interact with the user here.
00046     raise_user_interact();
00047 
00048     if(cur_analysis.movements.size() >= size_t(attack_depth())) {
00049         //std::cerr << "ANALYSIS " << cur_analysis.movements.size() << " >= " << attack_depth() << "\n";
00050         return;
00051     }
00052 
00053     static double best_results[6];
00054     if(result.empty()) {
00055         for(int i = 0; i != 6; ++i) {
00056             best_results[i] = 0.0;
00057         }
00058     }
00059 
00060     const size_t max_positions = 1000;
00061     if(result.size() > max_positions && !cur_analysis.movements.empty()) {
00062         LOG_AI << "cut analysis short with number of positions\n";
00063         return;
00064     }
00065 
00066     const double cur_rating = cur_analysis.movements.empty() ? -1.0 :
00067                               cur_analysis.rating(current_team().aggression(),*this);
00068 
00069     double rating_to_beat = cur_rating;
00070 
00071     if(!cur_analysis.movements.empty()) {
00072         assert(cur_analysis.movements.size() < 6);
00073         double& best_res = best_results[cur_analysis.movements.size()-1];
00074         rating_to_beat = best_res = maximum(best_res,cur_rating);
00075     }
00076 
00077     for(size_t i = 0; i != units.size(); ++i) {
00078         const location current_unit = units[i];
00079 
00080         unit_map::iterator unit_itor = units_.find(current_unit);
00081         assert(unit_itor != units_.end());
00082 
00083         // See if the unit has the backstab ability.
00084         // Units with backstab will want to try to have a
00085         // friendly unit opposite the position they move to.
00086         //
00087         // See if the unit has the slow ability -- units with slow only attack first.
00088         bool backstab = false, slow = false;
00089         std::vector<attack_type>& attacks = unit_itor->second.attacks();
00090         for(std::vector<attack_type>::iterator a = attacks.begin(); a != attacks.end(); ++a) {
00091             a->set_specials_context(gamemap::location(),gamemap::location(),
00092                                                             &units_,&map_,&state_,&teams_,true,NULL);
00093             if(a->get_special_bool("backstab")) {
00094                 backstab = true;
00095             }
00096 
00097             if(a->get_special_bool("slow")) {
00098                 slow = true;
00099             }
00100         }
00101 
00102         if(slow && cur_analysis.movements.empty() == false) {
00103             continue;
00104         }
00105 
00106                // Check if the friendly unit is surrounded,
00107                // A unit is surrounded if it is flanked by enemy units
00108                // and at least one other enemy unit is nearby
00109                // or if the unit is totaly surrounded by enemies
00110                // with max. one tile to escape.
00111                bool is_surrounded = false;
00112                bool is_flanked = false;
00113                int enemy_units_around = 0;
00114                int accessible_tiles = 0;
00115                gamemap::location adj[6];
00116                get_adjacent_tiles(current_unit, adj);
00117 
00118                size_t tile;
00119                for(tile = 0; tile != 3; ++tile) {
00120 
00121                        const unit_map::const_iterator tmp_unit = units_.find(adj[tile]);
00122                        bool possible_flanked = false;
00123 
00124                        if(map_.on_board(adj[tile]))
00125                        {
00126                                accessible_tiles++;
00127                                if(tmp_unit != units_.end() && team_num_ != tmp_unit->second.side())
00128                                {
00129                                        enemy_units_around++;
00130                                        possible_flanked = true;
00131                                }
00132                        }
00133 
00134                        const unit_map::const_iterator tmp_opposite_unit = units_.find(adj[tile + 3]);
00135                         if(map_.on_board(adj[tile + 3]))
00136                        {
00137                                accessible_tiles++;
00138                                if(tmp_opposite_unit != units_.end() && team_num_ != tmp_opposite_unit->second.side())
00139                                {
00140                                        enemy_units_around++;
00141                                        if(possible_flanked)
00142                                        {
00143                                                is_flanked = true;
00144                                        }
00145                                }
00146                        }
00147                }
00148 
00149                if((is_flanked && enemy_units_around > 2) || enemy_units_around >= accessible_tiles - 1)
00150                        is_surrounded = true;
00151 
00152 
00153 
00154         double best_vulnerability = 0.0, best_support = 0.0;
00155         int best_rating = 0;
00156         int cur_position = -1;
00157 
00158         // Iterate over positions adjacent to the unit, finding the best rated one.
00159         for(int j = 0; j != 6; ++j) {
00160 
00161             // If in this planned attack, a unit is already in this location.
00162             if(used_locations[j]) {
00163                 continue;
00164             }
00165 
00166             // See if the current unit can reach that position.
00167             if (tiles[j] != current_unit) {
00168                 typedef std::multimap<location,location>::const_iterator Itor;
00169                 std::pair<Itor,Itor> its = dstsrc.equal_range(tiles[j]);
00170                 while(its.first != its.second) {
00171                     if(its.first->second == current_unit)
00172                         break;
00173                     ++its.first;
00174                 }
00175 
00176                 // If the unit can't move to this location.
00177                 if(its.first == its.second || units_.find(tiles[j]) != units_.end()) {
00178                     continue;
00179                 }
00180             }
00181 
00182             // Check to see whether this move would be a backstab.
00183             int backstab_bonus = 1;
00184             double surround_bonus = 1.0;
00185 
00186             if(tiles[(j+3)%6] != current_unit) {
00187                 const unit_map::const_iterator itor = units_.find(tiles[(j+3)%6]);
00188 
00189                 // Note that we *could* also check if a unit plans to move there
00190                 // before we're at this stage, but we don't because, since the
00191                 // attack calculations don't actually take backstab into account (too complicated),
00192                 // this could actually make our analysis look *worse* instead of better.
00193                 // So we only check for 'concrete' backstab opportunities.
00194                 // That would also break backstab_check, since it assumes
00195                 // the defender is in place.
00196                 if(itor != units_.end() &&
00197                     backstab_check(tiles[j], loc, units_, teams_)) {
00198                     if(backstab) {
00199                         backstab_bonus = 2;
00200                     }
00201 
00202                     surround_bonus = 1.2;
00203                 }
00204 
00205 
00206             }
00207 
00208             // See if this position is the best rated we've seen so far.
00209             const int rating = rate_terrain(unit_itor->second,tiles[j]) * backstab_bonus;
00210             if(cur_position >= 0 && rating < best_rating) {
00211                 continue;
00212             }
00213 
00214             // Find out how vulnerable we are to attack from enemy units in this hex.
00215             const double vulnerability = power_projection(tiles[j],enemy_dstsrc);
00216 
00217             // Calculate how much support we have on this hex from allies.
00218             // Support does not take into account terrain, because we don't want
00219             // to move into a hex that is surrounded by good defensive terrain.
00220             const double support = power_projection(tiles[j],fullmove_dstsrc,false);
00221 
00222             // If this is a position with equal defense to another position,
00223             // but more vulnerability then we don't want to use it.
00224             if(cur_position >= 0 && rating == best_rating && vulnerability/surround_bonus - support*surround_bonus >= best_vulnerability - best_support) {
00225                 continue;
00226             }
00227 
00228             cur_position = j;
00229             best_rating = rating;
00230             best_vulnerability = vulnerability/surround_bonus;
00231             best_support = support*surround_bonus;
00232         }
00233 
00234         if(cur_position != -1) {
00235             units.erase(units.begin() + i);
00236 
00237             cur_analysis.movements.push_back(std::pair<location,location>(current_unit,tiles[cur_position]));
00238 
00239             cur_analysis.vulnerability += best_vulnerability;
00240 
00241             cur_analysis.support += best_support;
00242 
00243             cur_analysis.is_surrounded = is_surrounded;
00244 
00245             cur_analysis.analyze(map_, units_, teams_, state_, *this, dstsrc, srcdst, enemy_dstsrc, current_team().aggression());
00246 
00247             //Remove this short-circuiting logic for now.. --David
00248             if(cur_analysis.rating(current_team().aggression(),*this) > rating_to_beat) {
00249 
00250                 result.push_back(cur_analysis);
00251                 used_locations[cur_position] = true;
00252                 do_attack_analysis(loc,srcdst,dstsrc,fullmove_srcdst,fullmove_dstsrc,enemy_srcdst,enemy_dstsrc,
00253                                    tiles,used_locations,
00254                                    units,result,cur_analysis);
00255                 used_locations[cur_position] = false;
00256             }
00257 
00258             cur_analysis.vulnerability -= best_vulnerability;
00259             cur_analysis.support -= best_support;
00260 
00261             cur_analysis.movements.pop_back();
00262 
00263             units.insert(units.begin() + i, current_unit);
00264         }
00265     }
00266 }
00267 
00268 
00269 void ai::attack_analysis::analyze(const gamemap& map, unit_map& units,
00270                                   const std::vector<team>& teams,
00271                                   const gamestatus& status,
00272                                   class ai& ai_obj,
00273                                   const move_map& dstsrc, const move_map& srcdst,
00274                                   const move_map& enemy_dstsrc, double aggression)
00275 {
00276     const unit_map::const_iterator defend_it = units.find(target);
00277     assert(defend_it != units.end());
00278 
00279     // See if the target is a threat to our leader or an ally's leader.
00280     gamemap::location adj[6];
00281     get_adjacent_tiles(target,adj);
00282     size_t tile;
00283     for(tile = 0; tile != 6; ++tile) {
00284         const unit_map::const_iterator leader = units.find(adj[tile]);
00285         if(leader != units.end() && leader->second.can_recruit() && ai_obj.current_team().is_enemy(leader->second.side()) == false) {
00286             break;
00287         }
00288     }
00289 
00290     leader_threat = (tile != 6);
00291     uses_leader = false;
00292 
00293     target_value = defend_it->second.cost();
00294     target_value += (double(defend_it->second.experience())/
00295                      double(defend_it->second.max_experience()))*target_value;
00296     target_starting_damage = defend_it->second.max_hitpoints() -
00297                              defend_it->second.hitpoints();
00298 
00299     // Calculate the 'alternative_terrain_quality' -- the best possible defensive values
00300     // the attacking units could hope to achieve if they didn't attack and moved somewhere.
00301     // This is used for comparative purposes, to see just how vulnerable the AI is
00302     // making itself.
00303     alternative_terrain_quality = 0.0;
00304     double cost_sum = 0.0;
00305     for(size_t i = 0; i != movements.size(); ++i) {
00306         const unit_map::const_iterator att = units.find(movements[i].first);
00307         const double cost = att->second.cost();
00308         cost_sum += cost;
00309         alternative_terrain_quality += cost*ai_obj.best_defensive_position(movements[i].first,dstsrc,srcdst,enemy_dstsrc).chance_to_hit;
00310     }
00311     alternative_terrain_quality /= cost_sum*100;
00312 
00313     avg_damage_inflicted = 0.0;
00314     avg_damage_taken = 0.0;
00315     resources_used = 0.0;
00316     terrain_quality = 0.0;
00317     avg_losses = 0.0;
00318     chance_to_kill = 0.0;
00319 
00320     double def_avg_experience = 0.0;
00321     double first_chance_kill = 0.0;
00322 
00323     double prob_dead_already = 0.0;
00324     assert(!movements.empty());
00325     std::vector<std::pair<location,location> >::const_iterator m;
00326 
00327     battle_context *prev_bc = NULL;
00328     const combatant *prev_def = NULL;
00329 
00330     for (m = movements.begin(); m != movements.end(); ++m) {
00331         // We fix up units map to reflect what this would look like.
00332         std::pair<gamemap::location,unit> *up = units.extract(m->first);
00333         up->first = m->second;
00334         units.add(up);
00335 
00336         if (up->second.can_recruit()) {
00337             uses_leader = true;
00338             leader_threat = false;
00339         }
00340 
00341         int att_weapon = -1, def_weapon = -1;
00342         bool from_cache = false;
00343         battle_context *bc;
00344 
00345         // This cache is only about 99% correct, but speeds up evaluation by about 1000 times.
00346         // We recalculate when we actually attack.
00347         std::map<std::pair<location, const unit_type *>,std::pair<battle_context::unit_stats,battle_context::unit_stats> >::iterator usc;
00348         if(up->second.type()) {
00349             usc = ai_obj.unit_stats_cache_.find(std::pair<location, const unit_type *>(target, up->second.type()));
00350         } else {
00351             usc = ai_obj.unit_stats_cache_.end();
00352         }
00353         // Just check this attack is valid for this attacking unit (may be modified)
00354         if (usc != ai_obj.unit_stats_cache_.end() &&
00355                 usc->second.first.attack_num <
00356                 static_cast<int>(up->second.attacks().size())) {
00357 
00358             from_cache = true;
00359             bc = new battle_context(usc->second.first, usc->second.second);
00360         } else {
00361             bc = new battle_context(map, teams, units, status, m->second, target, att_weapon, def_weapon, aggression, prev_def);
00362         }
00363         const combatant &att = bc->get_attacker_combatant(prev_def);
00364         const combatant &def = bc->get_defender_combatant(prev_def);
00365 
00366         delete prev_bc;
00367         prev_bc = bc;
00368         prev_def = &bc->get_defender_combatant(prev_def);
00369 
00370         if (!from_cache && up->second.type()) {
00371             ai_obj.unit_stats_cache_.insert(std::pair<std::pair<location, const unit_type *>,std::pair<battle_context::unit_stats,battle_context::unit_stats> >
00372                                             (std::pair<location, const unit_type *>(target, up->second.type()),
00373                                              std::pair<battle_context::unit_stats,battle_context::unit_stats>(bc->get_attacker_stats(),
00374                                                                                                               bc->get_defender_stats())));
00375         }
00376 
00377         // Note we didn't fight at all if defender is already dead.
00378         double prob_fought = (1.0 - prob_dead_already);
00379 
00380         //! @todo FIXME: add combatant.prob_killed
00381         double prob_killed = def.hp_dist[0] - prob_dead_already;
00382         prob_dead_already = def.hp_dist[0];
00383 
00384         double prob_died = att.hp_dist[0];
00385         double prob_survived = (1.0 - prob_died) * prob_fought;
00386 
00387         double cost = up->second.cost();
00388         const bool on_village = map.is_village(m->second);
00389         // Up to double the value of a unit based on experience
00390         cost += (double(up->second.experience())/double(up->second.max_experience()))*cost;
00391         resources_used += cost;
00392         avg_losses += cost * prob_died;
00393 
00394         // Double reward to emphasize getting onto villages if they survive.
00395         if (on_village) {
00396             avg_damage_taken -= game_config::poison_amount*2 * prob_survived;
00397         }
00398 
00399         terrain_quality += (double(bc->get_defender_stats().chance_to_hit)/100.0)*cost * (on_village ? 0.5 : 1.0);
00400 
00401         double advance_prob = 0.0;
00402         // The reward for advancing a unit is to get a 'negative' loss of that unit
00403         if (!up->second.advances_to().empty()) {
00404             int xp_for_advance = up->second.max_experience() - up->second.experience();
00405             int kill_xp, fight_xp;
00406 
00407             // See bug #6272... in some cases, unit already has got enough xp to advance,
00408             // but hasn't (bug elsewhere?).  Can cause divide by zero.
00409             if (xp_for_advance <= 0)
00410                 xp_for_advance = 1;
00411 
00412             fight_xp = defend_it->second.level();
00413             kill_xp = fight_xp * game_config::kill_experience;
00414 
00415             if (fight_xp >= xp_for_advance)
00416                 advance_prob = prob_fought;
00417             else if (kill_xp >= xp_for_advance)
00418                 advance_prob = prob_killed;
00419             avg_losses -= up->second.cost() * advance_prob;
00420 
00421             // The reward for getting a unit closer to advancement
00422             // (if it didn't advance) is to get the proportion of
00423             // remaining experience needed, and multiply it by
00424             // a quarter of the unit cost.
00425             // This will cause the AI to heavily favor
00426             // getting xp for close-to-advance units.
00427             avg_losses -= (up->second.cost()*fight_xp)/(xp_for_advance*4) * (prob_fought - prob_killed);
00428             avg_losses -= (up->second.cost()*kill_xp)/(xp_for_advance*4) * prob_killed;
00429 
00430             // The reward for killing with a unit that plagues
00431             // is to get a 'negative' loss of that unit.
00432             if (bc->get_attacker_stats().plagues) {
00433                 avg_losses -= prob_killed * up->second.cost();
00434             }
00435         }
00436 
00437         // If we didn't advance, we took this damage.
00438         avg_damage_taken += (up->second.hitpoints() - att.average_hp()) * (1.0 - advance_prob);
00439 
00440         //! @todo FIXME: attack_prediction.cpp should understand advancement directly.
00441         // For each level of attacker def gets 1 xp or kill_experience.
00442         def_avg_experience += up->second.level() *
00443             (1.0 - att.hp_dist[0] + game_config::kill_experience * att.hp_dist[0]);
00444         if (m == movements.begin()) {
00445             first_chance_kill = def.hp_dist[0];
00446         }
00447     }
00448 
00449     if (!defend_it->second.advances_to().empty() &&
00450         def_avg_experience >= defend_it->second.max_experience() - defend_it->second.experience()) {
00451         // It's likely to advance: only if we can kill with first blow.
00452         chance_to_kill = first_chance_kill;
00453         // Negative average damage (it will advance).
00454         avg_damage_inflicted = defend_it->second.hitpoints() - defend_it->second.max_hitpoints();
00455     } else {
00456         chance_to_kill = prev_def->hp_dist[0];
00457         avg_damage_inflicted = defend_it->second.hitpoints() - prev_def->average_hp(map.gives_healing(defend_it->first));
00458     }
00459 
00460     delete prev_bc;
00461     terrain_quality /= resources_used;
00462 
00463     // Restore the units to their original positions.
00464     for (m = movements.begin(); m != movements.end(); ++m) {
00465         std::pair<gamemap::location,unit> *up = units.extract(m->second);
00466         up->first = m->first;
00467         units.add(up);
00468     }
00469 }
00470 
00471 double ai::attack_analysis::rating(double aggression, ai& ai_obj) const
00472 {
00473     if(leader_threat) {
00474         aggression = 1.0;
00475     }
00476 
00477     // Only use the leader if we do a serious amount of damage,
00478     // compared to how much they do to us.
00479     if(uses_leader && aggression > -4.0) {
00480         LOG_AI << "uses leader..\n";
00481         aggression = -4.0;
00482     }
00483 
00484     double value = chance_to_kill*target_value - avg_losses*(1.0-aggression);
00485 
00486     if(terrain_quality > alternative_terrain_quality) {
00487         // This situation looks like it might be a bad move:
00488         // we are moving our attackers out of their optimal terrain
00489         // into sub-optimal terrain.
00490         // Calculate the 'exposure' of our units to risk.
00491 
00492         const double exposure_mod = uses_leader ? 2.0 : ai_obj.current_team().caution();
00493         const double exposure = exposure_mod*resources_used*(terrain_quality - alternative_terrain_quality)*vulnerability/maximum<double>(0.01,support);
00494         LOG_AI << "attack option has base value " << value << " with exposure " << exposure << ": "
00495             << vulnerability << "/" << support << " = " << (vulnerability/maximum<double>(support,0.1)) << "\n";
00496         if(uses_leader) {
00497             ai_obj.log_message("attack option has value " + str_cast(value) + " with exposure " + str_cast(exposure) + ": " + str_cast(vulnerability) + "/" + str_cast(support));
00498         }
00499 
00500         value -= exposure*(1.0-aggression);
00501     }
00502 
00503     // If this attack uses our leader, and the leader can reach the keep,
00504     // and has gold to spend, reduce the value to reflect the leader's
00505     // lost recruitment opportunity in the case of an attack.
00506     if(uses_leader && ai_obj.leader_can_reach_keep() && ai_obj.current_team().gold() > 20) {
00507         value -= double(ai_obj.current_team().gold())*0.5;
00508     }
00509 
00510     // Prefer to attack already damaged targets.
00511     value += ((target_starting_damage/3 + avg_damage_inflicted) - (1.0-aggression)*avg_damage_taken)/10.0;
00512 
00513        // If the unit is surrounded and there is no support,
00514        // or if the unit is surrounded and the average damage is 0,
00515        // the unit skips its sanity check and tries to break free as good as possible.
00516        if(!is_surrounded || (support != 0 && avg_damage_taken != 0))
00517        {
00518                // Sanity check: if we're putting ourselves at major risk,
00519                // and have no chance to kill, and we're not aiding our allies
00520                // who are also attacking, then don't do it.
00521                if(vulnerability > 50.0 && vulnerability > support*2.0
00522                && chance_to_kill < 0.02 && aggression < 0.75
00523                && !ai_obj.attack_close(target)) {
00524                        return -1.0;
00525                }
00526         }
00527 
00528     if(!leader_threat && vulnerability*terrain_quality > 0.0) {
00529         value *= support/(vulnerability*terrain_quality);
00530     }
00531 
00532     value /= ((resources_used/2) + (resources_used/2)*terrain_quality);
00533 
00534     if(leader_threat) {
00535         value *= 5.0;
00536     }
00537 
00538     LOG_AI << "attack on " << target << ": attackers: " << movements.size()
00539         << " value: " << value << " chance to kill: " << chance_to_kill
00540         << " damage inflicted: " << avg_damage_inflicted
00541         << " damage taken: " << avg_damage_taken
00542         << " vulnerability: " << vulnerability
00543         << " support: " << support
00544         << " quality: " << terrain_quality
00545         << " alternative quality: " << alternative_terrain_quality << "\n";
00546 
00547     return value;
00548 }
00549 
00550 std::vector<ai::attack_analysis> ai::analyze_targets(
00551                  const move_map& srcdst, const move_map& dstsrc,
00552                  const move_map& enemy_srcdst, const move_map& enemy_dstsrc
00553                 )
00554 {
00555     log_scope2(ai, "analyzing targets...");
00556 
00557     std::vector<attack_analysis> res;
00558 
00559     std::vector<location> unit_locs;
00560     for(unit_map::const_iterator i = units_.begin(); i != units_.end(); ++i) {
00561         if(i->second.side() == team_num_ && i->second.attacks_left()) {
00562             unit_locs.push_back(i->first);
00563         }
00564     }
00565 
00566     bool used_locations[6];
00567     std::fill(used_locations,used_locations+6,false);
00568 
00569     std::map<location,paths> dummy_moves;
00570     move_map fullmove_srcdst, fullmove_dstsrc;
00571     calculate_possible_moves(dummy_moves,fullmove_srcdst,fullmove_dstsrc,false,true);
00572 
00573     unit_stats_cache_.clear();
00574 
00575     for(unit_map::const_iterator j = units_.begin(); j != units_.end(); ++j) {
00576 
00577         // Attack anyone who is on the enemy side,
00578         // and who is not invisible or turned to stone.
00579         if(current_team().is_enemy(j->second.side()) && !j->second.incapacitated() &&
00580            j->second.invisible(j->first,units_,teams_) == false) {
00581             location adjacent[6];
00582             get_adjacent_tiles(j->first,adjacent);
00583             attack_analysis analysis;
00584             analysis.target = j->first;
00585             analysis.vulnerability = 0.0;
00586             analysis.support = 0.0;
00587 
00588 //          const int ticks = SDL_GetTicks();
00589 
00590             do_attack_analysis(j->first,srcdst,dstsrc,fullmove_srcdst,fullmove_dstsrc,enemy_srcdst,enemy_dstsrc,
00591                         adjacent,used_locations,unit_locs,res,analysis);
00592 
00593 //          const int time_taken = SDL_GetTicks() - ticks;
00594 //          static int max_time = 0;
00595 //          if(time_taken > max_time)
00596 //              max_time = time_taken;
00597         }
00598     }
00599 
00600     return res;
00601 }
00602 
00603 double ai::power_projection(const gamemap::location& loc,  const move_map& dstsrc, bool use_terrain) const
00604 {
00605     gamemap::location used_locs[6];
00606     int ratings[6];
00607     int num_used_locs = 0;
00608 
00609     gamemap::location locs[6];
00610     get_adjacent_tiles(loc,locs);
00611 
00612     const int lawful_bonus = state_.get_time_of_day().lawful_bonus;
00613 
00614     int res = 0;
00615 
00616     bool changed = false;
00617     for (int i = 0;; ++i) {
00618         if (i == 6) {
00619             if (!changed) break;
00620             // Loop once again, in case a unit found a better spot
00621             // and freed the place for another unit.
00622             changed = false;
00623             i = 0;
00624         }
00625 
00626         if (map_.on_board(locs[i]) == false) {
00627             continue;
00628         }
00629 
00630         const t_translation::t_terrain terrain = map_[locs[i]];
00631 
00632         typedef move_map::const_iterator Itor;
00633         typedef std::pair<Itor,Itor> Range;
00634         Range its = dstsrc.equal_range(locs[i]);
00635 
00636         gamemap::location* const beg_used = used_locs;
00637         gamemap::location* end_used = used_locs + num_used_locs;
00638 
00639         int best_rating = 0;
00640         gamemap::location best_unit;
00641 
00642         for(Itor it = its.first; it != its.second; ++it) {
00643             const unit_map::const_iterator u = units_.find(it->second);
00644 
00645             // Unit might have been killed, and no longer exist
00646             if(u == units_.end()) {
00647                 continue;
00648             }
00649 
00650             const unit& un = u->second;
00651 
00652             int tod_modifier = 0;
00653             if(un.alignment() == unit_type::LAWFUL) {
00654                 tod_modifier = lawful_bonus;
00655             } else if(un.alignment() == unit_type::CHAOTIC) {
00656                 tod_modifier = -lawful_bonus;
00657             }
00658 
00659             int hp = un.hitpoints() * 1000 / un.max_hitpoints();
00660             int most_damage = 0;
00661             for(std::vector<attack_type>::const_iterator att =
00662                 un.attacks().begin(); att != un.attacks().end(); ++att) {
00663                 int damage = att->damage() * att->num_attacks() *
00664                              (100 + tod_modifier);
00665                 if(damage > most_damage) {
00666                     most_damage = damage;
00667                 }
00668             }
00669 
00670             int village_bonus = use_terrain && map_.is_village(terrain) ? 3 : 2;
00671             int defense = use_terrain ? 100 - un.defense_modifier(terrain) : 50;
00672             int rating = hp * defense * most_damage * village_bonus / 200;
00673             if(rating > best_rating) {
00674                 gamemap::location *pos = std::find(beg_used, end_used, it->second);
00675                 // Check if the spot is the same or better than an older one.
00676                 if (pos == end_used || rating >= ratings[pos - beg_used]) {
00677                     best_rating = rating;
00678                     best_unit = it->second;
00679                 }
00680             }
00681         }
00682 
00683         if (!best_unit.valid()) continue;
00684         gamemap::location *pos = std::find(beg_used, end_used, best_unit);
00685         int index = pos - beg_used;
00686         if (index == num_used_locs)
00687             ++num_used_locs;
00688         else if (best_rating == ratings[index])
00689             continue;
00690         else {
00691             // The unit was in another spot already, so remove its older rating
00692             // from the final result, and require a new run to fill its old spot.
00693             res -= ratings[index];
00694             changed = true;
00695         }
00696         used_locs[index] = best_unit;
00697         ratings[index] = best_rating;
00698         res += best_rating;
00699     }
00700 
00701     return res / 100000.;
00702 }
00703 
00704 //! There is no real hope for us: we should try to do some damage to the enemy.
00705 //! We can spend some cycles here, since it's rare.
00706 bool ai::desperate_attack(const gamemap::location &loc)
00707 {
00708     const unit &u = units_.find(loc)->second;
00709     LOG_AI << "desperate attack by '" << u.type_id() << "' " << loc << "\n";
00710 
00711     gamemap::location adj[6];
00712     get_adjacent_tiles(loc, adj);
00713 
00714     double best_kill_prob = 0.0;
00715     unsigned int best_weapon = 0;
00716     int best_def_weapon = -1;
00717     unsigned best_dir = 0;
00718 
00719     {
00720         for(unsigned int n = 0; n != 6; ++n) {
00721             const unit_map::iterator enemy = find_visible_unit(units_,adj[n], map_, teams_, current_team());
00722             if (enemy != units_.end() &&
00723                current_team().is_enemy(enemy->second.side()) && !enemy->second.incapacitated()) {
00724                 const std::vector<attack_type>& attacks = u.attacks();
00725                 for (unsigned int i = 0; i != attacks.size(); ++i) {
00726                     // Skip weapons with attack_weight=0
00727                     if (attacks[i].attack_weight() > 0) {
00728                         battle_context bc(map_, teams_, units_, state_, loc, adj[n], i);
00729                         combatant att(bc.get_attacker_stats());
00730                         combatant def(bc.get_defender_stats());
00731                         att.fight(def);
00732                         if (def.hp_dist[0] > best_kill_prob) {
00733                             best_kill_prob = def.hp_dist[0];
00734                             best_weapon = i;
00735                             best_def_weapon = bc.get_defender_stats().attack_num;
00736                             best_dir = n;
00737                         }
00738                     }
00739                 }
00740             }
00741         }
00742     }
00743 
00744     if (best_kill_prob > 0.0) {
00745         attack_enemy(loc, adj[best_dir], best_weapon, best_def_weapon);
00746         return true;
00747     }
00748 
00749     double least_hp = u.hitpoints() + 1;
00750 
00751     {
00752         // Who would do most damage to us when they attack?  (approximate: may be different ToD)
00753         for (unsigned int n = 0; n != 6; ++n) {
00754             const unit_map::iterator enemy = find_visible_unit(units_,adj[n], map_, teams_, current_team());
00755             if (enemy != units_.end() &&
00756                current_team().is_enemy(enemy->second.side()) && !enemy->second.incapacitated()) {
00757 
00758                 const std::vector<attack_type>& attacks = units_.find(adj[n])->second.attacks();
00759                 for (unsigned int i = 0; i != attacks.size(); ++i) {
00760                     // SKip weapons with attack_weight=0
00761                     if (attacks[i].attack_weight() > 0) {
00762                         battle_context bc(map_, teams_, units_, state_, adj[n], loc, i);
00763                         combatant att(bc.get_attacker_stats());
00764                         combatant def(bc.get_defender_stats());
00765                         att.fight(def);
00766                         if (def.average_hp() < least_hp) {
00767                             least_hp = def.average_hp();
00768                             best_dir = n;
00769                         }
00770                     }
00771                 }
00772             }
00773         }
00774     }
00775 
00776     // It is possible that there were no adjacent units to attack...
00777     if (least_hp != u.hitpoints() + 1) {
00778         battle_context bc(map_, teams_, units_, state_, loc, adj[best_dir], -1, -1, 0.5);
00779         attack_enemy(loc, adj[best_dir], bc.get_attacker_stats().attack_num,
00780                      bc.get_defender_stats().attack_num);
00781         return true;
00782     }
00783     return false;
00784 }

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