Get rid of last user of evaluate_battle_stats: the AI.
May effect performance. ai::choose_weapon now simply uses best_attack_weapon, which is a little smarter about poison and slow. Some code movement because best_attack_weapon needs unit map to indicate reality (for filters it needs exact location of units in combat). AI now uses attack_prediction code, not 50 iterations, to predict results. This will handle specials better, but there may be incidental changes to AI's decision-making.
This commit is contained in:
parent
0f69a33a32
commit
f22beebe51
5 changed files with 160 additions and 889 deletions
513
src/actions.cpp
513
src/actions.cpp
|
@ -224,509 +224,6 @@ gamemap::location under_leadership(const units_map& units,
|
|||
return abil.highest("value").second;
|
||||
}
|
||||
|
||||
double pr_atleast(int m, double p, int n, int d)
|
||||
{
|
||||
// calculate Pr[A does damage in [m,...)], where
|
||||
// p probability to hit, n swings, d damage/swing
|
||||
double P = 0.0;
|
||||
// 0 damage can happen when unit has no attack of this type
|
||||
if(d == 0)
|
||||
return (m <= 0) ? 1.0 : P;
|
||||
for(int k = (m + d - 1)/d; k <= n; ++k) {
|
||||
double r = 1.0;
|
||||
const int k2 = (k > n - k) ? (n - k) : k;
|
||||
for(int i = 0; i < k2; ++i) { r *= (n-i); r /= (k2-i); }
|
||||
P += r * pow(p, k) * pow(1-p, n-k);
|
||||
}
|
||||
return P;
|
||||
}
|
||||
|
||||
double pr_between(int mn, int mx, double p, int n, int d)
|
||||
{
|
||||
// calculate Pr[A does damage in [mn,mx)], where
|
||||
// p probability to hit, n swings, d damage/swing
|
||||
return pr_atleast(mn, p, n, d) - pr_atleast(mx, p, n, d);
|
||||
}
|
||||
|
||||
int reduce(int i, int u, int v)
|
||||
{
|
||||
// i-th swingpair, but reduce since u < v
|
||||
if(i == 0)
|
||||
return i;
|
||||
else
|
||||
return ((i-1) / v)*u + minimum<int>(u, ((i-1) % v) + 1);
|
||||
}
|
||||
|
||||
double pr_kills_during(const int hpa, const int dmga, const double pa,
|
||||
const int swa, const int hpb, const int dmgb, const double pb,
|
||||
const int swb, const int n, const bool second)
|
||||
{
|
||||
if ((swa < swb) && (swa < (n-1) % swb + 1)) // not our move
|
||||
return 0.0;
|
||||
// A kills B during swing n, and is it second?
|
||||
// take into account where one unit doesn't get all n swings
|
||||
const double t1 = pr_between(hpb - dmga, hpb, pa,
|
||||
(swa<swb) ? reduce(n-1, swa, swb) : (n-1), dmga);
|
||||
const int n2 = second ? n : (n - 1);
|
||||
const double t2 = 1.0 - pr_atleast(hpa, pb,
|
||||
(swa>swb) ? reduce(n2, swb, swa) : n2, dmgb);
|
||||
return t1 * pa * t2;
|
||||
}
|
||||
|
||||
battle_stats evaluate_battle_stats(const gamemap& map,
|
||||
std::vector<team>& teams,
|
||||
const gamemap::location& attacker,
|
||||
const gamemap::location& defender,
|
||||
int attack_with,
|
||||
units_map& units,
|
||||
const gamestatus& state,
|
||||
const game_data& gamedata,
|
||||
gamemap::TERRAIN attacker_terrain_override,
|
||||
battle_stats_strings *strings)
|
||||
{
|
||||
battle_stats res;
|
||||
LOG_NG << "Evaluating battle stats...\n";
|
||||
res.attack_with = attack_with;
|
||||
|
||||
if (strings)
|
||||
strings->defend_name = _("none");
|
||||
|
||||
const units_map::const_iterator a = units.find(attacker);
|
||||
const units_map::const_iterator d = units.find(defender);
|
||||
|
||||
wassert(a != units.end());
|
||||
wassert(d != units.end());
|
||||
|
||||
const gamemap::TERRAIN attacker_terrain = attacker_terrain_override ?
|
||||
attacker_terrain_override : map[attacker.x][attacker.y];
|
||||
const gamemap::TERRAIN defender_terrain = map[defender.x][defender.y];
|
||||
|
||||
res.attacker_hp = a->second.hitpoints();
|
||||
res.defender_hp = d->second.hitpoints();
|
||||
|
||||
res.chance_to_hit_attacker = a->second.defense_modifier(attacker_terrain);
|
||||
res.chance_to_hit_defender = d->second.defense_modifier(defender_terrain);
|
||||
|
||||
const std::vector<attack_type>& attacker_attacks = a->second.attacks();
|
||||
const std::vector<attack_type>& defender_attacks = d->second.attacks();
|
||||
|
||||
wassert((unsigned)attack_with < attacker_attacks.size());
|
||||
const attack_type& attack = attacker_attacks[attack_with];
|
||||
|
||||
double best_defend_rating = 0.0;
|
||||
int defend_with = -1;
|
||||
res.ndefends = 0;
|
||||
LOG_NG << "Finding defender weapon...\n";
|
||||
for(int defend_option = 0; defend_option != int(defender_attacks.size()); ++defend_option) {
|
||||
if(defender_attacks[defend_option].range() == attack.range()) {
|
||||
if (defender_attacks[defend_option].defense_weight() > 0) {
|
||||
const attack_type& defend = defender_attacks[defend_option];
|
||||
attack.set_specials_context(attacker,defender,&gamedata,&units,&map,&state,&teams,true,&defend);
|
||||
defend.set_specials_context(attacker,defender,&gamedata,&units,&map,&state,&teams,false,&attack);
|
||||
int d_nattacks = defend.num_attacks();
|
||||
|
||||
unit_ability_list swarm = defend.get_specials("attacks");
|
||||
if(!swarm.empty()) {
|
||||
int swarm_min_attacks = swarm.highest("attacks_min").first;
|
||||
int swarm_max_attacks = swarm.highest("attacks_max",d_nattacks).first;
|
||||
int hitp = d->second.hitpoints();
|
||||
int mhitp = d->second.max_hitpoints();
|
||||
|
||||
d_nattacks = swarm_min_attacks + (swarm_max_attacks - swarm_min_attacks) * hitp / mhitp;
|
||||
}
|
||||
|
||||
// calculate damage
|
||||
int bonus = 100;
|
||||
int divisor = 100;
|
||||
|
||||
int base_damage = defend.damage();
|
||||
int resistance_modifier = a->second.damage_from(defend,true,a->first);
|
||||
bool backstab = backstab_check(d->first,a->first,units,teams);
|
||||
{ // modify damage
|
||||
unit_ability_list dmg_specials = defend.get_specials("damage");
|
||||
unit_abilities::effect dmg_effect(dmg_specials,base_damage,backstab);
|
||||
base_damage = dmg_effect.get_composite_value();
|
||||
}
|
||||
|
||||
const int tod_modifier = combat_modifier(state,units,d->first,d->second.alignment(),map);
|
||||
bonus += tod_modifier;
|
||||
|
||||
int leader_bonus = 0;
|
||||
if (under_leadership(units, defender, &leader_bonus).valid()) {
|
||||
bonus += leader_bonus;
|
||||
|
||||
}
|
||||
if (utils::string_bool(d->second.get_state("slowed"))) {
|
||||
divisor *= 2;
|
||||
}
|
||||
|
||||
bonus *= resistance_modifier;
|
||||
divisor *= 100;
|
||||
const int final_damage = round_damage(base_damage, bonus, divisor);
|
||||
|
||||
const double rating = a->second.damage_from(defender_attacks[defend_option],true,a->first)
|
||||
*final_damage
|
||||
*d_nattacks
|
||||
*defender_attacks[defend_option].defense_weight();
|
||||
if(defend_with == -1 || rating > best_defend_rating) {
|
||||
best_defend_rating = rating;
|
||||
defend_with = defend_option;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.defend_with = defend_with;
|
||||
config tmp_config;
|
||||
static attack_type no_weapon(tmp_config,"fake_attack","");
|
||||
const attack_type& defend = defend_with == -1 ? no_weapon : defender_attacks[defend_with];
|
||||
attack.set_specials_context(attacker,defender,&gamedata,&units,&map,&state,&teams,true,&defend);
|
||||
defend.set_specials_context(attacker,defender,&gamedata,&units,&map,&state,&teams,false,&attack);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
LOG_NG << "getting weapon specials...\n";
|
||||
|
||||
static const std::string to_the_death_string("berserk");
|
||||
res.rounds = attack.get_specials(to_the_death_string).highest("value").first;
|
||||
res.defender_strikes_first = false;
|
||||
|
||||
unit_ability_list plague = attack.get_specials("plague");
|
||||
static const std::string plague_string("plague");
|
||||
res.attacker_plague = !utils::string_bool(d->second.get_state("not_living")) &&
|
||||
(!plague.empty()) &&
|
||||
strcmp(d->second.undead_variation().c_str(),"null") &&
|
||||
!map.is_village(defender);
|
||||
|
||||
if(!plague.empty()) {
|
||||
if((*plague.cfgs.front().first)["type"] == "") {
|
||||
res.attacker_plague_type = a->second.id();
|
||||
} else {
|
||||
res.attacker_plague_type = (*plague.cfgs.front().first)["type"];
|
||||
}
|
||||
}
|
||||
res.defender_plague = false;
|
||||
|
||||
static const std::string slow_string("slow");
|
||||
res.attacker_slows = attack.get_special_bool(slow_string);
|
||||
static const std::string poison_string("poison");
|
||||
res.attacker_poisons = attack.get_special_bool(poison_string);
|
||||
static const std::string stones_string("stones");
|
||||
res.attacker_stones = attack.get_special_bool(stones_string);
|
||||
|
||||
{ // modify chance to hit
|
||||
bool backstab = backstab_check(a->first,d->first,units,teams);
|
||||
unit_ability_list cth_specials = attack.get_specials("chance_to_hit");
|
||||
unit_abilities::effect cth_effects(cth_specials,res.chance_to_hit_defender,backstab);
|
||||
res.chance_to_hit_defender = cth_effects.get_composite_value();
|
||||
}
|
||||
|
||||
// compute swarm attacks;
|
||||
unit_ability_list swarm = attack.get_specials("attacks");
|
||||
if(!swarm.empty()) {
|
||||
int swarm_min_attacks = swarm.highest("attacks_min").first;
|
||||
int swarm_max_attacks = swarm.highest("attacks_max",attack.num_attacks()).first;
|
||||
int hitp = a->second.hitpoints();
|
||||
int mhitp = a->second.max_hitpoints();
|
||||
|
||||
res.nattacks = swarm_min_attacks + (swarm_max_attacks - swarm_min_attacks) * hitp / mhitp;
|
||||
|
||||
} else {
|
||||
res.nattacks = attack.num_attacks();
|
||||
}
|
||||
|
||||
if (strings) {
|
||||
strings->attack_name = attack.name();
|
||||
strings->attack_type = egettext(attack.type().c_str());
|
||||
strings->attack_special = attack.weapon_specials();
|
||||
strings->attack_icon = attack.icon();
|
||||
|
||||
strings->range = gettext(N_(attack.range().c_str()));
|
||||
}
|
||||
|
||||
const bool counterattack = defend_with != -1;
|
||||
|
||||
static const std::string EMPTY_COLUMN = std::string(1, COLUMN_SEPARATOR) + ' ' + COLUMN_SEPARATOR;
|
||||
|
||||
res.damage_attacker_takes = 0;
|
||||
res.amount_attacker_drains = 0;
|
||||
res.amount_defender_drains = 0;
|
||||
if (counterattack) {
|
||||
res.rounds = maximum<int>(res.rounds,defend.get_specials(to_the_death_string).highest("value").first);
|
||||
|
||||
bool backstab = backstab_check(d->first,a->first,units,teams);
|
||||
{ // modify chance to hit
|
||||
unit_ability_list cth_specials = defend.get_specials("chance_to_hit");
|
||||
unit_abilities::effect cth_effects(cth_specials,res.chance_to_hit_attacker,backstab);
|
||||
res.chance_to_hit_attacker = cth_effects.get_composite_value();
|
||||
}
|
||||
|
||||
int bonus = 100;
|
||||
int divisor = 100;
|
||||
|
||||
int base_damage = defend.damage();
|
||||
int resistance_modifier = a->second.damage_from(defend,true,a->first);
|
||||
|
||||
{ // modify damage
|
||||
unit_ability_list dmg_specials = defend.get_specials("damage");
|
||||
unit_abilities::effect dmg_effects(dmg_specials,base_damage,backstab);
|
||||
base_damage = dmg_effects.get_composite_value();
|
||||
}
|
||||
|
||||
if (strings) {
|
||||
std::stringstream str_base;
|
||||
str_base << _("base damage") << COLUMN_SEPARATOR << base_damage;
|
||||
strings->defend_calculations.push_back(str_base.str());
|
||||
}
|
||||
|
||||
const int tod_modifier = combat_modifier(state,units,d->first,d->second.alignment(),map);
|
||||
bonus += tod_modifier;
|
||||
|
||||
if (strings && tod_modifier != 0) {
|
||||
std::stringstream str_mod;
|
||||
const time_of_day& tod = timeofday_at(state,units,d->first,map);
|
||||
str_mod << tod.name << EMPTY_COLUMN << (tod_modifier > 0 ? "+" : "") << tod_modifier << '%';
|
||||
strings->defend_calculations.push_back(str_mod.str());
|
||||
}
|
||||
|
||||
int leader_bonus = 0;
|
||||
if (under_leadership(units, defender, &leader_bonus).valid()) {
|
||||
bonus += leader_bonus;
|
||||
|
||||
if (strings) {
|
||||
std::stringstream str;
|
||||
str << _("leadership") << EMPTY_COLUMN << '+' << leader_bonus << '%';
|
||||
strings->defend_calculations.push_back(str.str());
|
||||
}
|
||||
}
|
||||
/*
|
||||
if (charge) {
|
||||
bonus *= 2;
|
||||
|
||||
if (strings) {
|
||||
std::stringstream str;
|
||||
str << _("charge") << EMPTY_COLUMN << _("Doubled");
|
||||
strings->defend_calculations.push_back(str.str());
|
||||
}
|
||||
}
|
||||
*/
|
||||
if (utils::string_bool(d->second.get_state("slowed"))) {
|
||||
divisor *= 2;
|
||||
if (strings) {
|
||||
std::stringstream str;
|
||||
str << _("slowed") << EMPTY_COLUMN << _("Halved");
|
||||
strings->defend_calculations.push_back(str.str());
|
||||
}
|
||||
}
|
||||
|
||||
if (strings && resistance_modifier != 100) {
|
||||
const int resist = resistance_modifier - 100;
|
||||
std::stringstream str_resist;
|
||||
str_resist << gettext(resist < 0 ? N_("attacker resistance vs") : N_("attacker vulnerability vs"))
|
||||
<< ' ' << gettext(defend.type().c_str()) << EMPTY_COLUMN
|
||||
<< (resist > 0 ? "+" : "") << resist << '%';
|
||||
strings->defend_calculations.push_back(str_resist.str());
|
||||
}
|
||||
|
||||
bonus *= resistance_modifier;
|
||||
divisor *= 100;
|
||||
const int final_damage = round_damage(base_damage, bonus, divisor);
|
||||
res.damage_attacker_takes = final_damage;
|
||||
|
||||
if (strings) {
|
||||
const int difference = final_damage - base_damage;
|
||||
std::stringstream str;
|
||||
str << _("total damage") << COLUMN_SEPARATOR << res.damage_attacker_takes
|
||||
<< COLUMN_SEPARATOR << (difference >= 0 ? "+" : "")
|
||||
<< difference;
|
||||
strings->defend_calculations.push_back(str.str());
|
||||
}
|
||||
|
||||
// compute swarm attacks;
|
||||
unit_ability_list swarm = defend.get_specials("attacks");
|
||||
if(!swarm.empty()) {
|
||||
int swarm_min_attacks = swarm.highest("attacks_min").first;
|
||||
int swarm_max_attacks = swarm.highest("attacks_max",defend.num_attacks()).first;
|
||||
int hitp = d->second.hitpoints();
|
||||
int mhitp = d->second.max_hitpoints();
|
||||
|
||||
res.ndefends = swarm_min_attacks + (swarm_max_attacks - swarm_min_attacks) * hitp / mhitp;
|
||||
|
||||
} else {
|
||||
res.ndefends = defend.num_attacks();
|
||||
}
|
||||
|
||||
if (strings) {
|
||||
strings->defend_name = defend.name();
|
||||
strings->defend_type = egettext(defend.type().c_str());
|
||||
strings->defend_special = defend.weapon_specials();
|
||||
strings->defend_icon = defend.icon();
|
||||
}
|
||||
|
||||
//if the defender drains, and the attacker is a living creature, then
|
||||
//the defender will drain for half the damage it does
|
||||
if (defend.get_special_bool("drains") && !utils::string_bool(a->second.get_state("not_living"))) {
|
||||
res.amount_defender_drains = res.damage_attacker_takes/2;
|
||||
}
|
||||
|
||||
unit_ability_list defend_plague = attack.get_specials("plague");
|
||||
res.defender_plague = !utils::string_bool(a->second.get_state("not_living")) &&
|
||||
(!defend_plague.empty()) &&
|
||||
strcmp(a->second.undead_variation().c_str(),"null") &&
|
||||
!map.is_village(attacker);
|
||||
if(!plague.empty()) {
|
||||
if((*plague.cfgs.front().first)["type"] == "") {
|
||||
res.defender_plague_type = d->second.id();
|
||||
} else {
|
||||
res.defender_plague_type = (*plague.cfgs.front().first)["type"];
|
||||
}
|
||||
}
|
||||
|
||||
res.defender_slows = (defend.get_special_bool(slow_string));
|
||||
res.defender_poisons = (defend.get_special_bool(poison_string));
|
||||
res.defender_stones = (defend.get_special_bool(stones_string));
|
||||
|
||||
static const std::string first_strike = "firststrike";
|
||||
res.defender_strikes_first = defend.get_special_bool(first_strike) && !attack.get_special_bool(first_strike);
|
||||
}
|
||||
|
||||
int bonus = 100;
|
||||
int divisor = 100;
|
||||
|
||||
int base_damage = attack.damage();
|
||||
int resistance_modifier = d->second.damage_from(attack,false,d->first);
|
||||
|
||||
{ // modify damage
|
||||
bool backstab = backstab_check(a->first,d->first,units,teams);
|
||||
{ // modify damage
|
||||
unit_ability_list dmg_specials = attack.get_specials("damage");
|
||||
unit_abilities::effect dmg_effect(dmg_specials,base_damage,backstab);
|
||||
base_damage = dmg_effect.get_composite_value();
|
||||
}
|
||||
}
|
||||
|
||||
if (strings) {
|
||||
std::stringstream str_base;
|
||||
str_base << _("base damage") << COLUMN_SEPARATOR << base_damage << COLUMN_SEPARATOR;
|
||||
strings->attack_calculations.push_back(str_base.str());
|
||||
}
|
||||
|
||||
const int tod_modifier = combat_modifier(state,units,a->first,a->second.alignment(),map);
|
||||
|
||||
bonus += tod_modifier;
|
||||
|
||||
if (strings && tod_modifier != 0) {
|
||||
std::stringstream str_mod;
|
||||
const time_of_day& tod = timeofday_at(state,units,a->first,map);
|
||||
str_mod << tod.name << EMPTY_COLUMN << (tod_modifier > 0 ? "+" : "") << tod_modifier << '%';
|
||||
strings->attack_calculations.push_back(str_mod.str());
|
||||
}
|
||||
|
||||
int leader_bonus = 0;
|
||||
if (under_leadership(units,attacker,&leader_bonus).valid()) {
|
||||
bonus += leader_bonus;
|
||||
|
||||
if (strings) {
|
||||
std::stringstream str;
|
||||
str << _("leadership") << EMPTY_COLUMN << '+' << leader_bonus << '%';
|
||||
strings->attack_calculations.push_back(str.str());
|
||||
}
|
||||
}
|
||||
if (strings && resistance_modifier != 100) {
|
||||
const int resist = resistance_modifier - 100;
|
||||
std::stringstream str_resist;
|
||||
str_resist << gettext(resist < 0 ? N_("defender resistance vs") : N_("defender vulnerability vs"))
|
||||
<< ' ' << gettext(attack.type().c_str());
|
||||
|
||||
str_resist << EMPTY_COLUMN
|
||||
<< (resist > 0 ? "+" : "") << resist << '%';
|
||||
strings->attack_calculations.push_back(str_resist.str());
|
||||
|
||||
}
|
||||
|
||||
if (utils::string_bool(a->second.get_state("slowed"))) {
|
||||
divisor *= 2;
|
||||
|
||||
if (strings) {
|
||||
std::stringstream str;
|
||||
str << _("slowed") << EMPTY_COLUMN << _("Halved");
|
||||
strings->attack_calculations.push_back(str.str());
|
||||
}
|
||||
}
|
||||
|
||||
bonus *= resistance_modifier;
|
||||
divisor *= 100;
|
||||
const int final_damage = round_damage(base_damage, bonus, divisor);
|
||||
res.damage_defender_takes = final_damage;
|
||||
|
||||
if (strings) {
|
||||
const int difference = final_damage - base_damage;
|
||||
std::stringstream str;
|
||||
str << _("total damage") << COLUMN_SEPARATOR << res.damage_defender_takes
|
||||
<< COLUMN_SEPARATOR << (difference >= 0 ? "+" : "")
|
||||
<< difference;
|
||||
strings->attack_calculations.push_back(str.str());
|
||||
}
|
||||
|
||||
//if the attacker drains, and the defender is a living creature, then
|
||||
//the attacker will drain for half the damage it does
|
||||
if(attack.get_special_bool("drains") && utils::string_bool(d->second.get_state("not_living"))) {
|
||||
res.amount_attacker_drains = res.damage_defender_takes/2;
|
||||
}
|
||||
|
||||
// FIXME: doesn't take into account berserk+slow or drain
|
||||
if (strings && res.amount_attacker_drains == 0 &&
|
||||
res.amount_defender_drains == 0 &&
|
||||
!(res.rounds &&
|
||||
(res.attacker_slows || res.defender_slows)))
|
||||
{
|
||||
const int maxrounds = (res.rounds ? res.rounds : 1);
|
||||
const int hpa = res.attacker_hp;
|
||||
const int hpb = res.defender_hp;
|
||||
const int dmga = res.damage_defender_takes;
|
||||
const int dmgb = res.damage_attacker_takes;
|
||||
const double pa = res.chance_to_hit_defender/100.0;
|
||||
const double pb = res.chance_to_hit_attacker/100.0;
|
||||
const int swa = res.nattacks;
|
||||
const int swb = res.ndefends;
|
||||
double P1 = 0;
|
||||
|
||||
for(int n = 1; n <= maxrounds*maximum<int>(swa,swb); ++n)
|
||||
P1 += pr_kills_during(hpa, dmga, pa, swa,
|
||||
hpb, dmgb, pb, swb, n, res.defender_strikes_first);
|
||||
const double P3 = (1.0 - pr_atleast(hpb,pa,maxrounds*swa,dmga))
|
||||
* (1.0 - pr_atleast(hpa,pb,maxrounds*swb,dmgb));
|
||||
std::stringstream str;
|
||||
if (P3 > 0.99) {
|
||||
str << _("(both should survive)") << EMPTY_COLUMN;
|
||||
} else {
|
||||
str << _("% Pr[kills/killed by/both survive]")
|
||||
<< EMPTY_COLUMN << (int)(P1*100+0.5)
|
||||
<< '/' << (int)((1-P1-P3)*100+0.5)
|
||||
<< '/' << (int)(P3*100+0.5);
|
||||
}
|
||||
strings->attack_calculations.push_back(str.str());
|
||||
}
|
||||
LOG_NG << "done...\n";
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
battle_context::battle_context(const gamemap& map, const std::vector<team>& teams, const std::map<gamemap::location,unit>& units,
|
||||
const gamestatus& status, const game_data& gamedata,
|
||||
const gamemap::location& attacker_loc, const gamemap::location& defender_loc,
|
||||
|
@ -958,10 +455,16 @@ unsigned int battle_context::rate_attacker_weapon(double attack_weight) const
|
|||
|
||||
// Bias towards more damaging attacks.
|
||||
attack_weight += attacker_stats_->num_blows * attacker_stats_->damage;
|
||||
if (attacker_stats_->poisons)
|
||||
attack_weight += game_config::poison_amount;
|
||||
|
||||
attack_weight *= attacker_stats_->num_blows * attacker_stats_->damage;
|
||||
if (defender_stats_->num_blows * defender_stats_->damage)
|
||||
attack_weight /= defender_stats_->num_blows * defender_stats_->damage;
|
||||
if (defender_stats_->num_blows * defender_stats_->damage) {
|
||||
unsigned def_weight = defender_stats_->num_blows * defender_stats_->damage;
|
||||
if (attacker_stats_->slows)
|
||||
def_weight /= 2;
|
||||
attack_weight /= def_weight;
|
||||
}
|
||||
|
||||
return (unsigned int)(attack_weight * 100);
|
||||
}
|
||||
|
|
|
@ -47,62 +47,8 @@ std::string recruit_unit(const gamemap& map, int team, unit_map& units,
|
|||
unit u, gamemap::location& recruit_location,
|
||||
display *disp=NULL, bool need_castle=true, bool full_movement=false);
|
||||
|
||||
//a structure which defines all the statistics for a potential
|
||||
//battle that could take place.
|
||||
struct battle_stats
|
||||
{
|
||||
int attacker_hp, defender_hp;
|
||||
int chance_to_hit_attacker, chance_to_hit_defender;
|
||||
int damage_attacker_takes, damage_defender_takes;
|
||||
int amount_attacker_drains, amount_defender_drains;
|
||||
int ndefends, nattacks;
|
||||
int attack_with, defend_with;
|
||||
bool attacker_plague, defender_plague;
|
||||
std::string attacker_plague_type, defender_plague_type;
|
||||
bool attacker_slows, defender_slows;
|
||||
int rounds;
|
||||
bool defender_strikes_first;
|
||||
bool attacker_poisons, defender_poisons;
|
||||
bool attacker_stones, defender_stones;
|
||||
};
|
||||
|
||||
struct battle_stats_strings
|
||||
{
|
||||
std::string attack_name, defend_name;
|
||||
std::string attack_type, defend_type;
|
||||
std::string attack_special, defend_special;
|
||||
std::string range;
|
||||
std::string attack_icon, defend_icon;
|
||||
std::vector<std::string> attack_calculations, defend_calculations;
|
||||
};
|
||||
|
||||
//evaluate_battle_stats: a function which, if given an attacker
|
||||
//and defender, and the number of a weapon to use, will report
|
||||
//the statistics if that battle were to take place.
|
||||
//
|
||||
//attacker_terrain_override allows a different terrain to the
|
||||
//one currently stood on by the attacker to be used in calculating
|
||||
//the statistics. This is useful if one wants to look at the
|
||||
//statistics if an attacker were to attack from one of several
|
||||
//different locations.
|
||||
//
|
||||
//if include_strings is false, then none of the strings in
|
||||
//battle_stats will be populated, and the function will run
|
||||
//substantially faster.
|
||||
battle_stats evaluate_battle_stats(const gamemap& map,
|
||||
std::vector<team>& teams,
|
||||
const gamemap::location& attacker,
|
||||
const gamemap::location& defender,
|
||||
int attack_with,
|
||||
units_map& units,
|
||||
const gamestatus& state,
|
||||
const game_data& gamedata,
|
||||
gamemap::TERRAIN attacker_terrain_override = 0,
|
||||
battle_stats_strings *strings = NULL);
|
||||
|
||||
|
||||
/* The battle_context class computes the statistics of a battle between an
|
||||
* attacker and a defender unit. It is meant as a replacement of battle_stats.
|
||||
* attacker and a defender unit.
|
||||
*/
|
||||
class battle_context
|
||||
{
|
||||
|
|
90
src/ai.cpp
90
src/ai.cpp
|
@ -96,26 +96,10 @@ protected:
|
|||
}
|
||||
|
||||
int choose_weapon(const location& attacker, const location& defender) {
|
||||
const unit_map::const_iterator att = get_info().units.find(attacker);
|
||||
wassert(att != get_info().units.end());
|
||||
|
||||
const std::vector<attack_type>& attacks = att->second.attacks();
|
||||
|
||||
double best_attack_rating = 0.0;
|
||||
int best_attack = -1;
|
||||
for(size_t n = 0; n != attacks.size(); ++n) {
|
||||
if (attacks[n].attack_weight() > 0){
|
||||
const battle_stats stats = evaluate_battle_stats(get_info().map, get_info().teams, attacker, defender, n, get_info().units, get_info().state, get_info().gameinfo);
|
||||
const double attack_rating = stats.damage_defender_takes
|
||||
*stats.nattacks*stats.chance_to_hit_defender*attacks[n].attack_weight();
|
||||
if(best_attack == -1 || attack_rating > best_attack_rating) {
|
||||
best_attack = n;
|
||||
best_attack_rating = attack_rating;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return best_attack;
|
||||
std::vector<battle_context> bc_vector;
|
||||
return best_attack_weapon(get_info().map, get_info().teams,
|
||||
get_info().units, get_info().state,
|
||||
get_info().gameinfo, attacker, defender, bc_vector);
|
||||
}
|
||||
|
||||
void get_villages() {
|
||||
|
@ -593,8 +577,9 @@ gamemap::location ai::move_unit(location from, location to, std::map<location,pa
|
|||
const unit_map::const_iterator itor = units_.find(*adj_i);
|
||||
if(itor != units_.end() && current_team().is_enemy(itor->second.side()) &&
|
||||
!itor->second.incapacitated()) {
|
||||
battle_stats stats;
|
||||
const int weapon = choose_weapon(res,itor->first,stats,0);
|
||||
std::vector<battle_context> bc_vector;
|
||||
const int weapon = best_attack_weapon(map_, teams_, units_, state_,
|
||||
gameinfo_, res, *adj_i, bc_vector);
|
||||
attack_enemy(res,itor->first,weapon);
|
||||
break;
|
||||
}
|
||||
|
@ -1373,34 +1358,6 @@ bool ai::move_to_targets(std::map<gamemap::location,paths>& possible_moves, move
|
|||
|
||||
LOG_AI << "move: " << move.first << " -> " << move.second << '\n';
|
||||
|
||||
//search to see if there are any enemy units next
|
||||
//to the tile which really should be attacked once the move is done.
|
||||
gamemap::location adj[6];
|
||||
get_adjacent_tiles(move.second,adj);
|
||||
battle_stats bat_stats;
|
||||
gamemap::location target;
|
||||
int weapon = -1;
|
||||
|
||||
for(int n = 0; n != 6; ++n) {
|
||||
const unit_map::iterator enemy = find_visible_unit(units_,adj[n],
|
||||
map_,
|
||||
teams_,current_team());
|
||||
|
||||
if(enemy != units_.end() &&
|
||||
current_team().is_enemy(enemy->second.side()) && !enemy->second.incapacitated()) {
|
||||
const int res = choose_weapon(move.first,adj[n],bat_stats,
|
||||
map_[move.second.x][move.second.y]);
|
||||
|
||||
//current behavior is to only make risk-free attacks
|
||||
if(bat_stats.damage_attacker_takes == 0) {
|
||||
weapon = res;
|
||||
target = adj[n];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const location arrived_at = move_unit(move.first,move.second,possible_moves);
|
||||
|
||||
//we didn't arrive at our intended destination. We return true, meaning that
|
||||
|
@ -1410,12 +1367,35 @@ bool ai::move_to_targets(std::map<gamemap::location,paths>& possible_moves, move
|
|||
return true;
|
||||
}
|
||||
|
||||
const unit_map::const_iterator u_it = units_.find(move.second);
|
||||
const unit_map::const_iterator un_it = units_.find(arrived_at);
|
||||
const unit_map::const_iterator u_it = units_.find(arrived_at);
|
||||
// Event could have done anything: check
|
||||
if (u_it == units_.end() || u_it->second.incapacitated()) {
|
||||
LOG_STREAM(warn, ai) << "stolen or incapacitated\n";
|
||||
} else {
|
||||
//search to see if there are any enemy units next
|
||||
//to the tile which really should be attacked now the move is done.
|
||||
gamemap::location adj[6];
|
||||
get_adjacent_tiles(arrived_at,adj);
|
||||
gamemap::location target;
|
||||
|
||||
//if we're going to attack someone
|
||||
if(u_it != units_.end() && !u_it->second.incapacitated() && weapon != -1) {
|
||||
attack_enemy(move.second,target,weapon);
|
||||
for(int n = 0; n != 6; ++n) {
|
||||
const unit_map::iterator enemy = find_visible_unit(units_,adj[n],
|
||||
map_,
|
||||
teams_,current_team());
|
||||
|
||||
if(enemy != units_.end() &&
|
||||
current_team().is_enemy(enemy->second.side()) && !enemy->second.incapacitated()) {
|
||||
std::vector<battle_context> bc_vector;
|
||||
// FIXME: Must inform this we are cautious for weapon selection.
|
||||
const int res = best_attack_weapon(map_, teams_, units_, state_, gameinfo_,
|
||||
arrived_at, adj[n], bc_vector);
|
||||
//current behavior is to only make risk-free attacks
|
||||
if (bc_vector[res].get_defender_stats().damage == 0) {
|
||||
attack_enemy(arrived_at,adj[n],res);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//don't allow any other units to move onto the tile our unit
|
||||
|
|
12
src/ai.hpp
12
src/ai.hpp
|
@ -26,9 +26,6 @@ public:
|
|||
|
||||
virtual void play_turn();
|
||||
|
||||
virtual int choose_weapon(const location& att, const location& def,
|
||||
battle_stats& cur_stats, gamemap::TERRAIN terrain, bool use_cache=false);
|
||||
|
||||
struct target {
|
||||
enum TYPE { VILLAGE, LEADER, EXPLICIT, THREAT, BATTLE_AID, MASS, SUPPORT };
|
||||
|
||||
|
@ -106,9 +103,12 @@ protected:
|
|||
|
||||
struct attack_analysis
|
||||
{
|
||||
void analyze(const gamemap& map, std::map<location,unit>& units, int sims,
|
||||
class ai& ai_obj, const move_map& dstsrc, const move_map& srcdst,
|
||||
const move_map& enemy_dstsrc);
|
||||
void analyze(const gamemap& map, unit_map& units,
|
||||
const std::vector<team>& teams,
|
||||
const gamestatus& status, const game_data& gamedata,
|
||||
ai& ai_obj,
|
||||
const ai::move_map& dstsrc, const ai::move_map& srcdst,
|
||||
const ai::move_map& enemy_dstsrc);
|
||||
|
||||
double rating(double aggression, class ai& ai_obj) const;
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include "global.hpp"
|
||||
|
||||
#include "ai.hpp"
|
||||
#include "attack_prediction.hpp"
|
||||
#include "game_config.hpp"
|
||||
#include "gamestatus.hpp"
|
||||
#include "wassert.hpp"
|
||||
|
@ -227,7 +228,7 @@ void ai::do_attack_analysis(
|
|||
|
||||
cur_analysis.is_surrounded = is_surrounded;
|
||||
|
||||
cur_analysis.analyze(map_, units_, 50, *this, dstsrc, srcdst, enemy_dstsrc);
|
||||
cur_analysis.analyze(map_, units_, teams_, state_, gameinfo_, *this, dstsrc, srcdst, enemy_dstsrc);
|
||||
|
||||
if(cur_analysis.rating(current_team().aggression(),*this) > rating_to_beat) {
|
||||
|
||||
|
@ -260,7 +261,6 @@ struct battle_type {
|
|||
const gamemap::location defender;
|
||||
const gamemap::TERRAIN terrain;
|
||||
int weapon;
|
||||
battle_stats stats;
|
||||
};
|
||||
|
||||
bool operator<(const battle_type& a, const battle_type& b)
|
||||
|
@ -279,83 +279,10 @@ bool operator==(const battle_type& a, const battle_type& b)
|
|||
|
||||
std::set<battle_type> weapon_choice_cache;
|
||||
|
||||
int ai::choose_weapon(const location& att, const location& def,
|
||||
battle_stats& cur_stats, gamemap::TERRAIN terrain, bool use_cache)
|
||||
{
|
||||
const std::map<location,unit>::const_iterator itor = units_.find(att);
|
||||
if(itor == units_.end())
|
||||
return -1;
|
||||
|
||||
static int cache_hits = 0;
|
||||
static int cache_misses = 0;
|
||||
|
||||
if(use_cache == false) {
|
||||
weapon_choice_cache.clear();
|
||||
}
|
||||
|
||||
battle_type battle(att,def,terrain);
|
||||
const std::set<battle_type>::const_iterator cache_itor = weapon_choice_cache.find(battle);
|
||||
|
||||
if(cache_itor != weapon_choice_cache.end()) {
|
||||
wassert(*cache_itor == battle);
|
||||
|
||||
++cache_hits;
|
||||
cur_stats = cache_itor->stats;
|
||||
|
||||
if(!(size_t(cache_itor->weapon) < itor->second.attacks().size())) {
|
||||
LOG_STREAM(err, ai) << "cached illegal weapon: " << cache_itor->weapon
|
||||
<< "/" << itor->second.attacks().size() << "\n";
|
||||
}
|
||||
|
||||
wassert(size_t(cache_itor->weapon) < itor->second.attacks().size());
|
||||
return cache_itor->weapon;
|
||||
}
|
||||
|
||||
++cache_misses;
|
||||
|
||||
if((cache_misses%100) == 0) {
|
||||
LOG_AI << "cache_stats: " << cache_hits << ":" << cache_misses << " " << weapon_choice_cache.size() << "\n";
|
||||
}
|
||||
|
||||
int current_choice = -1;
|
||||
double current_rating = 0.0;
|
||||
const std::vector<attack_type>& attacks = itor->second.attacks();
|
||||
wassert(!attacks.empty());
|
||||
|
||||
const unit_map::const_iterator d_itor = units_.find(def);
|
||||
int d_hitpoints = d_itor->second.hitpoints();
|
||||
int a_hitpoints = itor->second.hitpoints();
|
||||
|
||||
for(size_t a = 0; a != attacks.size(); ++a) {
|
||||
if (attacks[a].attack_weight() > 0){
|
||||
const battle_stats stats = evaluate_battle_stats(map_, teams_, att, def, a, units_,
|
||||
state_, gameinfo_, terrain);
|
||||
|
||||
//TODO: improve this rating formula!
|
||||
const double rating =
|
||||
(double(stats.chance_to_hit_defender)/100.0)*
|
||||
minimum<int>(stats.damage_defender_takes,d_hitpoints)*stats.nattacks *
|
||||
attacks[a].attack_weight() -
|
||||
(double(stats.chance_to_hit_attacker)/100.0)*
|
||||
minimum<int>(stats.damage_attacker_takes,a_hitpoints)*stats.ndefends;
|
||||
if(rating > current_rating || current_choice == -1) {
|
||||
current_choice = a;
|
||||
current_rating = rating;
|
||||
cur_stats = stats;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wassert(size_t(current_choice) < attacks.size());
|
||||
|
||||
battle.stats = cur_stats;
|
||||
battle.weapon = current_choice;
|
||||
weapon_choice_cache.insert(battle);
|
||||
|
||||
return current_choice;
|
||||
}
|
||||
|
||||
void ai::attack_analysis::analyze(const gamemap& map, unit_map& units, int num_sims, ai& ai_obj,
|
||||
void ai::attack_analysis::analyze(const gamemap& map, unit_map& units,
|
||||
const std::vector<team>& teams,
|
||||
const gamestatus& status, const game_data& gamedata,
|
||||
ai& ai_obj,
|
||||
const ai::move_map& dstsrc, const ai::move_map& srcdst,
|
||||
const ai::move_map& enemy_dstsrc)
|
||||
{
|
||||
|
@ -381,210 +308,125 @@ void ai::attack_analysis::analyze(const gamemap& map, unit_map& units, int num_s
|
|||
double(defend_it->second.max_experience()))*target_value;
|
||||
target_starting_damage = defend_it->second.max_hitpoints() -
|
||||
defend_it->second.hitpoints();
|
||||
chance_to_kill = 0.0;
|
||||
avg_damage_inflicted = 0.0;
|
||||
avg_damage_taken = 0.0;
|
||||
resources_used = 0.0;
|
||||
terrain_quality = 0.0;
|
||||
avg_losses = 0.0;
|
||||
|
||||
const int target_max_hp = defend_it->second.max_hitpoints();
|
||||
const int target_hp = defend_it->second.hitpoints();
|
||||
static std::vector<int> hitpoints;
|
||||
static std::vector<battle_stats> stats;
|
||||
|
||||
hitpoints.clear();
|
||||
stats.clear();
|
||||
weapons.clear();
|
||||
|
||||
std::vector<std::pair<location,location> >::const_iterator m;
|
||||
for(m = movements.begin(); m != movements.end(); ++m) {
|
||||
battle_stats bat_stats;
|
||||
const int weapon = ai_obj.choose_weapon(m->first,target, bat_stats, map[m->second.x][m->second.y],true);
|
||||
|
||||
wassert(weapon != -1);
|
||||
weapons.push_back(weapon);
|
||||
|
||||
stats.push_back(bat_stats);
|
||||
hitpoints.push_back(units.find(m->first)->second.hitpoints());
|
||||
}
|
||||
|
||||
for(int j = 0; j != num_sims; ++j) {
|
||||
|
||||
int defenderxp = 0;
|
||||
|
||||
bool defender_slowed = utils::string_bool(defend_it->second.get_state("slowed"));
|
||||
|
||||
int defhp = target_hp;
|
||||
for(size_t i = 0; i != movements.size() && defhp; ++i) {
|
||||
battle_stats& stat = stats[i];
|
||||
int atthp = hitpoints[i];
|
||||
|
||||
int attacks = stat.nattacks;
|
||||
int defends = stat.ndefends;
|
||||
|
||||
unit_map::const_iterator att = units.find(movements[i].first);
|
||||
double cost = att->second.cost();
|
||||
|
||||
if(att->second.can_recruit()) {
|
||||
uses_leader = true;
|
||||
}
|
||||
|
||||
const bool on_village = map.is_village(movements[i].second);
|
||||
bool attacker_slowed = utils::string_bool(att->second.get_state("slowed"));
|
||||
|
||||
//up to double the value of a unit based on experience
|
||||
cost += (double(att->second.experience())/
|
||||
double(att->second.max_experience()))*cost;
|
||||
|
||||
terrain_quality += (double(stat.chance_to_hit_attacker)/100.0)*cost * (on_village ? 0.5 : 1.0);
|
||||
resources_used += cost;
|
||||
|
||||
bool defender_strikes_first = stat.defender_strikes_first;
|
||||
|
||||
while(attacks || defends) {
|
||||
if(attacks && !defender_strikes_first) {
|
||||
const int roll = rand()%100;
|
||||
if(roll < stat.chance_to_hit_defender) {
|
||||
defhp -= stat.damage_defender_takes;
|
||||
atthp += stat.amount_attacker_drains;
|
||||
if(defhp <= 0) {
|
||||
defhp = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if(atthp > hitpoints[i]) {
|
||||
atthp = hitpoints[i];
|
||||
}
|
||||
|
||||
if(stat.attacker_slows && !defender_slowed) {
|
||||
defender_slowed = true;
|
||||
stat.damage_defender_takes = round_damage(stat.damage_defender_takes,1,2);
|
||||
stat.amount_attacker_drains = round_damage(stat.amount_attacker_drains,1,2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
--attacks;
|
||||
}
|
||||
|
||||
defender_strikes_first = false;
|
||||
|
||||
if(defends) {
|
||||
const int roll = rand()%100;
|
||||
if(roll < stat.chance_to_hit_attacker) {
|
||||
atthp -= stat.damage_attacker_takes;
|
||||
defhp += stat.amount_defender_drains;
|
||||
if(atthp <= 0) {
|
||||
atthp = 0;
|
||||
|
||||
//penalty for allowing plague is a 'negative' kill
|
||||
if(stat.defender_plague) {
|
||||
chance_to_kill -= 1.0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if(defhp > target_max_hp) {
|
||||
defhp = target_max_hp;
|
||||
}
|
||||
if(stat.defender_slows && !attacker_slowed) {
|
||||
attacker_slowed = true;
|
||||
stat.damage_attacker_takes = round_damage(stat.damage_attacker_takes,1,2);
|
||||
stat.amount_defender_drains = round_damage(stat.amount_defender_drains,1,2);
|
||||
}
|
||||
}
|
||||
|
||||
--defends;
|
||||
}
|
||||
}
|
||||
|
||||
const int xp = defend_it->second.level() * (defhp <= 0 ? game_config::kill_experience : 1);
|
||||
|
||||
const int xp_for_advance = att->second.max_experience() - att->second.experience();
|
||||
|
||||
//the reward for advancing a unit is to get a 'negative' loss of that unit
|
||||
if(att->second.advances_to().empty() == false) {
|
||||
if(xp >= xp_for_advance) {
|
||||
avg_losses -= att->second.cost();
|
||||
|
||||
//ignore any damage done to this unit
|
||||
atthp = hitpoints[i];
|
||||
} else {
|
||||
//the reward for getting a unit closer to advancement is to get
|
||||
//the proportion of remaining experience needed, and multiply
|
||||
//it by a quarter of the unit cost. This will cause the AI
|
||||
//to heavily favor getting xp for close-to-advance units.
|
||||
avg_losses -= (att->second.cost()*xp)/(xp_for_advance*4);
|
||||
}
|
||||
}
|
||||
|
||||
if(defhp <= 0) {
|
||||
//the reward for killing with a unit that
|
||||
//plagues is to get a 'negative' loss of that unit
|
||||
if(stat.attacker_plague) {
|
||||
avg_losses -= att->second.cost();
|
||||
}
|
||||
|
||||
break;
|
||||
} else if(atthp == 0) {
|
||||
avg_losses += cost;
|
||||
}
|
||||
|
||||
//if the attacker moved onto a village, reward it for doing so
|
||||
else if(on_village) {
|
||||
atthp += game_config::poison_amount*2; //double reward to emphasize getting onto villages
|
||||
}
|
||||
|
||||
defenderxp += (atthp == 0 ? game_config::kill_experience:1)*att->second.level();
|
||||
|
||||
avg_damage_taken += hitpoints[i] - atthp;
|
||||
}
|
||||
|
||||
//penalty for allowing advancement is a 'negative' kill, and
|
||||
//defender's hitpoints get restored to maximum
|
||||
if(defend_it->second.advances_to().empty() == false &&
|
||||
defend_it->second.experience() < defend_it->second.max_experience() &&
|
||||
defend_it->second.experience() + defenderxp >=
|
||||
defend_it->second.max_experience()) {
|
||||
chance_to_kill -= 1.0;
|
||||
defhp = defend_it->second.hitpoints();
|
||||
} else if(defhp == 0) {
|
||||
chance_to_kill += 1.0;
|
||||
} else if(map.gives_healing(defend_it->first)) {
|
||||
defhp += map.gives_healing(defend_it->first);
|
||||
if(defhp > target_hp)
|
||||
defhp = target_hp;
|
||||
}
|
||||
|
||||
avg_damage_inflicted += target_hp - defhp;
|
||||
}
|
||||
combatant *prev_def = NULL;
|
||||
|
||||
//calculate the 'alternative_terrain_quality' -- the best possible defensive values
|
||||
//the attacking units could hope to achieve if they didn't attack and moved somewhere.
|
||||
//this is could for comparative purposes to see just how vulnerable the AI is
|
||||
//making itself
|
||||
|
||||
alternative_terrain_quality = 0.0;
|
||||
double cost_sum = 0.0;
|
||||
for(size_t i = 0; i != movements.size(); ++i) {
|
||||
const unit_map::const_iterator att = units.find(movements[i].first);
|
||||
const double cost = att->second.cost();
|
||||
cost_sum += cost;
|
||||
alternative_terrain_quality += cost*ai_obj.best_defensive_position(att->first,dstsrc,srcdst,enemy_dstsrc).chance_to_hit;
|
||||
alternative_terrain_quality += cost*ai_obj.best_defensive_position(movements[i].first,dstsrc,srcdst,enemy_dstsrc).chance_to_hit;
|
||||
}
|
||||
|
||||
alternative_terrain_quality /= cost_sum*100;
|
||||
|
||||
chance_to_kill /= num_sims;
|
||||
avg_damage_inflicted /= num_sims;
|
||||
avg_damage_taken /= num_sims;
|
||||
terrain_quality /= resources_used;
|
||||
resources_used /= num_sims;
|
||||
avg_losses /= num_sims;
|
||||
avg_damage_inflicted = 0.0;
|
||||
avg_damage_taken = 0.0;
|
||||
resources_used = 0.0;
|
||||
terrain_quality = 0.0;
|
||||
avg_losses = 0.0;
|
||||
chance_to_kill = 0.0;
|
||||
weapons.clear();
|
||||
|
||||
if(uses_leader) {
|
||||
leader_threat = false;
|
||||
double def_avg_experience = 0.0;
|
||||
double first_chance_kill = 0.0;
|
||||
|
||||
double prob_dead_already = 0.0;
|
||||
wassert(!movements.empty());
|
||||
std::vector<std::pair<location,location> >::const_iterator m;
|
||||
for (m = movements.begin(); m != movements.end(); ++m) {
|
||||
// We fix up units map to reflect what this would look like.
|
||||
unit_map::iterator att_it = units.find(m->first);
|
||||
unit att_u = att_it->second;
|
||||
units.erase(att_it);
|
||||
units.insert(std::pair<location,unit>(m->second, att_u));
|
||||
|
||||
if (att_u.can_recruit()) {
|
||||
uses_leader = true;
|
||||
leader_threat = false;
|
||||
}
|
||||
std::vector<battle_context> bc_vector;
|
||||
int weapon = best_attack_weapon(map, teams, units, status, gamedata, m->second,
|
||||
target, bc_vector);
|
||||
|
||||
wassert(weapon != -1);
|
||||
weapons.push_back(weapon);
|
||||
combatant att(bc_vector[weapon].get_attacker_stats());
|
||||
combatant *def = new combatant(bc_vector[weapon].get_defender_stats(), prev_def);
|
||||
delete prev_def;
|
||||
prev_def = def;
|
||||
|
||||
att.fight(*def);
|
||||
double prob_killed = def->hp_dist[0] - prob_dead_already;
|
||||
prob_dead_already = def->hp_dist[0];
|
||||
|
||||
avg_damage_taken += att_u.hitpoints() - att.average_hp();
|
||||
|
||||
double cost = att_u.cost();
|
||||
const bool on_village = map.is_village(m->second);
|
||||
//up to double the value of a unit based on experience
|
||||
cost += (double(att_u.experience())/double(att_u.max_experience()))*cost;
|
||||
resources_used += cost;
|
||||
avg_losses += cost * att.hp_dist[0];
|
||||
|
||||
//double reward to emphasize getting onto villages
|
||||
if (on_village) {
|
||||
avg_damage_taken -= game_config::poison_amount*2;
|
||||
}
|
||||
|
||||
terrain_quality += (double(bc_vector[weapon].get_defender_stats().chance_to_hit)/100.0)*cost * (on_village ? 0.5 : 1.0);
|
||||
|
||||
//the reward for advancing a unit is to get a 'negative' loss of that unit
|
||||
if (!att_u.advances_to().empty()) {
|
||||
int xp_for_advance = att_u.max_experience() - att_u.experience();
|
||||
if (defend_it->second.level() > xp_for_advance)
|
||||
avg_losses -= att_u.cost() * (1.0 - prob_dead_already);
|
||||
else if (defend_it->second.level() * game_config::kill_experience > xp_for_advance)
|
||||
avg_losses -= att_u.cost() * prob_killed;
|
||||
|
||||
//the reward for getting a unit closer to advancement is to get
|
||||
//the proportion of remaining experience needed, and multiply
|
||||
//it by a quarter of the unit cost. This will cause the AI
|
||||
//to heavily favor getting xp for close-to-advance units.
|
||||
avg_losses -= (att_u.cost()*defend_it->second.level())/(xp_for_advance*4);
|
||||
|
||||
//the reward for killing with a unit that
|
||||
//plagues is to get a 'negative' loss of that unit
|
||||
if (bc_vector[weapon].get_attacker_stats().plagues) {
|
||||
avg_losses -= prob_killed * att_u.cost();
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: attack_prediction.cpp should understand advancement directly.
|
||||
// For each level of attacker def gets 1 xp or kill_experience.
|
||||
def_avg_experience += att_u.level() *
|
||||
(1.0 - att.hp_dist[0] + game_config::kill_experience * att.hp_dist[0]);
|
||||
if (m == movements.begin()) {
|
||||
first_chance_kill = def->hp_dist[0];
|
||||
}
|
||||
}
|
||||
|
||||
if (!defend_it->second.advances_to().empty() &&
|
||||
def_avg_experience >= defend_it->second.max_experience() - defend_it->second.experience()) {
|
||||
// It's likely to advance: only if we can kill with first blow.
|
||||
chance_to_kill = first_chance_kill;
|
||||
// Negative average damage (it will advance).
|
||||
avg_damage_inflicted = defend_it->second.hitpoints() - defend_it->second.max_hitpoints();
|
||||
} else {
|
||||
chance_to_kill = prev_def->hp_dist[0];
|
||||
avg_damage_inflicted = defend_it->second.hitpoints() - prev_def->average_hp();
|
||||
}
|
||||
|
||||
// Restore the units to their original positions.
|
||||
for (m = movements.begin(); m != movements.end(); ++m) {
|
||||
unit_map::iterator att_it = units.find(m->second);
|
||||
unit att_u = att_it->second;
|
||||
units.erase(att_it);
|
||||
units.insert(std::pair<location,unit>(m->first, att_u));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue