From ba6a7aa2c47b317da959869b4294fb5f5a0955e6 Mon Sep 17 00:00:00 2001 From: Iurii Chernyi Date: Mon, 3 Aug 2009 22:03:13 +0000 Subject: [PATCH] used 'attacks' aspect to keep a cache of attacks... ...shared between ai_default/formula_ai/composite_ai code --- data/ai/utils/default_config.cfg | 11 + projectfiles/CodeBlocks-SCons/wesnoth.cbp | 2 + projectfiles/CodeBlocks/wesnoth.cbp | 2 + projectfiles/VC9/wesnoth.vcproj | 32 ++ src/CMakeLists.txt | 1 + src/Makefile.am | 1 + src/SConscript | 1 + src/ai/actions.cpp | 2 +- src/ai/default/ai.cpp | 17 +- src/ai/default/attack.cpp | 8 +- src/ai/default/contexts.cpp | 318 -------------- src/ai/default/contexts.hpp | 55 +-- src/ai/formula/ai.cpp | 2 +- src/ai/registry.cpp | 10 + src/ai/testing/aspect_attacks.cpp | 488 ++++++++++++++++++++++ src/ai/testing/aspect_attacks.hpp | 82 ++++ src/ai/testing/ca.cpp | 6 +- 17 files changed, 644 insertions(+), 394 deletions(-) create mode 100644 src/ai/testing/aspect_attacks.cpp create mode 100644 src/ai/testing/aspect_attacks.hpp 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)