Remove the deprecated recruitment stage, candidate action, and aspect
This commit is contained in:
parent
6972d5170f
commit
ca8cb3bd6a
13 changed files with 3 additions and 1534 deletions
|
@ -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}
|
||||
|
|
|
@ -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<stage_ptr> 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
|
||||
|
|
|
@ -54,7 +54,7 @@ public:
|
|||
|
||||
/**
|
||||
* 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 @@ public:
|
|||
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 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
/** 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
|
||||
|
|
|
@ -145,33 +145,6 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
class config_value_translator<ministage> {
|
||||
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<terrain_filter> {
|
||||
public:
|
||||
|
@ -264,34 +237,6 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
class variant_value_translator<ministage> {
|
||||
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<int> {
|
||||
public:
|
||||
|
|
|
@ -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<unit_ptr>& 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
|
||||
{
|
||||
|
|
|
@ -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 <typename T> class typesafe_aspect; }
|
||||
namespace boost { template <class T> class shared_ptr; }
|
||||
|
@ -310,9 +309,6 @@ public:
|
|||
virtual const std::vector<unit_ptr>& 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 @@ public:
|
|||
}
|
||||
|
||||
|
||||
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 @@ public:
|
|||
virtual const std::vector<unit_ptr>& get_recall_list() const;
|
||||
|
||||
|
||||
virtual stage_ptr get_recruitment(ai_context &context) const;
|
||||
|
||||
|
||||
virtual double get_recruitment_diversity() const;
|
||||
|
||||
|
||||
|
@ -1587,7 +1574,6 @@ private:
|
|||
aspect_type<bool>::typesafe_ptr passive_leader_;
|
||||
aspect_type<bool>::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_;
|
||||
|
|
|
@ -59,8 +59,6 @@ static lg::log_domain log_ai("ai/general");
|
|||
|
||||
namespace ai {
|
||||
|
||||
typedef util::array<map_location,6> 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<std::string,int>::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<std::string> 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<std::string,int>::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<std::string>(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<t_translation::t_terrain, size_t>& terrain =
|
||||
resources::gameboard->map().get_weighted_terrain_frequencies();
|
||||
|
||||
for (std::map<t_translation::t_terrain, size_t>::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<t_translation::t_terrain, size_t>::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<attack_type>& attacks = b.attacks();
|
||||
for (std::vector<attack_type>::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<int>(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<int>(1,std::min<int>(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<std::string> &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<std::string>& recruits = current_team().recruits();
|
||||
std::set<std::string>::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<target> 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<std::string>& recruits = current_team().recruits();
|
||||
|
||||
LOG_AI << "targets: " << targets.size() << "\n";
|
||||
|
||||
std::map<std::string,int> best_scores;
|
||||
|
||||
for(std::set<std::string>::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<target>::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<std::string,int>::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<std::string,int>::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<std::string, double> operator()(const unit_ptr u_ptr) {
|
||||
const unit & u = *u_ptr;
|
||||
std::pair<std::string,int> 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<double>(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_advancement<combat_score_of_advancement)) {
|
||||
best_combat_score_of_advancement = combat_score_of_advancement;
|
||||
best_combat_score_of_advancement_found = true;
|
||||
best_cost = ut->cost();
|
||||
}
|
||||
|
||||
}
|
||||
p.second += xp_ratio*best_combat_score_of_advancement*best_cost/recall_cost;
|
||||
}
|
||||
|
||||
return p;
|
||||
|
||||
}
|
||||
private:
|
||||
const ai_default_recruitment_stage &stage_;
|
||||
};
|
||||
|
||||
|
||||
template <class T, class V>
|
||||
bool smaller_mapped_value(const std::pair<T,V>& a, const std::pair<T,V>& b)
|
||||
{
|
||||
return a.second < b.second;
|
||||
}
|
||||
|
||||
class bad_recalls_remover {
|
||||
public:
|
||||
bad_recalls_remover(const std::map<std::string, int>& unit_combat_scores)
|
||||
: allow_any_(false), best_combat_score_()
|
||||
{
|
||||
std::map<std::string, int>::const_iterator cs = std::min_element(unit_combat_scores.begin(),unit_combat_scores.end(),&smaller_mapped_value<std::string,int>);
|
||||
|
||||
if (cs == unit_combat_scores.end()) {
|
||||
allow_any_ = true;
|
||||
} else {
|
||||
best_combat_score_ = cs->second;
|
||||
}
|
||||
}
|
||||
bool operator()(const std::pair<std::string,double>& 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<std::string,double> &s1, const std::pair<std::string,double> &s2)
|
||||
{
|
||||
return s1.second<s2.second;
|
||||
}
|
||||
};
|
||||
|
||||
static void debug_print_recall_list_scores(const std::vector< std::pair<std::string,double> > &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<std::string,double> >::const_iterator p = recall_list_scores.begin(); p!=recall_list_scores.end();++p) {
|
||||
s << p->first << " ["<<p->second<<"]"<<std::endl;
|
||||
}
|
||||
DBG_AI << s.str();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
bool ai_default_recruitment_stage::analyze_recall_list()
|
||||
{
|
||||
if (current_team().gold() < current_team().recall_cost() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (current_team().recall_list().empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::transform(current_team().recall_list().begin(), current_team().recall_list().end(),
|
||||
std::back_inserter< std::vector <std::pair<std::string,double> > > (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<std::string> 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<map_location>& villages = resources::gameboard->map().villages();
|
||||
for(std::vector<map_location>::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<team>::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<std::string,int> 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;
|
||||
|
|
|
@ -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 @@ private:
|
|||
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<std::string,int> best_usage_;
|
||||
|
||||
config cfg_;
|
||||
|
||||
std::map<std::string,int> maximum_counts_;
|
||||
|
||||
std::set<std::string> not_recommended_units_;
|
||||
|
||||
std::vector<std::pair<std::string,double> > recall_list_scores_;
|
||||
|
||||
recruit_situation_change_observer recruit_situation_change_observer_;
|
||||
|
||||
std::map<std::string,int> unit_combat_scores_;
|
||||
|
||||
std::map<std::string,int> 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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -66,7 +66,6 @@ class candidate_action;
|
|||
class engine;
|
||||
class goal;
|
||||
class known_aspect;
|
||||
class ministage;
|
||||
class stage;
|
||||
|
||||
template<typename T>
|
||||
|
@ -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<std::string, aspect_ptr > aspect_map;
|
||||
|
|
|
@ -88,9 +88,6 @@ static register_stage_factory<testing_ai_default::strategy_formulation_with_rca>
|
|||
static register_stage_factory<testing_ai_default::fallback_to_other_ai>
|
||||
fallback_to_other_ai_factory("testing_ai_default::fallback");
|
||||
|
||||
static register_stage_factory<ai_default_recruitment_stage>
|
||||
ai_default_recruitment_stage_factory("ai_default::recruitment");
|
||||
|
||||
static register_stage_factory<idle_stage>
|
||||
ai_idle_stage_factory("empty");
|
||||
|
||||
|
@ -105,12 +102,6 @@ static register_stage_factory<testing_ai_default::candidate_action_evaluation_lo
|
|||
static register_candidate_action_factory<testing_ai_default::goto_phase>
|
||||
goto_phase_factory("ai_default_rca::goto_phase");
|
||||
|
||||
static register_candidate_action_factory<testing_ai_default::aspect_recruitment_phase>
|
||||
aspect_recruitment_phase_factory("ai_default_rca::aspect_recruitment_phase");
|
||||
|
||||
static register_candidate_action_factory<testing_ai_default::recruitment_phase>
|
||||
recruitment_phase_factory("ai_default_rca::recruitment_phase");
|
||||
|
||||
static register_candidate_action_factory<testing_ai_default::combat_phase>
|
||||
combat_phase_factory("ai_default_rca::combat_phase");
|
||||
|
||||
|
@ -161,12 +152,6 @@ static register_candidate_action_factory<default_recruitment::recruitment>
|
|||
static register_candidate_action_factory<testing_ai_default::goto_phase>
|
||||
old_goto_phase_factory("testing_ai_default::goto_phase");
|
||||
|
||||
static register_candidate_action_factory<testing_ai_default::aspect_recruitment_phase>
|
||||
old_aspect_recruitment_phase_factory("testing_ai_default::aspect_recruitment_phase");
|
||||
|
||||
static register_candidate_action_factory<testing_ai_default::recruitment_phase>
|
||||
old_recruitment_phase_factory("testing_ai_default::recruitment_phase");
|
||||
|
||||
static register_candidate_action_factory<testing_ai_default::combat_phase>
|
||||
old_combat_phase_factory("testing_ai_default::combat_phase");
|
||||
|
||||
|
@ -285,9 +270,6 @@ static register_aspect_factory< composite_aspect<bool> >
|
|||
static register_aspect_factory< composite_aspect<bool> >
|
||||
passive_leader_shares_keep__composite_aspect_factory("passive_leader_shares_keep*composite_aspect");
|
||||
|
||||
static register_aspect_factory< composite_aspect<ministage> >
|
||||
recruitment__composite_aspect_factory("recruitment*composite_aspect");
|
||||
|
||||
static register_aspect_factory< composite_aspect<double> >
|
||||
recruitment_diversity__composite_aspect_factory("recruitment_diversity*composite_aspect");
|
||||
|
||||
|
@ -371,9 +353,6 @@ static register_aspect_factory< standard_aspect<bool> >
|
|||
static register_aspect_factory< standard_aspect<bool> >
|
||||
passive_leader_shares_keep__standard_aspect_factory("passive_leader_shares_keep*standard_aspect");
|
||||
|
||||
static register_aspect_factory< standard_aspect<ministage> >
|
||||
recruitment__standard_aspect_factory("recruitment*standard_aspect");
|
||||
|
||||
static register_aspect_factory< standard_aspect<double> >
|
||||
recruitment_diversity__standard_aspect_factory("recruitment_diversity*standard_aspect");
|
||||
|
||||
|
@ -461,9 +440,6 @@ static register_aspect_factory< standard_aspect<bool> >
|
|||
static register_aspect_factory< standard_aspect<bool> >
|
||||
passive_leader_shares_keep__standard_aspect_factory2("passive_leader_shares_keep*");
|
||||
|
||||
static register_aspect_factory< standard_aspect<ministage> >
|
||||
recruitment__standard_aspect_factory2("recruitment*");
|
||||
|
||||
static register_aspect_factory< standard_aspect<double> >
|
||||
recruitment_diversity__standard_aspect_factory2("recruitment_diversity*");
|
||||
|
||||
|
|
|
@ -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<map_location> 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<team> &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<std::string> 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<map_location>& villages = map_.villages();
|
||||
for(std::vector<map_location>::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<team>::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<std::string,int> 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<std::string> 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<std::string>(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<t_translation::t_terrain, size_t>& terrain =
|
||||
resources::gameboard->map().get_weighted_terrain_frequencies();
|
||||
|
||||
for (std::map<t_translation::t_terrain, size_t>::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<t_translation::t_terrain, size_t>::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<attack_type>& attacks = b.attacks();
|
||||
for (std::vector<attack_type>::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<int>(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<int>(1,std::min<int>(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<std::string,int> best_usage;
|
||||
|
||||
const std::set<std::string>& recruits = current_team().recruits();
|
||||
std::set<std::string>::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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//==============================================================
|
||||
|
||||
|
|
|
@ -52,67 +52,6 @@ private:
|
|||
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<std::string,int> unit_movement_scores_;
|
||||
std::set<std::string> 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<std::string,int> 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 {
|
||||
|
|
Loading…
Add table
Reference in a new issue