diff --git a/data/ai/utils/default_config.cfg b/data/ai/utils/default_config.cfg
index 77d3047cb74..308064ac22b 100644
--- a/data/ai/utils/default_config.cfg
+++ b/data/ai/utils/default_config.cfg
@@ -41,6 +41,17 @@
{DEFAULT_ASPECT_VALUE aggression 0.5}
{DEFAULT_ASPECT_VALUE attack_depth 5}
+[aspect]
+ id=attacks
+ engine=cpp
+ name=composite_aspect
+ invalidate_on_gamestate_change=yes
+ [default]
+ engine=cpp
+ name=testing_ai_default::aspect_attacks
+ invalidate_on_gamestate_change=yes
+ [/default]
+[/aspect]
{DEFAULT_ASPECT_EMPTY_SLF avoid}
{DEFAULT_ASPECT_VALUE caution 0.25}
{DEFAULT_ASPECT_VALUE grouping offensive}
diff --git a/projectfiles/CodeBlocks-SCons/wesnoth.cbp b/projectfiles/CodeBlocks-SCons/wesnoth.cbp
index 49283984db2..96c83d61e8c 100644
--- a/projectfiles/CodeBlocks-SCons/wesnoth.cbp
+++ b/projectfiles/CodeBlocks-SCons/wesnoth.cbp
@@ -98,6 +98,8 @@
+
+
diff --git a/projectfiles/CodeBlocks/wesnoth.cbp b/projectfiles/CodeBlocks/wesnoth.cbp
index f61ca72c1cf..a2a02d49a0c 100644
--- a/projectfiles/CodeBlocks/wesnoth.cbp
+++ b/projectfiles/CodeBlocks/wesnoth.cbp
@@ -127,6 +127,8 @@
+
+
diff --git a/projectfiles/VC9/wesnoth.vcproj b/projectfiles/VC9/wesnoth.vcproj
index d602f310e31..1cb003577c8 100644
--- a/projectfiles/VC9/wesnoth.vcproj
+++ b/projectfiles/VC9/wesnoth.vcproj
@@ -4395,6 +4395,34 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -5658,6 +5686,10 @@
+
+
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 0210ebe788a..5d04b72538a 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -245,6 +245,7 @@ SET(wesnoth-main_SRC
ai/interface.cpp
ai/manager.cpp
ai/registry.cpp
+ ai/testing/aspect_attacks.cpp
ai/testing/ca.cpp
ai/testing/stage_rca.cpp
ai/testing/stage_fallback.cpp
diff --git a/src/Makefile.am b/src/Makefile.am
index 67cb1c02760..ec62f5a9e97 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -68,6 +68,7 @@ wesnoth_source = \
ai/interface.cpp \
ai/manager.cpp \
ai/registry.cpp \
+ ai/testing/aspect_attacks.cpp \
ai/testing/ca.cpp \
ai/testing/stage_rca.cpp \
ai/testing/stage_fallback.cpp \
diff --git a/src/SConscript b/src/SConscript
index a05042b8d47..8289deb489e 100644
--- a/src/SConscript
+++ b/src/SConscript
@@ -173,6 +173,7 @@ wesnoth_sources = Split("""
ai/interface.cpp
ai/manager.cpp
ai/registry.cpp
+ ai/testing/aspect_attacks.cpp
ai/testing/ca.cpp
ai/testing/stage_rca.cpp
ai/testing/stage_fallback.cpp
diff --git a/src/ai/actions.cpp b/src/ai/actions.cpp
index 081daefa57a..182848caa25 100644
--- a/src/ai/actions.cpp
+++ b/src/ai/actions.cpp
@@ -316,7 +316,7 @@ void attack_result::do_execute()
check_victory();
set_gamestate_changed();
//start of ugly hack. @todo 1.8 rework that via extended event system
- //until event system is reworked, we note the attack this way
+ //until event system is reworked, we note the attack this way
get_info().recent_attacks.insert(defender_loc_);
//end of ugly hack
try {
diff --git a/src/ai/default/ai.cpp b/src/ai/default/ai.cpp
index 42936f338d3..5b1932e1c7b 100644
--- a/src/ai/default/ai.cpp
+++ b/src/ai/default/ai.cpp
@@ -798,17 +798,10 @@ void ai_default::do_move()
bool ai_default::do_combat(std::map& possible_moves, const move_map& srcdst,
const move_map& dstsrc, const move_map& enemy_srcdst, const move_map& enemy_dstsrc)
{
+
+ const std::vector &analysis = get_attacks();
int ticks = SDL_GetTicks();
- std::vector analysis = analyze_targets(srcdst, dstsrc,
- enemy_srcdst, enemy_dstsrc);
-
- int time_taken = SDL_GetTicks() - ticks;
- LOG_AI << "took " << time_taken << " ticks for " << analysis.size()
- << " positions. Analyzing...\n";
-
- ticks = SDL_GetTicks();
-
const int max_sims = 50000;
int num_sims = analysis.empty() ? 0 : max_sims/analysis.size();
if(num_sims < 20)
@@ -821,9 +814,9 @@ bool ai_default::do_combat(std::map& possible_moves, const m
const int max_positions = 30000;
const int skip_num = analysis.size()/max_positions;
- std::vector::iterator choice_it = analysis.end();
+ std::vector::const_iterator choice_it = analysis.end();
double choice_rating = -1000.0;
- for(std::vector::iterator it = analysis.begin();
+ for(std::vector::const_iterator it = analysis.begin();
it != analysis.end(); ++it) {
if(skip_num > 0 && ((it - analysis.begin())%skip_num) && it->movements.size() > 1)
@@ -849,7 +842,7 @@ bool ai_default::do_combat(std::map& possible_moves, const m
}
}
- time_taken = SDL_GetTicks() - ticks;
+ int time_taken = SDL_GetTicks() - ticks;
LOG_AI << "analysis took " << time_taken << " ticks\n";
// suokko tested the rating against current_team().caution()
diff --git a/src/ai/default/attack.cpp b/src/ai/default/attack.cpp
index e8d79e2889f..8ca3324afa5 100644
--- a/src/ai/default/attack.cpp
+++ b/src/ai/default/attack.cpp
@@ -34,7 +34,7 @@ static lg::log_domain log_ai("ai/attack");
namespace ai {
void attack_analysis::analyze(const gamemap& map, unit_map& units,
- class readonly_context& ai_obj,
+ const readonly_context& ai_obj,
const move_map& dstsrc, const move_map& srcdst,
const move_map& enemy_dstsrc, double aggression)
{
@@ -260,7 +260,7 @@ bool attack_analysis::attack_close(const map_location& loc) const
}
-double attack_analysis::rating(double aggression, readonly_context& ai_obj) const
+double attack_analysis::rating(double aggression, const readonly_context& ai_obj) const
{
if(leader_threat) {
aggression = 1.0;
@@ -292,10 +292,6 @@ double attack_analysis::rating(double aggression, readonly_context& ai_obj) cons
#endif
LOG_AI << "attack option has base value " << value << " with exposure " << exposure << ": "
<< vulnerability << "/" << support << " = " << (vulnerability/std::max(support,0.1)) << "\n";
- if(uses_leader) {
- ai_obj.log_message("attack option has value " + str_cast(value) + " with exposure " + str_cast(exposure) + ": " + str_cast(vulnerability) + "/" + str_cast(support));
- }
-
value -= exposure*(1.0-aggression);
}
diff --git a/src/ai/default/contexts.cpp b/src/ai/default/contexts.cpp
index 55ab057318e..a79064ccdf9 100644
--- a/src/ai/default/contexts.cpp
+++ b/src/ai/default/contexts.cpp
@@ -59,60 +59,6 @@ void default_ai_context_proxy::init_default_ai_context_proxy(default_ai_context
const int max_positions = 10000;
-std::vector default_ai_context_impl::analyze_targets(
- const move_map& srcdst, const move_map& dstsrc,
- const move_map& enemy_srcdst, const move_map& enemy_dstsrc
- )
-{
- log_scope2(log_ai, "analyzing targets...");
-
- std::vector res;
- unit_map units_ = get_info().units;
-
- std::vector unit_locs;
- for(unit_map::const_iterator i = units_.begin(); i != units_.end(); ++i) {
- if(i->second.side() == get_side() && i->second.attacks_left()) {
- unit_locs.push_back(i->first);
- }
- }
-
- bool used_locations[6];
- std::fill(used_locations,used_locations+6,false);
-
- moves_map dummy_moves;
- move_map fullmove_srcdst, fullmove_dstsrc;
- calculate_possible_moves(dummy_moves,fullmove_srcdst,fullmove_dstsrc,false,true);
-
- unit_stats_cache().clear();
-
- for(unit_map::const_iterator j = units_.begin(); j != units_.end(); ++j) {
-
- // Attack anyone who is on the enemy side,
- // and who is not invisible or petrified.
- if(current_team().is_enemy(j->second.side()) && !j->second.incapacitated() &&
- j->second.invisible(j->first,units_,get_info().teams) == false) {
- map_location adjacent[6];
- get_adjacent_tiles(j->first,adjacent);
- attack_analysis analysis;
- analysis.target = j->first;
- analysis.vulnerability = 0.0;
- analysis.support = 0.0;
-
-// const int ticks = SDL_GetTicks();
-
- do_attack_analysis(j->first,srcdst,dstsrc,fullmove_srcdst,fullmove_dstsrc,enemy_srcdst,enemy_dstsrc,
- adjacent,used_locations,unit_locs,res,analysis);
-
-// const int time_taken = SDL_GetTicks() - ticks;
-// static int max_time = 0;
-// if(time_taken > max_time)
-// max_time = time_taken;
- }
- }
-
- return res;
-}
-
default_ai_context_impl::~default_ai_context_impl()
{
@@ -145,270 +91,6 @@ int default_ai_context_impl::count_free_hexes_in_castle(const map_location &loc,
}
-void default_ai_context_impl::do_attack_analysis(
- const map_location& loc,
- const move_map& srcdst, const move_map& dstsrc,
- const move_map& fullmove_srcdst, const move_map& fullmove_dstsrc,
- const move_map& enemy_srcdst, const move_map& enemy_dstsrc,
- const map_location* tiles, bool* used_locations,
- std::vector& units,
- std::vector& result,
- attack_analysis& cur_analysis
- )
-{
- // This function is called fairly frequently, so interact with the user here.
- raise_user_interact();
-
- if(cur_analysis.movements.size() >= size_t(get_attack_depth())) {
- //std::cerr << "ANALYSIS " << cur_analysis.movements.size() << " >= " << get_attack_depth() << "\n";
- return;
- }
- gamemap &map_ = get_info().map;
- unit_map &units_ = get_info().units;
- std::vector &teams_ = get_info().teams;
-
- static double best_results[6];
- if(result.empty()) {
- for(int i = 0; i != 6; ++i) {
- best_results[i] = 0.0;
- }
- }
-
- const size_t max_positions = 1000;
- if(result.size() > max_positions && !cur_analysis.movements.empty()) {
- LOG_AI << "cut analysis short with number of positions\n";
- return;
- }
-
- const double cur_rating = cur_analysis.movements.empty() ? -1.0 :
- cur_analysis.rating(get_aggression(),*this);
-
- double rating_to_beat = cur_rating;
-
- if(!cur_analysis.movements.empty()) {
- assert(cur_analysis.movements.size() < 6);
- double& best_res = best_results[cur_analysis.movements.size()-1];
- rating_to_beat = best_res = std::max(best_res,cur_rating);
- }
-
- for(size_t i = 0; i != units.size(); ++i) {
- const map_location current_unit = units[i];
-
- unit_map::iterator unit_itor = units_.find(current_unit);
- assert(unit_itor != units_.end());
-
- // See if the unit has the backstab ability.
- // Units with backstab will want to try to have a
- // friendly unit opposite the position they move to.
- //
- // See if the unit has the slow ability -- units with slow only attack first.
- bool backstab = false, slow = false;
- std::vector& attacks = unit_itor->second.attacks();
- for(std::vector::iterator a = attacks.begin(); a != attacks.end(); ++a) {
- a->set_specials_context(map_location(), map_location(), units_, true, NULL);
- if(a->get_special_bool("backstab")) {
- backstab = true;
- }
-
- if(a->get_special_bool("slow")) {
- slow = true;
- }
- }
-
- if(slow && cur_analysis.movements.empty() == false) {
- continue;
- }
-
- // Check if the friendly unit is surrounded,
- // A unit is surrounded if it is flanked by enemy units
- // and at least one other enemy unit is nearby
- // or if the unit is totaly surrounded by enemies
- // with max. one tile to escape.
- bool is_surrounded = false;
- bool is_flanked = false;
- int enemy_units_around = 0;
- int accessible_tiles = 0;
- map_location adj[6];
- get_adjacent_tiles(current_unit, adj);
-
- size_t tile;
- for(tile = 0; tile != 3; ++tile) {
-
- const unit_map::const_iterator tmp_unit = units_.find(adj[tile]);
- bool possible_flanked = false;
-
- if(map_.on_board(adj[tile]))
- {
- accessible_tiles++;
- if(tmp_unit != units_.end() && get_side() != tmp_unit->second.side())
- {
- enemy_units_around++;
- possible_flanked = true;
- }
- }
-
- const unit_map::const_iterator tmp_opposite_unit = units_.find(adj[tile + 3]);
- if(map_.on_board(adj[tile + 3]))
- {
- accessible_tiles++;
- if(tmp_opposite_unit != units_.end() && get_side() != tmp_opposite_unit->second.side())
- {
- enemy_units_around++;
- if(possible_flanked)
- {
- is_flanked = true;
- }
- }
- }
- }
-
- if((is_flanked && enemy_units_around > 2) || enemy_units_around >= accessible_tiles - 1)
- is_surrounded = true;
-
-
-
- double best_vulnerability = 0.0, best_support = 0.0;
- int best_rating = 0;
- int cur_position = -1;
-
- // Iterate over positions adjacent to the unit, finding the best rated one.
- for(int j = 0; j != 6; ++j) {
-
- // If in this planned attack, a unit is already in this location.
- if(used_locations[j]) {
- continue;
- }
-
- // See if the current unit can reach that position.
- if (tiles[j] != current_unit) {
- typedef std::multimap::const_iterator Itor;
- std::pair its = dstsrc.equal_range(tiles[j]);
- while(its.first != its.second) {
- if(its.first->second == current_unit)
- break;
- ++its.first;
- }
-
- // If the unit can't move to this location.
- if(its.first == its.second || units_.find(tiles[j]) != units_.end()) {
- continue;
- }
- }
-
- unit_ability_list abil = unit_itor->second.get_abilities("leadership",tiles[j]);
- int best_leadership_bonus = abil.highest("value").first;
- double leadership_bonus = static_cast(best_leadership_bonus+100)/100.0;
- if (leadership_bonus > 1.1) {
- LOG_AI << unit_itor->second.name() << " is getting leadership " << leadership_bonus << "\n";
- }
-
- // Check to see whether this move would be a backstab.
- int backstab_bonus = 1;
- double surround_bonus = 1.0;
-
- if(tiles[(j+3)%6] != current_unit) {
- const unit_map::const_iterator itor = units_.find(tiles[(j+3)%6]);
-
- // Note that we *could* also check if a unit plans to move there
- // before we're at this stage, but we don't because, since the
- // attack calculations don't actually take backstab into account (too complicated),
- // this could actually make our analysis look *worse* instead of better.
- // So we only check for 'concrete' backstab opportunities.
- // That would also break backstab_check, since it assumes
- // the defender is in place.
- if(itor != units_.end() &&
- backstab_check(tiles[j], loc, units_, teams_)) {
- if(backstab) {
- backstab_bonus = 2;
- }
-
- // No surround bonus if target is skirmisher
- if (!itor->second.get_ability_bool("skirmisker"))
- surround_bonus = 1.2;
- }
-
-
- }
-
- // See if this position is the best rated we've seen so far.
- const int rating = static_cast(rate_terrain(unit_itor->second,tiles[j]) * backstab_bonus * leadership_bonus);
- if(cur_position >= 0 && rating < best_rating) {
- continue;
- }
-
- // Find out how vulnerable we are to attack from enemy units in this hex.
- //FIXME: suokko's r29531 multiplied this by a constant 1.5. ?
- const double vulnerability = power_projection(tiles[j],enemy_dstsrc);
-
- // Calculate how much support we have on this hex from allies.
- const double support = power_projection(tiles[j], fullmove_dstsrc);
-
- // If this is a position with equal defense to another position,
- // but more vulnerability then we don't want to use it.
-#ifdef SUOKKO
- //FIXME: this code was in sukko's r29531 Correct?
- // scale vulnerability to 60 hp unit
- if(cur_position >= 0 && rating < best_rating
- && (vulnerability/surround_bonus*30.0)/unit_itor->second.hitpoints() -
- (support*surround_bonus*30.0)/unit_itor->second.max_hitpoints()
- > best_vulnerability - best_support) {
- continue;
- }
-#else
- if(cur_position >= 0 && rating == best_rating && vulnerability/surround_bonus - support*surround_bonus >= best_vulnerability - best_support) {
- continue;
- }
-#endif
- cur_position = j;
- best_rating = rating;
-#ifdef SUOKKO
- //FIXME: this code was in sukko's r29531 Correct?
- best_vulnerability = (vulnerability/surround_bonus*30.0)/unit_itor->second.hitpoints();
- best_support = (support*surround_bonus*30.0)/unit_itor->second.max_hitpoints();
-#else
- best_vulnerability = vulnerability/surround_bonus;
- best_support = support*surround_bonus;
-#endif
- }
-
- if(cur_position != -1) {
- units.erase(units.begin() + i);
-
- cur_analysis.movements.push_back(std::pair(current_unit,tiles[cur_position]));
-
- cur_analysis.vulnerability += best_vulnerability;
-
- cur_analysis.support += best_support;
-
- cur_analysis.is_surrounded = is_surrounded;
-
- cur_analysis.analyze(map_, units_, *this, dstsrc, srcdst, enemy_dstsrc, get_aggression());
-
- //This logic to sometimes not add the attack because it doesn't
- //rate high enough seems to remove attacks from consideration
- //that should not be removed, so it has been removed.
- // -- David.
-// if(cur_analysis.rating(get_aggression(),*this) > rating_to_beat) {
-
- result.push_back(cur_analysis);
- used_locations[cur_position] = true;
- do_attack_analysis(loc,srcdst,dstsrc,fullmove_srcdst,fullmove_dstsrc,enemy_srcdst,enemy_dstsrc,
- tiles,used_locations,
- units,result,cur_analysis);
- used_locations[cur_position] = false;
-// }
-
- cur_analysis.vulnerability -= best_vulnerability;
- cur_analysis.support -= best_support;
-
- cur_analysis.movements.pop_back();
-
- units.insert(units.begin() + i, current_unit);
- }
- }
-}
-
-
default_ai_context& default_ai_context_impl::get_default_ai_context(){
return *this;
}
diff --git a/src/ai/default/contexts.hpp b/src/ai/default/contexts.hpp
index 4083b5d9a02..ad6a1815650 100644
--- a/src/ai/default/contexts.hpp
+++ b/src/ai/default/contexts.hpp
@@ -59,11 +59,11 @@ public:
}
void analyze(const gamemap& map, unit_map& units,
- class readonly_context& ai_obj,
+ const readonly_context& ai_obj,
const move_map& dstsrc, const move_map& srcdst,
const move_map& enemy_dstsrc, double aggression);
- double rating(double aggression, class readonly_context& ai_obj) const;
+ double rating(double aggression, const readonly_context& ai_obj) const;
variant get_value(const std::string& key) const;
void get_inputs(std::vector* inputs) const;
@@ -124,12 +124,6 @@ class default_ai_context;
class default_ai_context : public virtual readwrite_context{
public:
- /** Return a vector of all possible attack analysisis */
- virtual std::vector analyze_targets(
- const move_map& srcdst, const move_map& dstsrc,
- const move_map& enemy_srcdst, const move_map& enemy_dstsrc) = 0;
-
-
virtual int count_free_hexes_in_castle(const map_location& loc, std::set &checked_hexes) = 0;
@@ -141,16 +135,6 @@ public:
virtual ~default_ai_context();
- /** Analyze possibility of attacking target on 'loc'. */
- virtual void do_attack_analysis(
- const map_location& loc, const move_map& srcdst, const move_map& dstsrc,
- const move_map& fullmove_srcdst, const move_map& fullmove_dstsrc,
- const move_map& enemy_srcdst, const move_map& enemy_dstsrc,
- const map_location* tiles, bool* used_locations,
- std::vector& units,
- std::vector& result, attack_analysis& cur_analysis) = 0;
-
-
virtual default_ai_context& get_default_ai_context() = 0;
@@ -171,14 +155,6 @@ public:
class default_ai_context_proxy : public virtual default_ai_context, public virtual readwrite_context_proxy {
public:
- virtual std::vector analyze_targets(
- const move_map& srcdst, const move_map& dstsrc,
- const move_map& enemy_srcdst, const move_map& enemy_dstsrc)
- {
- return target_->analyze_targets(srcdst,dstsrc,enemy_srcdst,enemy_dstsrc);
- }
-
-
int count_free_hexes_in_castle(const map_location& loc, std::set &checked_hexes)
{
return target_->count_free_hexes_in_castle(loc, checked_hexes);
@@ -194,19 +170,6 @@ public:
virtual ~default_ai_context_proxy();
-
- virtual void do_attack_analysis(
- const map_location& loc, const move_map& srcdst, const move_map& dstsrc,
- const move_map& fullmove_srcdst, const move_map& fullmove_dstsrc,
- const move_map& enemy_srcdst, const move_map& enemy_dstsrc,
- const map_location* tiles, bool* used_locations,
- std::vector& units,
- std::vector& result, attack_analysis& cur_analysis)
- {
- target_->do_attack_analysis(loc,srcdst,dstsrc,fullmove_srcdst,fullmove_dstsrc,enemy_srcdst,enemy_dstsrc,tiles,used_locations,units,result,cur_analysis);
- }
-
-
virtual default_ai_context& get_default_ai_context()
{
return target_->get_default_ai_context();
@@ -244,11 +207,6 @@ private:
class default_ai_context_impl : public virtual readwrite_context_proxy, public default_ai_context {
public:
- virtual std::vector analyze_targets(
- const move_map& srcdst, const move_map& dstsrc,
- const move_map& enemy_srcdst, const move_map& enemy_dstsrc);
-
-
int count_free_hexes_in_castle(const map_location& loc, std::set &checked_hexes);
@@ -262,15 +220,6 @@ public:
virtual ~default_ai_context_impl();
- virtual void do_attack_analysis(
- const map_location& loc, const move_map& srcdst, const move_map& dstsrc,
- const move_map& fullmove_srcdst, const move_map& fullmove_dstsrc,
- const move_map& enemy_srcdst, const move_map& enemy_dstsrc,
- const map_location* tiles, bool* used_locations,
- std::vector& units,
- std::vector& result, attack_analysis& cur_analysis);
-
-
virtual default_ai_context& get_default_ai_context();
diff --git a/src/ai/formula/ai.cpp b/src/ai/formula/ai.cpp
index 658242dc332..00f2055dfc6 100644
--- a/src/ai/formula/ai.cpp
+++ b/src/ai/formula/ai.cpp
@@ -789,7 +789,7 @@ variant formula_ai::get_value(const std::string& key) const
return attacks_cache_;
}
- std::vector attacks = const_cast(this)->analyze_targets(srcdst_, dstsrc_, enemy_srcdst_, enemy_dstsrc_);
+ const std::vector attacks = get_attacks();
std::vector vars;
for(std::vector::const_iterator i = attacks.begin(); i != attacks.end(); ++i) {
vars.push_back(variant(new attack_analysis(*i)));
diff --git a/src/ai/registry.cpp b/src/ai/registry.cpp
index f93eab52afd..cc777f209f1 100644
--- a/src/ai/registry.cpp
+++ b/src/ai/registry.cpp
@@ -26,6 +26,7 @@
#include "dfool/ai.hpp"
#include "formula/ai.hpp"
#include "registry.hpp"
+#include "testing/aspect_attacks.hpp"
#include "testing/ca.hpp"
#include "testing/stage_rca.hpp"
#include "testing/stage_fallback.hpp"
@@ -108,6 +109,9 @@ static register_aspect_factory< composite_aspect >
static register_aspect_factory< composite_aspect >
attack_depth__composite_aspect_factory("attack_depth*composite_aspect");
+static register_aspect_factory< composite_aspect< attacks_vector > >
+ attacks__composite_aspect_factory("attacks*composite_aspect");
+
static register_aspect_factory< composite_aspect< terrain_filter > >
avoid__composite_aspect_factory("avoid*composite_aspect");
@@ -164,6 +168,9 @@ static register_aspect_factory< standard_aspect >
static register_aspect_factory< standard_aspect >
attack_depth__standard_aspect_factory("attack_depth*standard_aspect");
+static register_aspect_factory< testing_ai_default::aspect_attacks >
+ attacks__testing_ai_default_aspect_attacks_factory("attacks*testing_ai_default::aspect_attacks");
+
static register_aspect_factory< standard_aspect< terrain_filter > >
avoid__standard_aspect_factory("avoid*standard_aspect");
@@ -220,6 +227,9 @@ static register_aspect_factory< standard_aspect >
static register_aspect_factory< standard_aspect >
attack_depth__standard_aspect_factory2("attack_depth*");
+static register_aspect_factory< testing_ai_default::aspect_attacks >
+ attacks__testing_ai_default_aspect_attacks_factory2("attacks*");
+
static register_aspect_factory< standard_aspect< terrain_filter > >
avoid__standard_aspect_factory2("avoid*");
diff --git a/src/ai/testing/aspect_attacks.cpp b/src/ai/testing/aspect_attacks.cpp
new file mode 100644
index 00000000000..ae0f13010b0
--- /dev/null
+++ b/src/ai/testing/aspect_attacks.cpp
@@ -0,0 +1,488 @@
+/* $Id$ */
+/*
+ Copyright (C) 2009 by Yurii Chernyi
+ Part of the Battle for Wesnoth Project http://www.wesnoth.org/
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2
+ or at your option any later version.
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY.
+
+ See the COPYING file for more details.
+*/
+
+/**
+ * Stage: fallback to other AI
+ * @file ai/testing/aspect_attacks.cpp
+ */
+
+#include "aspect_attacks.hpp"
+
+#include "../manager.hpp"
+#include "../../foreach.hpp"
+#include "../../log.hpp"
+
+namespace ai {
+
+namespace testing_ai_default {
+
+static lg::log_domain log_ai_testing_aspect_attacks("ai/aspect/attacks");
+#define DBG_AI LOG_STREAM(debug, log_ai_testing_aspect_attacks)
+#define LOG_AI LOG_STREAM(info, log_ai_testing_aspect_attacks)
+#define ERR_AI LOG_STREAM(err, log_ai_testing_aspect_attacks)
+
+aspect_attacks::aspect_attacks(readonly_context &context, const config &cfg, const std::string &id)
+ : typesafe_aspect(context,cfg,id)
+{
+}
+
+aspect_attacks::~aspect_attacks()
+{
+}
+
+void aspect_attacks::recalculate() const
+{
+ this->value_ = analyze_targets();
+}
+
+boost::shared_ptr aspect_attacks::analyze_targets() const
+{
+ const move_map& srcdst = get_srcdst();
+ const move_map& dstsrc = get_dstsrc();
+ const move_map& enemy_srcdst = get_enemy_srcdst();
+ const move_map& enemy_dstsrc = get_enemy_dstsrc();
+
+ boost::shared_ptr res(new attacks_vector());
+ unit_map& units_ = get_info().units;
+
+ std::vector unit_locs;
+ for(unit_map::const_iterator i = units_.begin(); i != units_.end(); ++i) {
+ if(i->second.side() == get_side() && i->second.attacks_left()) {
+ unit_locs.push_back(i->first);
+ }
+ }
+
+ bool used_locations[6];
+ std::fill(used_locations,used_locations+6,false);
+
+ moves_map dummy_moves;
+ move_map fullmove_srcdst, fullmove_dstsrc;
+ calculate_possible_moves(dummy_moves,fullmove_srcdst,fullmove_dstsrc,false,true);
+
+ unit_stats_cache().clear();
+
+ for(unit_map::const_iterator j = units_.begin(); j != units_.end(); ++j) {
+
+ // Attack anyone who is on the enemy side,
+ // and who is not invisible or petrified.
+ if(current_team().is_enemy(j->second.side()) && !j->second.incapacitated() &&
+ j->second.invisible(j->first,units_,get_info().teams) == false) {
+ map_location adjacent[6];
+ get_adjacent_tiles(j->first,adjacent);
+ attack_analysis analysis;
+ analysis.target = j->first;
+ analysis.vulnerability = 0.0;
+ analysis.support = 0.0;
+ do_attack_analysis(j->first,srcdst,dstsrc,fullmove_srcdst,fullmove_dstsrc,enemy_srcdst,enemy_dstsrc,
+ adjacent,used_locations,unit_locs,*res,analysis);
+ }
+ }
+ return res;
+}
+
+void aspect_attacks::do_attack_analysis(
+ const map_location& loc,
+ const move_map& srcdst, const move_map& dstsrc,
+ const move_map& fullmove_srcdst, const move_map& fullmove_dstsrc,
+ const move_map& enemy_srcdst, const move_map& enemy_dstsrc,
+ const map_location* tiles, bool* used_locations,
+ std::vector& units,
+ std::vector& result,
+ attack_analysis& cur_analysis
+ ) const
+{
+ // This function is called fairly frequently, so interact with the user here.
+ raise_user_interact();
+
+ if(cur_analysis.movements.size() >= size_t(get_attack_depth())) {
+ //std::cerr << "ANALYSIS " << cur_analysis.movements.size() << " >= " << get_attack_depth() << "\n";
+ return;
+ }
+ gamemap &map_ = get_info().map;
+ unit_map &units_ = get_info().units;
+ std::vector &teams_ = get_info().teams;
+
+ static double best_results[6];
+ if(result.empty()) {
+ for(int i = 0; i != 6; ++i) {
+ best_results[i] = 0.0;
+ }
+ }
+
+ const size_t max_positions = 1000;
+ if(result.size() > max_positions && !cur_analysis.movements.empty()) {
+ LOG_AI << "cut analysis short with number of positions\n";
+ return;
+ }
+
+ const double cur_rating = cur_analysis.movements.empty() ? -1.0 :
+ cur_analysis.rating(get_aggression(),*this);
+
+ double rating_to_beat = cur_rating;
+
+ if(!cur_analysis.movements.empty()) {
+ assert(cur_analysis.movements.size() < 6);
+ double& best_res = best_results[cur_analysis.movements.size()-1];
+ rating_to_beat = best_res = std::max(best_res,cur_rating);
+ }
+
+ for(size_t i = 0; i != units.size(); ++i) {
+ const map_location current_unit = units[i];
+
+ unit_map::iterator unit_itor = units_.find(current_unit);
+ assert(unit_itor != units_.end());
+
+ // See if the unit has the backstab ability.
+ // Units with backstab will want to try to have a
+ // friendly unit opposite the position they move to.
+ //
+ // See if the unit has the slow ability -- units with slow only attack first.
+ bool backstab = false, slow = false;
+ std::vector& attacks = unit_itor->second.attacks();
+ for(std::vector::iterator a = attacks.begin(); a != attacks.end(); ++a) {
+ a->set_specials_context(map_location(), map_location(), units_, true, NULL);
+ if(a->get_special_bool("backstab")) {
+ backstab = true;
+ }
+
+ if(a->get_special_bool("slow")) {
+ slow = true;
+ }
+ }
+
+ if(slow && cur_analysis.movements.empty() == false) {
+ continue;
+ }
+
+ // Check if the friendly unit is surrounded,
+ // A unit is surrounded if it is flanked by enemy units
+ // and at least one other enemy unit is nearby
+ // or if the unit is totaly surrounded by enemies
+ // with max. one tile to escape.
+ bool is_surrounded = false;
+ bool is_flanked = false;
+ int enemy_units_around = 0;
+ int accessible_tiles = 0;
+ map_location adj[6];
+ get_adjacent_tiles(current_unit, adj);
+
+ size_t tile;
+ for(tile = 0; tile != 3; ++tile) {
+
+ const unit_map::const_iterator tmp_unit = units_.find(adj[tile]);
+ bool possible_flanked = false;
+
+ if(map_.on_board(adj[tile]))
+ {
+ accessible_tiles++;
+ if(tmp_unit != units_.end() && get_side() != tmp_unit->second.side())
+ {
+ enemy_units_around++;
+ possible_flanked = true;
+ }
+ }
+
+ const unit_map::const_iterator tmp_opposite_unit = units_.find(adj[tile + 3]);
+ if(map_.on_board(adj[tile + 3]))
+ {
+ accessible_tiles++;
+ if(tmp_opposite_unit != units_.end() && get_side() != tmp_opposite_unit->second.side())
+ {
+ enemy_units_around++;
+ if(possible_flanked)
+ {
+ is_flanked = true;
+ }
+ }
+ }
+ }
+
+ if((is_flanked && enemy_units_around > 2) || enemy_units_around >= accessible_tiles - 1)
+ is_surrounded = true;
+
+
+
+ double best_vulnerability = 0.0, best_support = 0.0;
+ int best_rating = 0;
+ int cur_position = -1;
+
+ // Iterate over positions adjacent to the unit, finding the best rated one.
+ for(int j = 0; j != 6; ++j) {
+
+ // If in this planned attack, a unit is already in this location.
+ if(used_locations[j]) {
+ continue;
+ }
+
+ // See if the current unit can reach that position.
+ if (tiles[j] != current_unit) {
+ typedef std::multimap::const_iterator Itor;
+ std::pair its = dstsrc.equal_range(tiles[j]);
+ while(its.first != its.second) {
+ if(its.first->second == current_unit)
+ break;
+ ++its.first;
+ }
+
+ // If the unit can't move to this location.
+ if(its.first == its.second || units_.find(tiles[j]) != units_.end()) {
+ continue;
+ }
+ }
+
+ unit_ability_list abil = unit_itor->second.get_abilities("leadership",tiles[j]);
+ int best_leadership_bonus = abil.highest("value").first;
+ double leadership_bonus = static_cast(best_leadership_bonus+100)/100.0;
+ if (leadership_bonus > 1.1) {
+ LOG_AI << unit_itor->second.name() << " is getting leadership " << leadership_bonus << "\n";
+ }
+
+ // Check to see whether this move would be a backstab.
+ int backstab_bonus = 1;
+ double surround_bonus = 1.0;
+
+ if(tiles[(j+3)%6] != current_unit) {
+ const unit_map::const_iterator itor = units_.find(tiles[(j+3)%6]);
+
+ // Note that we *could* also check if a unit plans to move there
+ // before we're at this stage, but we don't because, since the
+ // attack calculations don't actually take backstab into account (too complicated),
+ // this could actually make our analysis look *worse* instead of better.
+ // So we only check for 'concrete' backstab opportunities.
+ // That would also break backstab_check, since it assumes
+ // the defender is in place.
+ if(itor != units_.end() &&
+ backstab_check(tiles[j], loc, units_, teams_)) {
+ if(backstab) {
+ backstab_bonus = 2;
+ }
+
+ // No surround bonus if target is skirmisher
+ if (!itor->second.get_ability_bool("skirmisker"))
+ surround_bonus = 1.2;
+ }
+
+
+ }
+
+ // See if this position is the best rated we've seen so far.
+ const int rating = static_cast(rate_terrain(unit_itor->second,tiles[j]) * backstab_bonus * leadership_bonus);
+ if(cur_position >= 0 && rating < best_rating) {
+ continue;
+ }
+
+ // Find out how vulnerable we are to attack from enemy units in this hex.
+ //FIXME: suokko's r29531 multiplied this by a constant 1.5. ?
+ const double vulnerability = power_projection(tiles[j],enemy_dstsrc);//?
+
+ // Calculate how much support we have on this hex from allies.
+ const double support = power_projection(tiles[j], fullmove_dstsrc);//?
+
+ // If this is a position with equal defense to another position,
+ // but more vulnerability then we don't want to use it.
+#ifdef SUOKKO
+ //FIXME: this code was in sukko's r29531 Correct?
+ // scale vulnerability to 60 hp unit
+ if(cur_position >= 0 && rating < best_rating
+ && (vulnerability/surround_bonus*30.0)/unit_itor->second.hitpoints() -
+ (support*surround_bonus*30.0)/unit_itor->second.max_hitpoints()
+ > best_vulnerability - best_support) {
+ continue;
+ }
+#else
+ if(cur_position >= 0 && rating == best_rating && vulnerability/surround_bonus - support*surround_bonus >= best_vulnerability - best_support) {
+ continue;
+ }
+#endif
+ cur_position = j;
+ best_rating = rating;
+#ifdef SUOKKO
+ //FIXME: this code was in sukko's r29531 Correct?
+ best_vulnerability = (vulnerability/surround_bonus*30.0)/unit_itor->second.hitpoints();
+ best_support = (support*surround_bonus*30.0)/unit_itor->second.max_hitpoints();
+#else
+ best_vulnerability = vulnerability/surround_bonus;
+ best_support = support*surround_bonus;
+#endif
+ }
+
+ if(cur_position != -1) {
+ units.erase(units.begin() + i);
+
+ cur_analysis.movements.push_back(std::pair(current_unit,tiles[cur_position]));
+
+ cur_analysis.vulnerability += best_vulnerability;
+
+ cur_analysis.support += best_support;
+
+ cur_analysis.is_surrounded = is_surrounded;
+
+ cur_analysis.analyze(map_, units_, *this, dstsrc, srcdst, enemy_dstsrc, get_aggression());
+ result.push_back(cur_analysis);
+ used_locations[cur_position] = true;
+ do_attack_analysis(loc,srcdst,dstsrc,fullmove_srcdst,fullmove_dstsrc,enemy_srcdst,enemy_dstsrc,
+ tiles,used_locations,
+ units,result,cur_analysis);
+ used_locations[cur_position] = false;
+
+
+ cur_analysis.vulnerability -= best_vulnerability;
+ cur_analysis.support -= best_support;
+
+ cur_analysis.movements.pop_back();
+
+ units.insert(units.begin() + i, current_unit);
+ }
+ }
+}
+
+int aspect_attacks::rate_terrain(const unit& u, const map_location& loc) const
+{
+ gamemap &map_ = get_info().map;
+ const t_translation::t_terrain terrain = map_.get_terrain(loc);
+ const int defense = u.defense_modifier(terrain);
+ int rating = 100 - defense;
+
+ const int healing_value = 10;
+ const int friendly_village_value = 5;
+ const int neutral_village_value = 10;
+ const int enemy_village_value = 15;
+
+ if(map_.gives_healing(terrain) && u.get_ability_bool("regenerates",loc) == false) {
+ rating += healing_value;
+ }
+
+ if(map_.is_village(terrain)) {
+ int owner = village_owner(loc, get_info().teams) + 1;
+
+ if(owner == get_side()) {
+ rating += friendly_village_value;
+ } else if(owner == 0) {
+ rating += neutral_village_value;
+ } else {
+ rating += enemy_village_value;
+ }
+ }
+
+ return rating;
+}
+
+
+double aspect_attacks::power_projection(const map_location& loc, const move_map& dstsrc) const
+{
+ map_location used_locs[6];
+ int ratings[6];
+ int num_used_locs = 0;
+
+ map_location locs[6];
+ get_adjacent_tiles(loc,locs);
+
+ const int lawful_bonus = get_info().tod_manager_.get_time_of_day().lawful_bonus;
+ gamemap& map_ = get_info().map;
+ unit_map& units_ = get_info().units;
+
+ int res = 0;
+
+ bool changed = false;
+ for (int i = 0;; ++i) {
+ if (i == 6) {
+ if (!changed) break;
+ // Loop once again, in case a unit found a better spot
+ // and freed the place for another unit.
+ changed = false;
+ i = 0;
+ }
+
+ if (map_.on_board(locs[i]) == false) {
+ continue;
+ }
+
+ const t_translation::t_terrain terrain = map_[locs[i]];
+
+ typedef move_map::const_iterator Itor;
+ typedef std::pair Range;
+ Range its = dstsrc.equal_range(locs[i]);
+
+ map_location* const beg_used = used_locs;
+ map_location* end_used = used_locs + num_used_locs;
+
+ int best_rating = 0;
+ map_location best_unit;
+
+ for(Itor it = its.first; it != its.second; ++it) {
+ const unit_map::const_iterator u = units_.find(it->second);
+
+ // Unit might have been killed, and no longer exist
+ if(u == units_.end()) {
+ continue;
+ }
+
+ const unit& un = u->second;
+
+ int tod_modifier = 0;
+ if(un.alignment() == unit_type::LAWFUL) {
+ tod_modifier = lawful_bonus;
+ } else if(un.alignment() == unit_type::CHAOTIC) {
+ tod_modifier = -lawful_bonus;
+ }
+
+ // The 0.5 power avoids underestimating too much the damage of a wounded unit.
+ int hp = int(sqrt(double(un.hitpoints()) / un.max_hitpoints()) * 1000);
+ int most_damage = 0;
+ foreach (const attack_type &att, un.attacks())
+ {
+ int damage = att.damage() * att.num_attacks() * (100 + tod_modifier);
+ if (damage > most_damage) {
+ most_damage = damage;
+ }
+ }
+
+ int village_bonus = map_.is_village(terrain) ? 3 : 2;
+ int defense = 100 - un.defense_modifier(terrain);
+ int rating = hp * defense * most_damage * village_bonus / 200;
+ if(rating > best_rating) {
+ map_location *pos = std::find(beg_used, end_used, it->second);
+ // Check if the spot is the same or better than an older one.
+ if (pos == end_used || rating >= ratings[pos - beg_used]) {
+ best_rating = rating;
+ best_unit = it->second;
+ }
+ }
+ }
+
+ if (!best_unit.valid()) continue;
+ map_location *pos = std::find(beg_used, end_used, best_unit);
+ int index = pos - beg_used;
+ if (index == num_used_locs)
+ ++num_used_locs;
+ else if (best_rating == ratings[index])
+ continue;
+ else {
+ // The unit was in another spot already, so remove its older rating
+ // from the final result, and require a new run to fill its old spot.
+ res -= ratings[index];
+ changed = true;
+ }
+ used_locs[index] = best_unit;
+ ratings[index] = best_rating;
+ res += best_rating;
+ }
+
+ return res / 100000.;
+}
+
+
+} // end of namespace testing_ai_default
+
+} // end of namespace ai
diff --git a/src/ai/testing/aspect_attacks.hpp b/src/ai/testing/aspect_attacks.hpp
new file mode 100644
index 00000000000..71a6ccfb04b
--- /dev/null
+++ b/src/ai/testing/aspect_attacks.hpp
@@ -0,0 +1,82 @@
+/* $Id$ */
+/*
+ Copyright (C) 2009 by Yurii Chernyi
+ Part of the Battle for Wesnoth Project http://www.wesnoth.org/
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2
+ or at your option any later version.
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY.
+
+ See the COPYING file for more details.
+*/
+
+/**
+ * Aspect: attacks
+ * @file ai/testing/aspect_attacks.hpp
+ */
+
+#ifndef AI_TESTING_ASPECT_ATTACKS_HPP_INCLUDED
+#define AI_TESTING_ASPECT_ATTACKS_HPP_INCLUDED
+
+#include "../../global.hpp"
+
+#include "../composite/aspect.hpp"
+#include "../interface.hpp"
+#include "../../config.hpp"
+
+#include
+#include
+
+#ifdef _MSC_VER
+#pragma warning(push)
+//silence "inherits via dominance" warnings
+#pragma warning(disable:4250)
+#endif
+
+namespace ai {
+
+
+namespace testing_ai_default {
+
+class aspect_attacks: public typesafe_aspect {
+public:
+ aspect_attacks(readonly_context &context, const config &cfg, const std::string &id);
+
+
+ virtual ~aspect_attacks();
+
+
+ virtual void recalculate() const;
+
+
+protected:
+ boost::shared_ptr analyze_targets() const;
+
+ void do_attack_analysis(
+ const map_location& loc,
+ const move_map& srcdst, const move_map& dstsrc,
+ const move_map& fullmove_srcdst, const move_map& fullmove_dstsrc,
+ const move_map& enemy_srcdst, const move_map& enemy_dstsrc,
+ const map_location* tiles, bool* used_locations,
+ std::vector& units,
+ std::vector& result,
+ attack_analysis& cur_analysis
+ ) const;
+
+ int rate_terrain(const unit& u, const map_location& loc) const;
+ double power_projection(const map_location& loc, const move_map& dstsrc) const;
+};
+
+
+
+} // end of namespace testing_ai_default
+
+} // end of namespace ai
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+#endif
diff --git a/src/ai/testing/ca.cpp b/src/ai/testing/ca.cpp
index af737b05962..57edf03f16a 100644
--- a/src/ai/testing/ca.cpp
+++ b/src/ai/testing/ca.cpp
@@ -559,7 +559,7 @@ double combat_phase::evaluate()
choice_rating_ = -1000.0;
int ticks = SDL_GetTicks();
- std::vector analysis = analyze_targets(get_srcdst(), get_dstsrc(), get_enemy_srcdst(), get_enemy_dstsrc());
+ const std::vector analysis = get_attacks();
int time_taken = SDL_GetTicks() - ticks;
LOG_AI_TESTING_AI_DEFAULT << "took " << time_taken << " ticks for " << analysis.size()
@@ -579,8 +579,8 @@ double combat_phase::evaluate()
const int max_positions = 30000;
const int skip_num = analysis.size()/max_positions;
- std::vector::iterator choice_it = analysis.end();
- for(std::vector::iterator it = analysis.begin();
+ std::vector::const_iterator choice_it = analysis.end();
+ for(std::vector::const_iterator it = analysis.begin();
it != analysis.end(); ++it) {
if(skip_num > 0 && ((it - analysis.begin())%skip_num) && it->movements.size() > 1)