From ca8cb3bd6a45d4fd2da9de79d484d9ad16f6951d Mon Sep 17 00:00:00 2001 From: Celtic Minstrel Date: Mon, 29 Feb 2016 18:38:46 -0500 Subject: [PATCH] Remove the deprecated recruitment stage, candidate action, and aspect --- data/ai/utils/default_config.cfg | 13 - src/ai/composite/stage.cpp | 37 -- src/ai/composite/stage.hpp | 18 +- src/ai/composite/value_translator.hpp | 55 -- src/ai/contexts.cpp | 13 - src/ai/contexts.hpp | 14 - src/ai/default/ai.cpp | 793 -------------------------- src/ai/default/ai.hpp | 101 ---- src/ai/formula/ai.cpp | 7 +- src/ai/game_info.hpp | 2 - src/ai/registry.cpp | 24 - src/ai/testing/ca.cpp | 399 ------------- src/ai/testing/ca.hpp | 61 -- 13 files changed, 3 insertions(+), 1534 deletions(-) diff --git a/data/ai/utils/default_config.cfg b/data/ai/utils/default_config.cfg index b1de3d886fee..27c5f97cebd0 100644 --- a/data/ai/utils/default_config.cfg +++ b/data/ai/utils/default_config.cfg @@ -64,19 +64,6 @@ {DEFAULT_ASPECT_VALUE number_of_possible_recruits_to_force_recruit 3.1} {DEFAULT_ASPECT_VALUE passive_leader no} {DEFAULT_ASPECT_VALUE passive_leader_shares_keep no} -[aspect] - id=recruitment - engine=cpp - name=composite_aspect - [default] - engine=cpp - name=standard_aspect - [value] - engine=cpp - name=ai_default::recruitment - [/value] - [/default] -[/aspect] {DEFAULT_ASPECT_VALUE recruitment_diversity 2.0} {DEFAULT_ASPECT_VALUE recruitment_ignore_bad_combat no} {DEFAULT_ASPECT_VALUE recruitment_ignore_bad_movement no} diff --git a/src/ai/composite/stage.cpp b/src/ai/composite/stage.cpp index 1c9d89a918fb..89211a521b29 100644 --- a/src/ai/composite/stage.cpp +++ b/src/ai/composite/stage.cpp @@ -109,43 +109,6 @@ bool idle_stage::do_play_stage(){ return false; } -// ======================================================================= -// COMPOSITE AI MINISTAGE -// ======================================================================= - -ministage::ministage(const config &cfg) - : cfg_(cfg),stage_() -{ -} - -ministage::~ministage() -{ -} - - -stage_ptr ministage::get_stage_ptr(ai_context &context) -{ - if (stage_) { - return stage_; - } - - std::vector stages; - engine::parse_stage_from_config(context,cfg_,std::back_inserter(stages)); - if (stages.empty()) { - return stage_ptr(); - } - stage_ = stages.front(); - return stage_; -} - -config ministage::to_config() const -{ - if (!stage_) { - return cfg_; - } - return stage_->to_config(); -} - } //end of namespace ai diff --git a/src/ai/composite/stage.hpp b/src/ai/composite/stage.hpp index e22b04a5ae09..49f9c7d715ea 100644 --- a/src/ai/composite/stage.hpp +++ b/src/ai/composite/stage.hpp @@ -54,7 +54,7 @@ class stage : public virtual ai_context_proxy, public component { /** * Play the turn - strategy - * @return true only if game state has changed. Really only needed for ministages. Returning false is always safe. + * @return true only if game state has changed. Returning false is always safe. */ bool play_stage(); @@ -77,7 +77,7 @@ class stage : public virtual ai_context_proxy, public component { protected: /** * Play the turn - implementation - * @return true only if game state has changed. Really only needed for ministages. Returning false is always safe. + * @return true only if game state has changed. Returning false is always safe. */ virtual bool do_play_stage() = 0; @@ -139,20 +139,6 @@ class register_stage_factory : public stage_factory { } }; - -/** this class is a lazily-initializing proxy for a stage **/ -class ministage { -public: - ministage(const config &cfg); - virtual ~ministage(); - stage_ptr get_stage_ptr(ai_context &context); - config to_config() const; - -private: - config cfg_; - stage_ptr stage_; -}; - } //end of namespace ai #ifdef _MSC_VER diff --git a/src/ai/composite/value_translator.hpp b/src/ai/composite/value_translator.hpp index ac05f434e800..b58ca5811528 100644 --- a/src/ai/composite/value_translator.hpp +++ b/src/ai/composite/value_translator.hpp @@ -145,33 +145,6 @@ class config_value_translator { } }; -template<> -class config_value_translator { -public: - - static ministage cfg_to_value(const config &cfg) - { - return ministage(cfg.child_or_empty("value")); - } - - static void cfg_to_value(const config &cfg, ministage &value) - { - value = cfg_to_value(cfg); - } - - static void value_to_cfg(const ministage &value, config &cfg) - { - cfg.add_child("value",value.to_config()); - } - - static config value_to_cfg(const ministage &value) - { - config cfg; - value_to_cfg(value,cfg); - return cfg; - } -}; - template<> class config_value_translator { public: @@ -264,34 +237,6 @@ class variant_value_translator { } }; -template<> -class variant_value_translator { -public: - - static void variant_to_value(const variant &/*var*/, ministage &/*value*/) - { - assert(false);//not implemented - } - - static void value_to_variant(const ministage &/*value*/, variant &/*var*/) - { - assert(false);//not implemented - } - - static variant value_to_variant(const ministage &/*value*/) - { - assert(false); - return variant(); - } - - static ministage variant_to_value(const variant &/*var*/) - { - assert(false); - config cfg; - return ministage(cfg); - } -}; - template<> class variant_value_translator { public: diff --git a/src/ai/contexts.cpp b/src/ai/contexts.cpp index f8ced0281ec9..63f06daaf8f0 100644 --- a/src/ai/contexts.cpp +++ b/src/ai/contexts.cpp @@ -218,7 +218,6 @@ readonly_context_impl::readonly_context_impl(side_context &context, const config passive_leader_(), passive_leader_shares_keep_(), possible_moves_(), - recruitment_(), recruitment_diversity_(), recruitment_ignore_bad_combat_(), recruitment_ignore_bad_movement_(), @@ -253,7 +252,6 @@ readonly_context_impl::readonly_context_impl(side_context &context, const config add_known_aspect("number_of_possible_recruits_to_force_recruit",number_of_possible_recruits_to_force_recruit_); add_known_aspect("passive_leader",passive_leader_); add_known_aspect("passive_leader_shares_keep",passive_leader_shares_keep_); - add_known_aspect("recruitment",recruitment_); add_known_aspect("recruitment_diversity",recruitment_diversity_); add_known_aspect("recruitment_ignore_bad_combat",recruitment_ignore_bad_combat_); add_known_aspect("recruitment_ignore_bad_movement",recruitment_ignore_bad_movement_); @@ -806,17 +804,6 @@ const std::vector& readonly_context_impl::get_recall_list() const return current_team().recall_list().recall_list_; //TODO: Refactor ai so that friend of ai context is not required of recall_list_manager at this line } -stage_ptr readonly_context_impl::get_recruitment(ai_context &context) const -{ - if (recruitment_) { - ministage_ptr m = recruitment_->get_ptr(); - if (m) { - return m->get_stage_ptr(context); - } - } - return stage_ptr(); -} - double readonly_context_impl::get_recruitment_diversity() const { diff --git a/src/ai/contexts.hpp b/src/ai/contexts.hpp index 71924af97b91..6b059c9db7e3 100644 --- a/src/ai/contexts.hpp +++ b/src/ai/contexts.hpp @@ -44,7 +44,6 @@ class unit_map; class unit_type; // lines 46-46 class variant; // lines 42-42 namespace ai { class ai_context; } // lines 51-51 -namespace ai { class ministage; } namespace ai { class unit_advancements_aspect; } namespace ai { template class typesafe_aspect; } namespace boost { template class shared_ptr; } @@ -310,9 +309,6 @@ class readonly_context : public virtual side_context { virtual const std::vector& get_recall_list() const = 0; - virtual stage_ptr get_recruitment(ai_context &context) const = 0; - - virtual double get_recruitment_diversity() const = 0; @@ -834,12 +830,6 @@ class readonly_context_proxy : public virtual readonly_context, public virtual s } - virtual stage_ptr get_recruitment(ai_context &context) const - { - return target_->get_recruitment(context); - } - - virtual double get_recruitment_diversity() const { return target_->get_recruitment_diversity(); @@ -1439,9 +1429,6 @@ class readonly_context_impl : public virtual side_context_proxy, public readonly virtual const std::vector& get_recall_list() const; - virtual stage_ptr get_recruitment(ai_context &context) const; - - virtual double get_recruitment_diversity() const; @@ -1587,7 +1574,6 @@ class readonly_context_impl : public virtual side_context_proxy, public readonly aspect_type::typesafe_ptr passive_leader_; aspect_type::typesafe_ptr passive_leader_shares_keep_; mutable moves_map possible_moves_; - aspect_type< ministage >::typesafe_ptr recruitment_; aspect_type< double >::typesafe_ptr recruitment_diversity_; aspect_type< bool >::typesafe_ptr recruitment_ignore_bad_combat_; aspect_type< bool >::typesafe_ptr recruitment_ignore_bad_movement_; diff --git a/src/ai/default/ai.cpp b/src/ai/default/ai.cpp index 279327c88c54..2e34d6bad77e 100644 --- a/src/ai/default/ai.cpp +++ b/src/ai/default/ai.cpp @@ -59,8 +59,6 @@ static lg::log_domain log_ai("ai/general"); namespace ai { -typedef util::array adjacent_tiles_array; - idle_ai::idle_ai(readwrite_context &context, const config& /*cfg*/) : recursion_counter_(context.get_recursion_count()) { @@ -108,797 +106,6 @@ void idle_ai::play_turn() #pragma warning(pop) #endif - -ai_default_recruitment_stage::recruit_situation_change_observer::recruit_situation_change_observer() - : valid_(false) -{ - manager::add_recruit_list_changed_observer(this); - manager::add_turn_started_observer(this); -} - - -void ai_default_recruitment_stage::recruit_situation_change_observer::handle_generic_event(const std::string &/*event_name*/) -{ - valid_ = false; -} - - -ai_default_recruitment_stage::recruit_situation_change_observer::~recruit_situation_change_observer() -{ - manager::remove_recruit_list_changed_observer(this); - manager::remove_turn_started_observer(this); -} - - -bool ai_default_recruitment_stage::recruit_situation_change_observer::get_valid() -{ - return valid_; -} - - -void ai_default_recruitment_stage::recruit_situation_change_observer::set_valid(bool valid) -{ - valid_ = valid; -} - - -void ai_default_recruitment_stage::on_create() { - stage::on_create(); - BOOST_FOREACH(const config &c, cfg_.child_range("limit")) { - if (c.has_attribute("type") && c.has_attribute("max") ) { - maximum_counts_.insert(std::make_pair(c["type"],c["max"].to_int(0))); - } - } -} - -config ai_default_recruitment_stage::to_config() const -{ - config cfg = stage::to_config(); - for (std::map::const_iterator i = maximum_counts_.begin(); i!= maximum_counts_.end(); ++i) { - config lim; - lim["type"] = i->first; - lim["max"] = str_cast(i->second); - cfg.add_child("limit",lim); - } - return cfg; -} - - -void ai_default_recruitment_stage::analyze_all() -{ - if (!recruit_situation_change_observer_.get_valid()) { - not_recommended_units_.clear(); - unit_movement_scores_.clear(); - unit_combat_scores_.clear(); - analyze_potential_recruit_movements(); - analyze_potential_recruit_combat(); - recruit_situation_change_observer_.set_valid(true); - } -} - -bool ai_default_recruitment_stage::recruit_usage(const std::string& usage) -{ - raise_user_interact(); - analyze_all(); - - const int min_gold = 0; - - log_scope2(log_ai, "recruiting troops"); - LOG_AI << "recruiting '" << usage << "'\n"; - - //make sure id, usage and cost are known for the coming evaluation of unit types - unit_types.build_all(unit_type::HELP_INDEXED); - - std::vector options; - bool found = false; - // Find an available unit that can be recruited, - // matches the desired usage type, and comes in under budget. - BOOST_FOREACH(const std::string &name, current_team().recruits()) - { - const unit_type *ut = unit_types.find(name); - if (!ut) continue; - // If usage is empty consider any unit. - if (usage.empty() || ut->usage() == usage) { - LOG_AI << name << " considered for " << usage << " recruitment\n"; - found = true; - - if (current_team().gold() - ut->cost() < min_gold) { - LOG_AI << name << " rejected, cost too high (cost: " << ut->cost() << ", current gold: " << current_team().gold() <<", min_gold: " << min_gold << ")\n"; - continue; - } - - if (not_recommended_units_.count(name)) - { - LOG_AI << name << " rejected, bad terrain or combat\n"; - continue; - } - - - std::map::iterator imc = maximum_counts_.find(name); - - if (imc != maximum_counts_.end()) { - int count_active = 0; - for (unit_map::const_iterator u = resources::units->begin(); u != resources::units->end(); ++u) { - if (u->side() == get_side() && !u->incapacitated() && u->type().base_id() == name) { - ++count_active; - } - } - - if (count_active >= imc->second) { - LOG_AI << name << " rejected, too many in the field\n"; - continue; - } - } - - LOG_AI << "recommending '" << name << "'\n"; - options.push_back(name); - } - } - - // From the available options, choose one at random - if(options.empty() == false) { - const int option = rand()%options.size(); - recruit_result_ptr recruit_res = check_recruit_action(options[option]); - if (recruit_res->is_ok()) { - recruit_res->execute(); - if (!recruit_res->is_ok()) { - ERR_AI << "recruitment failed "<< std::endl; - } - } - return recruit_res->is_gamestate_changed(); - } - if (found) { - LOG_AI << "No available units to recruit that come under the price.\n"; - } else if (usage != "") { - //FIXME: This message should be suppressed when WML author - //chooses the default recruitment pattern. - const std::string warning = "At difficulty level " + - resources::classification->difficulty + ", trying to recruit a:" + - usage + " but no unit of that type (usage=) is" - " available. Check the recruit and [ai]" - " recruitment_pattern keys for team '" + - current_team().current_player() + "' (" + - lexical_cast(get_side()) + ")" - " against the usage key of the" - " units in question! Removing invalid" - " recruitment_pattern entry and continuing...\n"; - WRN_AI << warning; - // Uncommented until the recruitment limiting macro can be fixed to not trigger this warning. - //lg::wml_error << warning; - //@fixme - //return current_team_w().remove_recruitment_pattern_entry(usage); - return false; - } - return false; -} - - -namespace { - -/** A structure for storing an item we're trying to protect. */ -struct protected_item { - protected_item(double value, int radius, const map_location& loc) : - value(value), radius(radius), loc(loc) {} - - double value; - int radius; - map_location loc; -}; - -} - -class remove_wrong_targets { -public: - remove_wrong_targets(const readonly_context &context) - :avoid_(context.get_avoid()), map_(resources::gameboard->map()) - { - } - -bool operator()(const target &t){ - if (!map_.on_board(t.loc)) { - DBG_AI << "removing target "<< t.loc << " due to it not on_board" << std::endl; - return true; - } - - if (t.value<=0) { - DBG_AI << "removing target "<< t.loc << " due to value<=0" << std::endl; - return true; - } - - if (avoid_.match(t.loc)) { - DBG_AI << "removing target "<< t.loc << " due to 'avoid' match" << std::endl; - return true; - } - - return false; -} -private: - const terrain_filter &avoid_; - const gamemap &map_; - -}; - - -int ai_default_recruitment_stage::average_resistance_against(const unit_type& a, const unit_type& b) const -{ - int weighting_sum = 0, defense = 0; - const std::map& terrain = - resources::gameboard->map().get_weighted_terrain_frequencies(); - - for (std::map::const_iterator j = terrain.begin(), - j_end = terrain.end(); j != j_end; ++j) - { - // Use only reachable tiles when computing the average defense. - if (a.movement_type().movement_cost(j->first) < movetype::UNREACHABLE) { - defense += a.movement_type().defense_modifier(j->first) * j->second; - weighting_sum += j->second; - } - } - - if (weighting_sum == 0) { - // This unit can't move on this map, so just get the average weighted - // of all available terrains. This still is a kind of silly - // since the opponent probably can't recruit this unit and it's a static unit. - for (std::map::const_iterator jj = terrain.begin(), - jj_end = terrain.end(); jj != jj_end; ++jj) - { - defense += a.movement_type().defense_modifier(jj->first) * jj->second; - weighting_sum += jj->second; - } - } - - if(weighting_sum != 0) { - defense /= weighting_sum; - } else { - ERR_AI << "The weighting sum is 0 and is ignored." << std::endl; - } - - LOG_AI << "average defense of '" << a.id() << "': " << defense << "\n"; - - int sum = 0, weight_sum = 0; - - // calculation of the average damage taken - bool steadfast = a.has_ability_by_id("steadfast"); - bool poisonable = !a.musthave_status("unpoisonable"); - const std::vector& attacks = b.attacks(); - for (std::vector::const_iterator i = attacks.begin(), - i_end = attacks.end(); i != i_end; ++i) - { - int resistance = a.movement_type().resistance_against(*i); - // Apply steadfast resistance modifier. - if (steadfast && resistance < 100) - resistance = std::max(resistance * 2 - 100, 50); - // Do not look for filters or values, simply assume 70% if CTH is customized. - int cth = i->get_special_bool("chance_to_hit", true) ? 70 : defense; - int weight = i->damage() * i->num_attacks(); - // if cth == 0 the division will do 0/0 so don't execute this part - if (poisonable && cth != 0 && i->get_special_bool("poison", true)) { - // Compute the probability of not poisoning the unit. - int prob = 100; - for (int j = 0; j < i->num_attacks(); ++j) - prob = prob * (100 - cth); - // Assume poison works one turn. - weight += game_config::poison_amount * (100 - prob) / 100; - } - sum += cth * resistance * weight * weight; // average damage * weight - weight_sum += weight; - } - - // normalize by HP - sum /= std::max(1,std::min(a.hitpoints(),1000)); // avoid values really out of range - - // Catch division by zero here if the attacking unit - // has zero attacks and/or zero damage. - // If it has no attack at all, the ai shouldn't prefer - // that unit anyway. - if (weight_sum == 0) { - return sum; - } - return sum/weight_sum; -} - -int ai_default_recruitment_stage::compare_unit_types(const unit_type& a, const unit_type& b) const -{ - const int a_effectiveness_vs_b = average_resistance_against(b,a); - const int b_effectiveness_vs_a = average_resistance_against(a,b); - - LOG_AI << "comparison of '" << a.id() << " vs " << b.id() << ": " - << a_effectiveness_vs_b << " - " << b_effectiveness_vs_a << " = " - << (a_effectiveness_vs_b - b_effectiveness_vs_a) << '\n'; - return a_effectiveness_vs_b - b_effectiveness_vs_a; -} - -void ai_default_recruitment_stage::get_combat_score_vs(const unit_type& ut, const std::string &enemy_type_id, int &score, int &weighting, int hitpoints, int max_hitpoints) const -{ - const unit_type *enemy_info = unit_types.find(enemy_type_id); - VALIDATE(enemy_info, "Unknown unit type : " + enemy_type_id + " while scoring units."); - int weight = ut.cost(); - if ((hitpoints>0) && (max_hitpoints>0)) { - weight = weight * hitpoints / max_hitpoints; - } - - weighting += weight; - score += compare_unit_types(ut, *enemy_info) * weight; -} - -int ai_default_recruitment_stage::get_combat_score(const unit_type& ut) const -{ - int score = 0, weighting = 0; - const unit_map & units_ = *resources::units; - for(unit_map::const_iterator j = units_.begin(); j != units_.end(); ++j) { - if (!current_team().is_enemy(j->side())) { - continue; - } - - if (j->can_recruit()) { - - team &enemy_team = (*resources::teams)[j->side() - 1]; - const std::set &recruits = enemy_team.recruits(); - BOOST_FOREACH(const std::string &rec, recruits) { - get_combat_score_vs(ut,rec,score,weighting,0,0); - } - continue; - } - - get_combat_score_vs(ut, j->type().base_id(), score, weighting, j->hitpoints(), j->max_hitpoints()); - } - - if(weighting != 0) { - score /= weighting; - } - return score; -} - -void ai_default_recruitment_stage::analyze_potential_recruit_combat() -{ - if(unit_combat_scores_.empty() == false || - get_recruitment_ignore_bad_combat()) { - return; - } - - log_scope2(log_ai, "analyze_potential_recruit_combat()"); - - // Records the best combat analysis for each usage type. - best_usage_.clear(); - - const std::set& recruits = current_team().recruits(); - std::set::const_iterator i; - for(i = recruits.begin(); i != recruits.end(); ++i) { - const unit_type *info = unit_types.find(*i); - if (!info || not_recommended_units_.count(*i)) { - continue; - } - - int score = get_combat_score(*info); - LOG_AI << "combat score of '" << *i << "': " << score << "\n"; - unit_combat_scores_[*i] = score; - - if(best_usage_.count(info->usage()) == 0 || - score > best_usage_[info->usage()]) { - best_usage_[info->usage()] = score; - } - } - - // Recommend not to use units of a certain usage type - // if they have a score more than 600 below - // the best unit of that usage type. - for(i = recruits.begin(); i != recruits.end(); ++i) { - const unit_type *info = unit_types.find(*i); - if (!info || not_recommended_units_.count(*i)) { - continue; - } - - if(unit_combat_scores_[*i] + 600 < best_usage_[info->usage()]) { - LOG_AI << "recommending not to use '" << *i << "' because of poor combat performance " - << unit_combat_scores_[*i] << "/" << best_usage_[info->usage()] << "\n"; - not_recommended_units_.insert(*i); - } - } -} - -namespace { - -struct target_comparer_distance { - target_comparer_distance(const map_location& loc) : loc_(loc) {} - - bool operator()(const ai::target& a, const ai::target& b) const { - return distance_between(a.loc,loc_) < distance_between(b.loc,loc_); - } - -private: - map_location loc_; -}; - -} - - -ai_default_recruitment_stage::ai_default_recruitment_stage(ai_context &context, const config &cfg) - : stage(context,cfg), - best_usage_(), - cfg_(cfg), - maximum_counts_(), - not_recommended_units_(), - recall_list_scores_(), - recruit_situation_change_observer_(), - unit_combat_scores_(), - unit_movement_scores_() - -{ -} - - -ai_default_recruitment_stage::~ai_default_recruitment_stage() -{ -} - -void ai_default_recruitment_stage::analyze_potential_recruit_movements() -{ - const unit_map &units_ = *resources::units; - const gamemap &map_ = resources::gameboard->map(); - - if(unit_movement_scores_.empty() == false || - get_recruitment_ignore_bad_movement()) { - return; - } - - const unit_map::const_iterator leader = units_.find_leader(get_side()); - if(leader == units_.end()) { - return; - } - - const map_location& start = nearest_keep(leader->get_location()); - if(map_.on_board(start) == false) { - return; - } - - log_scope2(log_ai, "analyze_potential_recruit_movements()"); - - const unsigned int max_targets = 5; - - const move_map dstsrc; - std::vector targets = find_targets(dstsrc); - if(targets.size() > max_targets) { - std::sort(targets.begin(),targets.end(),target_comparer_distance(start)); - targets.erase(targets.begin()+max_targets,targets.end()); - } - - const std::set& recruits = current_team().recruits(); - - LOG_AI << "targets: " << targets.size() << "\n"; - - std::map best_scores; - - for(std::set::const_iterator i = recruits.begin(); i != recruits.end(); ++i) { - const unit_type *info = unit_types.find(*i); - if (!info) { - continue; - } - - const unit_type &ut = *info; - ///@todo 1.9: we give max movement, but recruited will get 0? Seems inaccurate - //but keep it like that for now - // pathfinding ignoring other units and terrain defense - const pathfind::move_type_path_calculator calc(ut.movement_type(), ut.movement(), ut.movement(), current_team(),map_); - - int cost = 0; - int targets_reached = 0; - int targets_missed = 0; - - for(std::vector::const_iterator t = targets.begin(); t != targets.end(); ++t) { - LOG_AI << "analyzing '" << *i << "' getting to target...\n"; - pathfind::plain_route route = a_star_search(start, t->loc, 100.0, &calc, - resources::gameboard->map().w(), resources::gameboard->map().h()); - - if (!route.steps.empty()) { - LOG_AI << "made it: " << route.move_cost << "\n"; - cost += route.move_cost; - ++targets_reached; - } else { - LOG_AI << "failed\n"; - ++targets_missed; - } - } - - if(targets_reached == 0 || targets_missed >= targets_reached*2) { - unit_movement_scores_[*i] = 100000; - not_recommended_units_.insert(*i); - } else { - const int average_cost = cost/targets_reached; - const int score = (average_cost * (targets_reached+targets_missed))/targets_reached; - unit_movement_scores_[*i] = score; - - const std::map::const_iterator current_best = best_scores.find(ut.usage()); - if(current_best == best_scores.end() || score < current_best->second) { - best_scores[ut.usage()] = score; - } - } - } - - for(std::map::iterator j = unit_movement_scores_.begin(); - j != unit_movement_scores_.end(); ++j) { - - const unit_type *info = unit_types.find(j->first); - - if (!info) { - continue; - } - - const int best_score = best_scores[info->usage()]; - if(best_score > 0) { - j->second = (j->second*10)/best_score; - if(j->second > 15) { - LOG_AI << "recommending against recruiting '" << j->first << "' (score: " << j->second << ")\n"; - not_recommended_units_.insert(j->first); - } else { - LOG_AI << "recommending recruit of '" << j->first << "' (score: " << j->second << ")\n"; - } - } - } - - if(not_recommended_units_.size() == unit_movement_scores_.size()) { - not_recommended_units_.clear(); - } -} - - -std::string ai_default_recruitment_stage::find_suitable_recall_id() -{ - if (recall_list_scores_.empty()) { - return ""; - } - std::string best_id = recall_list_scores_.back().first; - recall_list_scores_.pop_back(); - return best_id; -} - -class unit_combat_score_getter { -public: - unit_combat_score_getter(const ai_default_recruitment_stage &s) - : stage_(s) - { - } - std::pair operator()(const unit_ptr u_ptr) { - const unit & u = *u_ptr; - std::pair p; - p.first = u.id(); - const unit_type& u_type = u.type(); - - double xp_ratio = 0; - if (u.can_advance() && (u.max_experience()>0)) { - xp_ratio = u.experience()/u.max_experience(); - } - - p.second = (1-xp_ratio) * stage_.get_combat_score(u_type); - double recall_cost = game_config::recall_cost != 0 ? game_config::recall_cost : 1; - - p.second *= static_cast(u_type.cost())/recall_cost; - if (u.can_advance() && (xp_ratio>0) ) { - double best_combat_score_of_advancement = 0; - bool best_combat_score_of_advancement_found = false; - int best_cost = recall_cost; - BOOST_FOREACH(const std::string &i, u.advances_to()) { - const unit_type *ut = unit_types.find(i); - if (!ut) { - continue; - } - - int combat_score_of_advancement = stage_.get_combat_score(*ut); - if (!best_combat_score_of_advancement_found || (best_combat_score_of_advancementcost(); - } - - } - p.second += xp_ratio*best_combat_score_of_advancement*best_cost/recall_cost; - } - - return p; - - } -private: - const ai_default_recruitment_stage &stage_; -}; - - -template -bool smaller_mapped_value(const std::pair& a, const std::pair& b) -{ - return a.second < b.second; -} - -class bad_recalls_remover { -public: - bad_recalls_remover(const std::map& unit_combat_scores) - : allow_any_(false), best_combat_score_() - { - std::map::const_iterator cs = std::min_element(unit_combat_scores.begin(),unit_combat_scores.end(),&smaller_mapped_value); - - if (cs == unit_combat_scores.end()) { - allow_any_ = true; - } else { - best_combat_score_ = cs->second; - } - } - bool operator()(const std::pair& p) { - if (allow_any_) { - return false; - } - if (p.second>=best_combat_score_) { - return false; - } - return true; - } - -private: - bool allow_any_; - double best_combat_score_; -}; - - -class combat_score_less { -public: - bool operator()(const std::pair &s1, const std::pair &s2) - { - return s1.second > &recall_list_scores,const char *message) -{ - if (!lg::debug().dont_log(log_ai)) { - std::stringstream s; - s << message << std::endl; - for (std::vector< std::pair >::const_iterator p = recall_list_scores.begin(); p!=recall_list_scores.end();++p) { - s << p->first << " ["<second<<"]"< > > (recall_list_scores_), unit_combat_score_getter(*this) ); - - debug_print_recall_list_scores(recall_list_scores_,"Recall list, after scoring:"); - - recall_list_scores_.erase( std::remove_if(recall_list_scores_.begin(), recall_list_scores_.end(), bad_recalls_remover(unit_combat_scores_)), recall_list_scores_.end() ); - - debug_print_recall_list_scores(recall_list_scores_,"Recall list, after erase:"); - - if (recall_list_scores_.empty()) { - return false; - } - - sort(recall_list_scores_.begin(),recall_list_scores_.end(),combat_score_less()); - - debug_print_recall_list_scores(recall_list_scores_,"Recall list, after sort (worst to best):"); - - return !(recall_list_scores_.empty()); -} - - -bool ai_default_recruitment_stage::do_play_stage() -{ - const unit_map &units_ = *resources::units; - - const unit_map::const_iterator leader = units_.find_leader(get_side()); - if(leader == units_.end()) { - return false; - } - - const map_location& start_pos = nearest_keep(leader->get_location()); - - analyze_all(); - - //handle recalls - //if there any recalls left which have a better combat score/cost ratio, get them - bool gamestate_changed = false; - std::string id; - if (analyze_recall_list()) { - while ( !(id = find_suitable_recall_id()).empty() ) { - - recall_result_ptr recall_res = check_recall_action(id); - if (recall_res->is_ok()) { - recall_res->execute(); - if (!recall_res->is_ok()) { - ERR_AI << "recall failed "<< std::endl; - break; - } - } - gamestate_changed |= recall_res->is_gamestate_changed(); - - } - } - - std::vector options = get_recruitment_pattern(); - if (std::count(options.begin(), options.end(), "scout") > 0) { - size_t neutral_villages = 0; - - // We recruit the initial allocation of scouts - // based on how many neutral villages there are - // that are closer to us than to other keeps. - const std::vector& villages = resources::gameboard->map().villages(); - for(std::vector::const_iterator v = villages.begin(); v != villages.end(); ++v) { - const int owner = resources::gameboard->village_owner(*v); - if(owner == -1) { - const size_t distance = distance_between(start_pos,*v); - - bool closest = true; - for(std::vector::const_iterator i = resources::teams->begin(); i != resources::teams->end(); ++i) { - const int index = i - resources::teams->begin() + 1; - const map_location& loc = resources::gameboard->map().starting_position(index); - if(loc != start_pos && distance_between(loc,*v) < distance) { - closest = false; - break; - } - } - - if(closest) { - ++neutral_villages; - } - } - } - - // The villages per scout is for a two-side battle, - // accounting for all neutral villages on the map. - // We only look at villages closer to us, so we halve it, - // making us get twice as many scouts. - const int villages_per_scout = get_villages_per_scout()/2; - - // Get scouts depending on how many neutral villages there are. - int scouts_wanted = villages_per_scout > 0 ? neutral_villages/villages_per_scout : 0; - - LOG_AI << "scouts_wanted: " << neutral_villages << "/" - << villages_per_scout << " = " << scouts_wanted << "\n"; - - std::map unit_types; - - for(unit_map::const_iterator u = units_.begin(); u != units_.end(); ++u) { - if (u->side() == get_side()) { - ++unit_types[u->usage()]; - } - } - - LOG_AI << "we have " << unit_types["scout"] << " scouts already and we want " - << scouts_wanted << " in total\n"; - - while(unit_types["scout"] < scouts_wanted) { - if(recruit_usage("scout") == false) - break; - ++unit_types["scout"]; - } - } - - // If there is no recruitment_pattern use "" which makes us consider - // any unit available. - if (options.empty()) { - options.push_back(""); - } - // Buy units as long as we have room and can afford it. - - while (recruit_usage(options[rand()%options.size()])) { - gamestate_changed = true; - options = get_recruitment_pattern(); - if (options.empty()) { - options.push_back(""); - } - } - - return gamestate_changed; -} - variant attack_analysis::get_value(const std::string& key) const { using namespace game_logic; diff --git a/src/ai/default/ai.hpp b/src/ai/default/ai.hpp index 42e78eeeb1e0..0c7262fd8781 100644 --- a/src/ai/default/ai.hpp +++ b/src/ai/default/ai.hpp @@ -27,17 +27,8 @@ #endif -namespace pathfind { - -struct plain_route; - -} // of namespace pathfind - - namespace ai { -class formula_ai; - /** A trivial ai that sits around doing absolutely nothing. */ class idle_ai : public readwrite_context_proxy, public interface { public: @@ -52,98 +43,6 @@ class idle_ai : public readwrite_context_proxy, public interface { recursion_counter recursion_counter_; }; -class ai_default_recruitment_stage : public stage { -public: - ai_default_recruitment_stage(ai_context &context, const config &cfg); - virtual ~ai_default_recruitment_stage(); - void on_create(); - bool do_play_stage(); - config to_config() const; - int get_combat_score(const unit_type& ut) const; - -private: - - void get_combat_score_vs(const unit_type& ut, const std::string &enemy_type_id, int &score, int &weighting, int hitpoints, int max_hitpoints) const; - - virtual bool recruit_usage(const std::string& usage); - - - class recruit_situation_change_observer : public events::observer { - public: - recruit_situation_change_observer(); - ~recruit_situation_change_observer(); - - void handle_generic_event(const std::string& /*event_name*/); - - bool get_valid(); - void set_valid(bool valid); - private: - bool valid_; - - }; - - /** - * initialize recruitment recommendations - */ - void analyze_all(); - - /** - * Analyze all the units that this side can recruit - * and rate their movement types. - * Ratings will be placed in 'unit_movement_scores_', - * with lower scores being better, - * and the lowest possible rating being '10'. - */ - virtual void analyze_potential_recruit_movements(); - - std::string find_suitable_recall_id(); - - std::map best_usage_; - - config cfg_; - - std::map maximum_counts_; - - std::set not_recommended_units_; - - std::vector > recall_list_scores_; - - recruit_situation_change_observer recruit_situation_change_observer_; - - std::map unit_combat_scores_; - - std::map unit_movement_scores_; - - /** - * Analyze all the units that this side can recruit - * and rate their fighting suitability against enemy units. - * Ratings will be placed in 'unit_combat_scores_', - * with a '0' rating indicating that the unit is 'average' against enemy units, - * negative ratings meaning they are poorly suited, - * and positive ratings meaning they are well suited. - */ - virtual void analyze_potential_recruit_combat(); - - - bool analyze_recall_list(); - - /** - * Rates two unit types for their suitability against each other. - * Returns 0 if the units are equally matched, - * a positive number if a is suited against b, - * and a negative number if b is suited against a. - */ - virtual int compare_unit_types(const unit_type& a, const unit_type& b) const; - - /** - * calculates the average resistance unit type a has against the attacks of - * unit type b. - */ - virtual int average_resistance_against(const unit_type& a, const unit_type& b) const; - - -}; - } //end of namespace ai #ifdef _MSC_VER diff --git a/src/ai/formula/ai.cpp b/src/ai/formula/ai.cpp index 19ca5fe1af5b..39f86e585e5e 100644 --- a/src/ai/formula/ai.cpp +++ b/src/ai/formula/ai.cpp @@ -489,12 +489,7 @@ variant formula_ai::execute_variant(const variant& var, ai_context &ai_, bool co } } else if( action.is_string() && action.as_string() == "recruit") { - stage_ptr r = get_recruitment(ai_); - if (r) { - if (r->play_stage()) { - made_moves.push_back(action); - } - } + ERR_AI << "FormulaAI recruitment is currently broken, sorry!" << std::endl; } else if( action.is_string() && action.as_string() == "continue") { if( infinite_loop_guardian_.continue_check() ) { made_moves.push_back(action); diff --git a/src/ai/game_info.hpp b/src/ai/game_info.hpp index 32a330e0c694..478224de3cde 100644 --- a/src/ai/game_info.hpp +++ b/src/ai/game_info.hpp @@ -66,7 +66,6 @@ class candidate_action; class engine; class goal; class known_aspect; -class ministage; class stage; template @@ -110,7 +109,6 @@ typedef boost::shared_ptr< candidate_action > candidate_action_ptr; typedef boost::shared_ptr< engine > engine_ptr; typedef boost::shared_ptr< goal > goal_ptr; typedef boost::shared_ptr< known_aspect > known_aspect_ptr; -typedef boost::shared_ptr< ministage > ministage_ptr; typedef boost::shared_ptr< stage > stage_ptr; typedef std::map aspect_map; diff --git a/src/ai/registry.cpp b/src/ai/registry.cpp index ebfea9a4c882..466c1a7b5950 100644 --- a/src/ai/registry.cpp +++ b/src/ai/registry.cpp @@ -88,9 +88,6 @@ static register_stage_factory static register_stage_factory fallback_to_other_ai_factory("testing_ai_default::fallback"); -static register_stage_factory - ai_default_recruitment_stage_factory("ai_default::recruitment"); - static register_stage_factory ai_idle_stage_factory("empty"); @@ -105,12 +102,6 @@ static register_stage_factory goto_phase_factory("ai_default_rca::goto_phase"); -static register_candidate_action_factory - aspect_recruitment_phase_factory("ai_default_rca::aspect_recruitment_phase"); - -static register_candidate_action_factory - recruitment_phase_factory("ai_default_rca::recruitment_phase"); - static register_candidate_action_factory combat_phase_factory("ai_default_rca::combat_phase"); @@ -161,12 +152,6 @@ static register_candidate_action_factory static register_candidate_action_factory old_goto_phase_factory("testing_ai_default::goto_phase"); -static register_candidate_action_factory - old_aspect_recruitment_phase_factory("testing_ai_default::aspect_recruitment_phase"); - -static register_candidate_action_factory - old_recruitment_phase_factory("testing_ai_default::recruitment_phase"); - static register_candidate_action_factory old_combat_phase_factory("testing_ai_default::combat_phase"); @@ -285,9 +270,6 @@ static register_aspect_factory< composite_aspect > static register_aspect_factory< composite_aspect > passive_leader_shares_keep__composite_aspect_factory("passive_leader_shares_keep*composite_aspect"); -static register_aspect_factory< composite_aspect > - recruitment__composite_aspect_factory("recruitment*composite_aspect"); - static register_aspect_factory< composite_aspect > recruitment_diversity__composite_aspect_factory("recruitment_diversity*composite_aspect"); @@ -371,9 +353,6 @@ static register_aspect_factory< standard_aspect > static register_aspect_factory< standard_aspect > passive_leader_shares_keep__standard_aspect_factory("passive_leader_shares_keep*standard_aspect"); -static register_aspect_factory< standard_aspect > - recruitment__standard_aspect_factory("recruitment*standard_aspect"); - static register_aspect_factory< standard_aspect > recruitment_diversity__standard_aspect_factory("recruitment_diversity*standard_aspect"); @@ -461,9 +440,6 @@ static register_aspect_factory< standard_aspect > static register_aspect_factory< standard_aspect > passive_leader_shares_keep__standard_aspect_factory2("passive_leader_shares_keep*"); -static register_aspect_factory< standard_aspect > - recruitment__standard_aspect_factory2("recruitment*"); - static register_aspect_factory< standard_aspect > recruitment_diversity__standard_aspect_factory2("recruitment_diversity*"); diff --git a/src/ai/testing/ca.cpp b/src/ai/testing/ca.cpp index 93abfeded03d..c7cf285f3510 100644 --- a/src/ai/testing/ca.cpp +++ b/src/ai/testing/ca.cpp @@ -139,405 +139,6 @@ void goto_phase::execute() } } -//============================================================== - -aspect_recruitment_phase::aspect_recruitment_phase( rca_context &context, const config &cfg ) - : candidate_action(context,cfg) -{ -} - - -aspect_recruitment_phase::~aspect_recruitment_phase() -{ -} - -double aspect_recruitment_phase::evaluate() -{ - const unit_map::const_iterator leader = resources::units->find_leader(get_side()); - if(leader == resources::units->end()) { - return BAD_SCORE; - } - if (!resources::gameboard->map().is_keep(leader->get_location())) { - return BAD_SCORE; - } - - map_location recruit_loc = pathfind::find_vacant_castle(*leader); - if (!resources::gameboard->map().on_board(recruit_loc)) { - return BAD_SCORE; - } - - //note that no gold check is done. This is intended, to speed up recruitment_phase::evaluate() - //so, after 1st failed recruit, this candidate action will be blacklisted for 1 turn. - - return get_score(); -} - -void aspect_recruitment_phase::execute() -{ - raise_user_interact(); - stage_ptr r = get_recruitment(*this); - if (r) { - r->play_stage(); - } else { - ERR_AI_TESTING_AI_DEFAULT << "no recruitment aspect - skipping recruitment" << std::endl; - } -} - -//============================================================== - -recruitment_phase::recruitment_phase( rca_context &context, const config &cfg ) - : candidate_action(context,cfg) - , unit_movement_scores_() - , not_recommended_units_() - , unit_combat_scores_() -{ -} - - -recruitment_phase::~recruitment_phase() -{ -} - -double recruitment_phase::evaluate() -{ - const unit_map::const_iterator leader = resources::units->find_leader(get_side()); - if(leader == resources::units->end()) { - return BAD_SCORE; - } - if (!resources::gameboard->map().is_keep(leader->get_location())) { - return BAD_SCORE; - } - - std::set checked_hexes; - checked_hexes.insert(leader->get_location()); - if (count_free_hexes_in_castle(leader->get_location(), checked_hexes)==0) { - return BAD_SCORE; - } - - //note that no gold check is done. This is intended, to speed up recruitment_phase::evaluate() - //so, after 1st failed recruit, this candidate action will be blacklisted for 1 turn. - - return get_score(); -} - -void recruitment_phase::execute() -{ - not_recommended_units_.clear(); - unit_combat_scores_.clear(); - unit_movement_scores_.clear(); - - const unit_map &units_ = *resources::units; - const gamemap &map_ = resources::gameboard->map(); - const std::vector &teams_ = *resources::teams; - - map_location start_pos = units_.find_leader(get_side())->get_location(); - - raise_user_interact(); - //analyze_potential_recruit_movements(); - analyze_potential_recruit_combat(); - - std::vector options = get_recruitment_pattern(); - - if (std::count(options.begin(), options.end(), "scout") > 0) { - size_t neutral_villages = 0; - - // We recruit the initial allocation of scouts - // based on how many neutral villages there are - // that are closer to us than to other keeps. - const std::vector& villages = map_.villages(); - for(std::vector::const_iterator v = villages.begin(); v != villages.end(); ++v) { - const int owner = resources::gameboard->village_owner(*v); - if(owner == -1) { - const size_t distance = distance_between(start_pos,*v); - - bool closest = true; - for(std::vector::const_iterator i = teams_.begin(); i != teams_.end(); ++i) { - const int index = i - teams_.begin() + 1; - const map_location& loc = map_.starting_position(index); - if(loc != start_pos && distance_between(loc,*v) < distance) { - closest = false; - break; - } - } - - if(closest) { - ++neutral_villages; - } - } - } - - // The villages per scout is for a two-side battle, - // accounting for all neutral villages on the map. - // We only look at villages closer to us, so we halve it, - // making us get twice as many scouts. - const int villages_per_scout = get_villages_per_scout()/2; - - // Get scouts depending on how many neutral villages there are. - int scouts_wanted = villages_per_scout > 0 ? neutral_villages/villages_per_scout : 0; - - LOG_AI_TESTING_AI_DEFAULT << "scouts_wanted: " << neutral_villages << "/" - << villages_per_scout << " = " << scouts_wanted << "\n"; - - std::map unit_types; - - for(unit_map::const_iterator u = units_.begin(); u != units_.end(); ++u) { - if (u->side() == get_side()) { - ++unit_types[u->usage()]; - } - } - - LOG_AI_TESTING_AI_DEFAULT << "we have " << unit_types["scout"] << " scouts already and we want " - << scouts_wanted << " in total\n"; - - while(unit_types["scout"] < scouts_wanted) { - if (!recruit_usage("scout")){ - break; - } - ++unit_types["scout"]; - } - } - - // If there is no recruitment_pattern use "" which makes us consider - // any unit available. - if (options.empty()) { - options.push_back(""); - } - // Buy units as long as we have room and can afford it. - while (recruit_usage(options[rand()%options.size()])) { - //refresh the recruitment pattern - it can be changed by recruit_usage - options = get_recruitment_pattern(); - if (options.empty()) { - options.push_back(""); - } - } - -} - -bool recruitment_phase::recruit_usage(const std::string& usage) -{ - raise_user_interact(); - - const int min_gold = 0; - - log_scope2(log_ai_testing_ai_default, "recruiting troops"); - LOG_AI_TESTING_AI_DEFAULT << "recruiting '" << usage << "'\n"; - - //make sure id, usage and cost are known for the coming evaluation of unit types - unit_types.build_all(unit_type::HELP_INDEXED); - - std::vector options; - bool found = false; - // Find an available unit that can be recruited, - // matches the desired usage type, and comes in under budget. - BOOST_FOREACH(const std::string &name, current_team().recruits()) - { - const unit_type *ut = unit_types.find(name); - if (!ut) continue; - // If usage is empty consider any unit. - if (usage.empty() || ut->usage() == usage) { - LOG_AI_TESTING_AI_DEFAULT << name << " considered for " << usage << " recruitment\n"; - found = true; - - if (current_team().gold() - ut->cost() < min_gold) { - LOG_AI_TESTING_AI_DEFAULT << name << " rejected, cost too high (cost: " << ut->cost() << ", current gold: " << current_team().gold() <<", min_gold: " << min_gold << ")\n"; - continue; - } - - if (not_recommended_units_.count(name)) - { - LOG_AI_TESTING_AI_DEFAULT << name << " rejected, bad terrain or combat\n"; - continue; - } - - LOG_AI_TESTING_AI_DEFAULT << "recommending '" << name << "'\n"; - options.push_back(name); - } - } - - // From the available options, choose one at random - if(options.empty() == false) { - const int option = rand()%options.size(); - recruit_result_ptr recruit_result = execute_recruit_action(options[option]); - return recruit_result->is_ok(); - } - if (found) { - LOG_AI_TESTING_AI_DEFAULT << "No available units to recruit that come under the price.\n"; - } else if (usage != "") { - //FIXME: This message should be suppressed when WML author - //chooses the default recruitment pattern. - const std::string warning = "At difficulty level " + - resources::classification->difficulty + ", trying to recruit a:" + - usage + " but no unit of that type (usage=) is" - " available. Check the recruit and [ai]" - " recruitment_pattern keys for team '" + - current_team().current_player() + "' (" + - lexical_cast(get_side()) + ")" - " against the usage key of the" - " units in question! Removing invalid" - " recruitment_pattern entry and continuing...\n"; - WRN_AI_TESTING_AI_DEFAULT << warning; - // Uncommented until the recruitment limiting macro can be fixed to not trigger this warning. - //lg::wml_error << warning; - //@fixme - //return current_team_w().remove_recruitment_pattern_entry(usage); - return false; - } - return false; -} - -int recruitment_phase::average_resistance_against(const unit_type& a, const unit_type& b) const -{ - int weighting_sum = 0, defense = 0; - const std::map& terrain = - resources::gameboard->map().get_weighted_terrain_frequencies(); - - for (std::map::const_iterator j = terrain.begin(), - j_end = terrain.end(); j != j_end; ++j) - { - // Use only reachable tiles when computing the average defense. - if (a.movement_type().movement_cost(j->first) < movetype::UNREACHABLE) { - defense += a.movement_type().defense_modifier(j->first) * j->second; - weighting_sum += j->second; - } - } - - if (weighting_sum == 0) { - // This unit can't move on this map, so just get the average weighted - // of all available terrains. This still is a kind of silly - // since the opponent probably can't recruit this unit and it's a static unit. - for (std::map::const_iterator jj = terrain.begin(), - jj_end = terrain.end(); jj != jj_end; ++jj) - { - defense += a.movement_type().defense_modifier(jj->first) * jj->second; - weighting_sum += jj->second; - } - } - - if(weighting_sum != 0) { - defense /= weighting_sum; - } else { - ERR_AI_TESTING_AI_DEFAULT << "The weighting sum is 0 and is ignored." << std::endl; - } - - LOG_AI_TESTING_AI_DEFAULT << "average defense of '" << a.id() << "': " << defense << "\n"; - - int sum = 0, weight_sum = 0; - - // calculation of the average damage taken - bool steadfast = a.has_ability_by_id("steadfast"); - bool poisonable = !a.musthave_status("unpoisonable"); - const std::vector& attacks = b.attacks(); - for (std::vector::const_iterator i = attacks.begin(), - i_end = attacks.end(); i != i_end; ++i) - { - int resistance = a.movement_type().resistance_against(*i); - // Apply steadfast resistance modifier. - if (steadfast && resistance < 100) - resistance = std::max(resistance * 2 - 100, 50); - // Do not look for filters or values, simply assume 70% if CTH is customized. - int cth = i->get_special_bool("chance_to_hit", true) ? 70 : defense; - int weight = i->damage() * i->num_attacks(); - // if cth == 0 the division will do 0/0 so don't execute this part - if (poisonable && cth != 0 && i->get_special_bool("poison", true)) { - // Compute the probability of not poisoning the unit. - int prob = 100; - for (int j = 0; j < i->num_attacks(); ++j) - prob = prob * (100 - cth); - // Assume poison works one turn. - weight += game_config::poison_amount * (100 - prob) / 100; - } - sum += cth * resistance * weight * weight; // average damage * weight - weight_sum += weight; - } - - // normalize by HP - sum /= std::max(1,std::min(a.hitpoints(),1000)); // avoid values really out of range - - // Catch division by zero here if the attacking unit - // has zero attacks and/or zero damage. - // If it has no attack at all, the ai shouldn't prefer - // that unit anyway. - if (weight_sum == 0) { - return sum; - } - return sum/weight_sum; -} - -int recruitment_phase::compare_unit_types(const unit_type& a, const unit_type& b) const -{ - const int a_effectiveness_vs_b = average_resistance_against(b,a); - const int b_effectiveness_vs_a = average_resistance_against(a,b); - - LOG_AI_TESTING_AI_DEFAULT << "comparison of '" << a.id() << " vs " << b.id() << ": " - << a_effectiveness_vs_b << " - " << b_effectiveness_vs_a << " = " - << (a_effectiveness_vs_b - b_effectiveness_vs_a) << '\n'; - return a_effectiveness_vs_b - b_effectiveness_vs_a; -} - -void recruitment_phase::analyze_potential_recruit_combat() -{ - unit_map &units_ = *resources::units; - if(unit_combat_scores_.empty() == false || get_recruitment_ignore_bad_combat()) { - return; - } - - log_scope2(log_ai_testing_ai_default, "analyze_potential_recruit_combat()"); - - // Records the best combat analysis for each usage type. - std::map best_usage; - - const std::set& recruits = current_team().recruits(); - std::set::const_iterator i; - for(i = recruits.begin(); i != recruits.end(); ++i) { - const unit_type *info = unit_types.find(*i); - if (!info || not_recommended_units_.count(*i)) { - continue; - } - - int score = 0, weighting = 0; - - for(unit_map::const_iterator j = units_.begin(); j != units_.end(); ++j) { - if (j->can_recruit() || !current_team().is_enemy(j->side())) { - continue; - } - - unit const &un = *j; - - int weight = un.cost() * un.hitpoints() / un.max_hitpoints(); - weighting += weight; - score += compare_unit_types(*info, un.type()) * weight; - } - - if(weighting != 0) { - score /= weighting; - } - - LOG_AI_TESTING_AI_DEFAULT << "combat score of '" << *i << "': " << score << "\n"; - unit_combat_scores_[*i] = score; - - if (best_usage.count(info->usage()) == 0 || - score > best_usage[info->usage()]) { - best_usage[info->usage()] = score; - } - } - - // Recommend not to use units of a certain usage type - // if they have a score more than 600 below - // the best unit of that usage type. - for(i = recruits.begin(); i != recruits.end(); ++i) { - const unit_type *info = unit_types.find(*i); - if (!info || not_recommended_units_.count(*i)) { - continue; - } - - if (unit_combat_scores_[*i] + 600 < best_usage[info->usage()]) { - LOG_AI_TESTING_AI_DEFAULT << "recommending not to use '" << *i << "' because of poor combat performance " - << unit_combat_scores_[*i] << "/" << best_usage[info->usage()] << "\n"; - not_recommended_units_.insert(*i); - } - } -} - //============================================================== diff --git a/src/ai/testing/ca.hpp b/src/ai/testing/ca.hpp index 286079add6a3..22e5b0caf048 100644 --- a/src/ai/testing/ca.hpp +++ b/src/ai/testing/ca.hpp @@ -52,67 +52,6 @@ class goto_phase : public candidate_action { move_result_ptr move_; }; - -//============================================================================ -class aspect_recruitment_phase : public candidate_action { -public: - - aspect_recruitment_phase( rca_context &context, const config &cfg ); - - virtual ~aspect_recruitment_phase(); - - virtual double evaluate(); - - virtual void execute(); -}; - -//============================================================================ - -class recruitment_phase : public candidate_action { -public: - - recruitment_phase( rca_context &context, const config &cfg ); - - virtual ~recruitment_phase(); - - virtual double evaluate(); - - virtual void execute(); - -private: - - bool recruit_usage(const std::string& usage); - - std::map unit_movement_scores_; - std::set not_recommended_units_; - - /** - * Analyze all the units that this side can recruit - * and rate their fighting suitability against enemy units. - * Ratings will be placed in 'unit_combat_scores_', - * with a '0' rating indicating that the unit is 'average' against enemy units, - * negative ratings meaning they are poorly suited, - * and positive ratings meaning they are well suited. - */ - void analyze_potential_recruit_combat(); - - std::map unit_combat_scores_; - - /** - * Rates two unit types for their suitability against each other. - * Returns 0 if the units are equally matched, - * a positive number if a is suited against b, - * and a negative number if b is suited against a. - */ - int compare_unit_types(const unit_type& a, const unit_type& b) const; - - /** - * calculates the average resistance unit type a has against the attacks of - * unit type b. - */ - int average_resistance_against(const unit_type& a, const unit_type& b) const; -}; - //============================================================================ class combat_phase : public candidate_action {