[[AI tuning]]

* Improved default the attack selection of default AI

* Improved movement target selection

* Allowed leader to retreat from dangerous location

* Tried to teach AI to understand poison and leadership in attack
  selection but result wasn't 100% success

* Fixed some minor bugs found from AI
This commit is contained in:
Pauli Nieminen 2008-09-18 14:30:42 +00:00
parent 380c341e24
commit 0626d88862
11 changed files with 273 additions and 140 deletions

View file

@ -538,6 +538,6 @@ while True:
ai.recruit()
ai.report_stats()
print "======================="
print "bruteforce ran for %0.4f-seconds." % (time.time() - st)
print "======================="
#print "======================="
#print "bruteforce ran for %0.4f-seconds." % (time.time() - st)
#print "======================="

View file

@ -537,9 +537,14 @@ bool battle_context::better_combat(const combatant &us_a, const combatant &them_
if (a - b > 0.01)
return true;
// Add poison to calculations
double poison_a_us = (us_a.poisoned) * game_config::poison_amount;
double poison_a_them = (them_a.poisoned) * game_config::poison_amount;
double poison_b_us = (us_b.poisoned) * game_config::poison_amount;
double poison_b_them = (them_b.poisoned) * game_config::poison_amount;
// Compare: damage to them - damage to us (average_hp replaces -damage)
a = us_a.average_hp()*harm_weight - them_a.average_hp();
b = us_b.average_hp()*harm_weight - them_b.average_hp();
a = (us_a.average_hp()+poison_a_us)*harm_weight - (them_a.average_hp()+poison_a_them);
b = (us_b.average_hp()+poison_b_us)*harm_weight - (them_b.average_hp()+poison_b_them);
if (a - b < -0.01)
return false;
if (a - b > 0.01)

View file

@ -37,6 +37,7 @@
#include "log.hpp"
#include "menu_events.hpp"
#include "mouse_handler_base.hpp"
#include "attack_prediction.hpp"
#include "replay.hpp"
#include "statistics.hpp"
#include "unit_display.hpp"
@ -48,6 +49,8 @@
#include <fstream>
#include <boost/scoped_ptr.hpp>
#define DBG_AI LOG_STREAM(debug, ai)
#define LOG_AI LOG_STREAM(info, ai)
#define WRN_AI LOG_STREAM(warn, ai)
@ -331,6 +334,7 @@ bool ai::recruit_usage(const std::string& usage)
log_scope2(ai, "recruiting troops");
LOG_AI << "recruiting '" << usage << "'\n";
raise_user_interact();
//make sure id, usage and cost are known for the coming evaluation of unit types
unit_type_data::types().build_all(unit_type::HELP_INDEX);
@ -579,7 +583,8 @@ gamemap::location ai_interface::move_unit_partial(location from, location to,
steps.erase(i,steps.end());
break;
} else {
if (!u_it->second.get_ability_bool("skirmisher",*i)){
if (i+1 != steps.end()
&& !u_it->second.get_ability_bool("skirmisher",*i)){
LOG_STREAM(err, ai) << "AI tried to skirmish with non-skirmisher\n";
LOG_AI << "\tresetting destination from " <<to;
to = *i;
@ -906,7 +911,7 @@ void ai::find_threats()
if(leader != units_.end()) {
items.push_back(protected_item(
lexical_cast_default<double>(parms["protect_leader"], 2.0),
lexical_cast_default<int>(parms["protect_leader_radius"], 7),
lexical_cast_default<int>(parms["protect_leader_radius"], 15),
leader->first));
}
@ -1198,6 +1203,7 @@ bool ai::do_combat(std::map<gamemap::location,paths>& possible_moves, const move
std::vector<attack_analysis>::iterator choice_it = analysis.end();
double choice_rating = -1000.0;
double vuln = 0.0;
for(std::vector<attack_analysis>::iterator it = analysis.begin();
it != analysis.end(); ++it) {
@ -1221,13 +1227,14 @@ bool ai::do_combat(std::map<gamemap::location,paths>& possible_moves, const move
if(rating > choice_rating) {
choice_it = it;
choice_rating = rating;
vuln = it->vulnerability/it->support;
}
}
time_taken = SDL_GetTicks() - ticks;
LOG_AI << "analysis took " << time_taken << " ticks\n";
if(choice_rating > current_team().caution()/2) {
if(choice_rating > current_team().caution()) {
location from = choice_it->movements[0].first;
location to = choice_it->movements[0].second;
location target_loc = choice_it->target;
@ -1243,10 +1250,14 @@ bool ai::do_combat(std::map<gamemap::location,paths>& possible_moves, const move
return true;
}
if (vuln > 1.5)
add_target(target(to, vuln,target::SUPPORT));
// Recalc appropriate weapons here: AI uses approximations.
battle_context bc(map_, teams_, units_, state_,
to, target_loc, -1, -1,
current_team().aggression());
attack_enemy(to, target_loc, bc.get_attacker_stats().attack_num,
bc.get_defender_stats().attack_num);
@ -1254,7 +1265,7 @@ bool ai::do_combat(std::map<gamemap::location,paths>& possible_moves, const move
// is still alive, then also summon reinforcements
if(choice_it->movements.size() == 1 && units_.count(target_loc)) {
LOG_AI << "found reinforcement target... " << target_loc << "\n";
add_target(target(target_loc,3.0,target::BATTLE_AID));
add_target(target(target_loc,5.0,target::BATTLE_AID));
}
return true;
@ -1270,38 +1281,32 @@ void ai_interface::attack_enemy(const location u,
// Stop the user from issuing any commands while the unit is attacking
const events::command_disabler disable_commands;
if(info_.units.count(u) && info_.units.count(target)) {
if(info_.units.find(target)->second.incapacitated()) {
LOG_STREAM(err, ai) << "attempt to attack unit that is turned to stone\n";
return;
}
if(!info_.units.find(u)->second.attacks_left()) {
LOG_STREAM(err, ai) << "attempt to attack twice with the same unit\n";
return;
}
if(!info_.units.count(u))
{
ERR_AI << "attempt to attack without attacker\n";
}
if (!info_.units.count(target))
{
ERR_AI << "attempt to attack without defender\n";
}
if(info_.units.find(target)->second.incapacitated()) {
LOG_STREAM(err, ai) << "attempt to attack unit that is turned to stone\n";
return;
}
if(!info_.units.find(u)->second.attacks_left()) {
LOG_STREAM(err, ai) << "attempt to attack twice with the same unit\n";
return;
}
if(weapon >= 0) {
recorder.add_attack(u,target,weapon,def_weapon);
}
try {
attack(info_.disp, info_.map, info_.teams, u, target, weapon, def_weapon,
info_.units, info_.state);
}
catch (end_level_exception&)
{
dialogs::advance_unit(info_.map,info_.units,u,info_.disp,true);
const unit_map::const_iterator defender = info_.units.find(target);
if(defender != info_.units.end()) {
const size_t defender_team = size_t(defender->second.side()) - 1;
if(defender_team < info_.teams.size()) {
dialogs::advance_unit(info_.map, info_.units,
target, info_.disp, !info_.teams[defender_team].is_human());
}
}
throw;
}
if(weapon >= 0) {
recorder.add_attack(u,target,weapon,def_weapon);
}
try {
attack(info_.disp, info_.map, info_.teams, u, target, weapon, def_weapon,
info_.units, info_.state);
}
catch (end_level_exception&)
{
dialogs::advance_unit(info_.map,info_.units,u,info_.disp,true);
const unit_map::const_iterator defender = info_.units.find(target);
@ -1313,9 +1318,22 @@ void ai_interface::attack_enemy(const location u,
}
}
check_victory(info_.units,info_.teams, info_.disp);
raise_enemy_attacked();
throw;
}
dialogs::advance_unit(info_.map,info_.units,u,info_.disp,true);
const unit_map::const_iterator defender = info_.units.find(target);
if(defender != info_.units.end()) {
const size_t defender_team = size_t(defender->second.side()) - 1;
if(defender_team < info_.teams.size()) {
dialogs::advance_unit(info_.map, info_.units,
target, info_.disp, !info_.teams[defender_team].is_human());
}
}
check_victory(info_.units,info_.teams, info_.disp);
raise_enemy_attacked();
}
bool ai::get_healing(std::map<gamemap::location,paths>& possible_moves,
@ -1415,7 +1433,7 @@ bool ai::retreat_units(std::map<gamemap::location,paths>& possible_moves,
for(unit_map::iterator i = units_.begin(); i != units_.end(); ++i) {
if(i->second.side() == team_num_ &&
i->second.movement_left() == i->second.total_movement() &&
unit_map::const_iterator(i) != leader &&
(unit_map::const_iterator(i) != leader || !recruiting_prefered_) &&
!i->second.incapacitated()) {
// This unit still has movement left, and is a candidate to retreat.
@ -1491,6 +1509,9 @@ bool ai::retreat_units(std::map<gamemap::location,paths>& possible_moves,
LOG_AI << "retreating '" << i->second.type_id() << "' " << i->first
<< " -> " << best_pos << '\n';
move_unit(i->first,best_pos,possible_moves);
i->second.set_movement(0);
if (best_rating < 0.0)
add_target(target(best_pos, -3.0*best_rating, target::SUPPORT));
return true;
}
}
@ -1516,14 +1537,11 @@ bool ai::move_to_targets(std::map<gamemap::location, paths>& possible_moves,
}
}
std::vector<target>::iterator choosen;
LOG_AI << "choosing move...\n";
std::pair<location,location> move = choose_move(targets, srcdst,
dstsrc, enemy_dstsrc);
for(std::vector<target>::const_iterator ittg = targets.begin();
ittg != targets.end(); ++ittg) {
assert(map_.on_board(ittg->loc));
}
dstsrc, enemy_dstsrc, choosen);
if(move.first.valid() == false) {
break;
@ -1532,6 +1550,8 @@ bool ai::move_to_targets(std::map<gamemap::location, paths>& possible_moves,
if(move.second.valid() == false) {
return true;
}
assert (map_.on_board(move.first)
&& map_.on_board(move.second));
LOG_AI << "move: " << move.first << " -> " << move.second << '\n';
@ -1545,33 +1565,53 @@ bool ai::move_to_targets(std::map<gamemap::location, paths>& possible_moves,
return true;
}
const unit_map::const_iterator u_it = units_.find(arrived_at);
unit_map::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 {
u_it->second.set_movement(0);
// 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;
int selected = -1;
boost::scoped_ptr<battle_context> bc_sel;
double harm_weight = 2.0 + current_team().caution();
if (choosen->type == target::THREAT
|| choosen->type == target::BATTLE_AID)
harm_weight -= 1.0;
harm_weight = current_team().aggression() - harm_weight;
for(int n = 0; n != 6; ++n) {
const unit_map::iterator enemy = find_visible_unit(units_,adj[n],
map_,
teams_,current_team());
map_,
teams_,current_team());
if(enemy != units_.end() &&
current_team().is_enemy(enemy->second.side()) && !enemy->second.incapacitated()) {
current_team().is_enemy(enemy->second.side()) && !enemy->second.incapacitated()) {
// Current behavior is to only make risk-free attacks.
battle_context bc(map_, teams_, units_, state_, arrived_at, adj[n], -1, -1, 100.0);
if (bc.get_defender_stats().damage == 0) {
attack_enemy(arrived_at, adj[n], bc.get_attacker_stats().attack_num,
bc.get_defender_stats().attack_num);
break;
const double value = (bc.get_defender_combatant().hp_dist[0] - bc.get_attacker_combatant().hp_dist[0]*harm_weight)*u_it->second.max_hitpoints()/2
+ (bc.get_defender_combatant().average_hp() - bc.get_attacker_combatant().average_hp() * harm_weight);
if (value > 0.0
&& (selected == -1
|| bc_sel->better_attack(bc,harm_weight)))
{
// Select attack target
bc_sel.reset(new battle_context(bc));
selected = n;
}
}
}
if (selected >= 0) {
attack_enemy(arrived_at, adj[selected], bc_sel->get_attacker_stats().attack_num,
bc_sel->get_defender_stats().attack_num);
}
}
// Don't allow any other units to move onto the tile
@ -1871,6 +1911,7 @@ bool ai::do_recruitment()
return false;
}
raise_user_interact();
// Let formula ai to do recruiting first
if (master_)
{
@ -2064,6 +2105,7 @@ void ai::move_leader_after_recruit(const move_map& /*srcdst*/,
if(p.routes.count(i->first)) {
move_unit(leader->first,current_loc,possible_moves);
leader->second.set_movement(0);
return;
}
}

View file

@ -281,7 +281,8 @@ protected:
const std::vector<location>& battlefield) const;
virtual std::pair<location,location> choose_move(std::vector<target>& targets,
const move_map& srcdst, const move_map& dstsrc, const move_map& enemy_dstsrc);
const move_map& srcdst, const move_map& dstsrc, const move_map& enemy_dstsrc,
std::vector<target>::iterator& best_target);
/** Rates the value of moving onto certain terrain for a unit. */
virtual int rate_terrain(const unit& u, const location& loc);

View file

@ -21,13 +21,17 @@
#include "ai.hpp"
#include "attack_prediction.hpp"
#include "actions.hpp"
#include "game_config.hpp"
#include "gamestatus.hpp"
#include "unit_abilities.hpp"
#include "log.hpp"
#include <cassert>
#define DBG_AI LOG_STREAM(debug, ai)
#define LOG_AI LOG_STREAM(info, ai)
#define ERR_AI LOG_STREAM(err, ai)
const int max_positions = 10000;
@ -81,6 +85,7 @@ void ai::do_attack_analysis(
unit_map::iterator unit_itor = units_.find(current_unit);
assert(unit_itor != units_.end());
// See if the unit has the backstab ability.
// Units with backstab will want to try to have a
// friendly unit opposite the position they move to.
@ -180,6 +185,12 @@ void ai::do_attack_analysis(
}
}
unit_ability_list abil = unit_itor->second.get_abilities("leadership",tiles[j]);
int best_leadership_bonus = abil.highest("value").first;
double leadership_bonus = static_cast<double>(best_leadership_bonus+100)/100.0;
if (leadership_bonus > 1.1)
ERR_AI << unit_itor->second.name() << " is getting leadership " << leadership_bonus << "\n";
// Check to see whether this move would be a backstab.
int backstab_bonus = 1;
double surround_bonus = 1.0;
@ -200,38 +211,44 @@ void ai::do_attack_analysis(
backstab_bonus = 2;
}
surround_bonus = 1.2;
// No surround bonus if target is skirmisher
if (!itor->second.get_ability_bool("skirmisker", itor->first))
surround_bonus = 1.2;
}
}
// See if this position is the best rated we've seen so far.
int rating = rate_terrain(unit_itor->second,tiles[j]) * backstab_bonus;
if(cur_position >= 0 && rating < best_rating * 2) {
int rating = rate_terrain(unit_itor->second,tiles[j]) * backstab_bonus * leadership_bonus;
if(cur_position >= 0 && rating < best_rating) {
continue;
}
// Find out how vulnerable we are to attack from enemy units in this hex.
const double vulnerability = power_projection(tiles[j],enemy_dstsrc)*current_team().caution()*10;
const double penalty = 1.5;
const double vulnerability = power_projection(tiles[j],enemy_dstsrc)*penalty;
// Calculate how much support we have on this hex from allies.
// Support does not take into account terrain, because we don't want
// to move into a hex that is surrounded by good defensive terrain.
const double support = power_projection(tiles[j],fullmove_dstsrc,false)*current_team().aggression();
const double support = power_projection(tiles[j],fullmove_dstsrc,false);
// If this is a position with equal defense to another position,
// but more vulnerability then we don't want to use it.
const double leader_penalty = (unit_itor->second.can_recruit()? 1.4:1.0);
rating -= ((vulnerability/surround_bonus)*leader_penalty - support*surround_bonus);
if(cur_position >= 0 && rating < best_rating) {
// scale vulnerability to 60 hp unit
if(cur_position >= 0 && rating < best_rating
&& (vulnerability/surround_bonus*30.0)/unit_itor->second.hitpoints() -
(support*surround_bonus*30.0)/unit_itor->second.max_hitpoints()
> best_vulnerability - best_support) {
continue;
}
cur_position = j;
best_rating = rating;
best_vulnerability = vulnerability/surround_bonus;
best_support = support*surround_bonus;
best_vulnerability = (vulnerability/surround_bonus*30.0)/unit_itor->second.hitpoints();
best_support = (support*surround_bonus*30.0)/unit_itor->second.max_hitpoints();
}
if(cur_position != -1) {
@ -338,7 +355,6 @@ void ai::attack_analysis::analyze(const gamemap& map, unit_map& units,
if (up->second.can_recruit()) {
uses_leader = true;
leader_threat = false;
}
int att_weapon = -1, def_weapon = -1;
@ -394,6 +410,9 @@ void ai::attack_analysis::analyze(const gamemap& map, unit_map& units,
resources_used += cost;
avg_losses += cost * prob_died;
// add half of cost for poisoned unit so it might get chance to heal
avg_losses += cost * utils::string_bool(up->second.get_state("poisoned")) /2;
// Double reward to emphasize getting onto villages if they survive.
if (on_village) {
avg_damage_taken -= game_config::poison_amount*2 * prob_survived;
@ -480,13 +499,6 @@ double ai::attack_analysis::rating(double aggression, ai& ai_obj) const
aggression = 1.0;
}
// Only use the leader if we do a serious amount of damage,
// compared to how much they do to us.
if(uses_leader && aggression > -4.0) {
LOG_AI << "uses leader..\n";
aggression = -4.0;
}
double value = chance_to_kill*target_value - avg_losses*(1.0-aggression);
if(terrain_quality > alternative_terrain_quality) {
@ -495,8 +507,8 @@ double ai::attack_analysis::rating(double aggression, ai& ai_obj) const
// into sub-optimal terrain.
// Calculate the 'exposure' of our units to risk.
const double exposure_mod = uses_leader ? 2.0 : ai_obj.current_team().caution();
const double exposure = exposure_mod*resources_used*(terrain_quality - alternative_terrain_quality)*vulnerability/std::max<double>(0.01,support);
const double exposure_mod = uses_leader ? ai_obj.current_team().caution()* 8.0 : ai_obj.current_team().caution() * 4.0;
const double exposure = exposure_mod*resources_used*((terrain_quality - alternative_terrain_quality)/10)*vulnerability/std::max<double>(0.01,support);
LOG_AI << "attack option has base value " << value << " with exposure " << exposure << ": "
<< vulnerability << "/" << support << " = " << (vulnerability/std::max<double>(support,0.1)) << "\n";
if(uses_leader) {
@ -506,10 +518,10 @@ double ai::attack_analysis::rating(double aggression, ai& ai_obj) const
value -= exposure*(1.0-aggression);
}
// If this attack uses our leader, and the leader can reach the keep,
// and has gold to spend, reduce the value to reflect the leader's
// If this attack uses our leader and has gold to spend,
// reduce the value to reflect the leader's
// lost recruitment opportunity in the case of an attack.
if(uses_leader && ai_obj.leader_can_reach_keep() && ai_obj.current_team().gold() > 20) {
if(uses_leader && ai_obj.current_team().gold() > 20) {
value -= double(ai_obj.current_team().gold())*0.5;
}
@ -665,16 +677,23 @@ double ai::power_projection(const gamemap::location& loc, const move_map& dstsr
int hp = un.hitpoints() * 1000 / un.max_hitpoints();
int most_damage = 0;
for(std::vector<attack_type>::const_iterator att =
un.attacks().begin(); att != un.attacks().end(); ++att) {
un.attacks().begin(); att != un.attacks().end(); ++att) {
int poison_bonus = 0;
if (att->get_special_bool("poison", true))
poison_bonus = 800 * (1.0 -
std::pow(0.7,
static_cast<double>(att->num_attacks())));
int damage = att->damage() * att->num_attacks() *
(100 + tod_modifier);
(100 + tod_modifier) + poison_bonus;
if(damage > most_damage) {
most_damage = damage;
}
}
int village_bonus = use_terrain && map_.is_village(terrain) ? 3 : 2;
int defense = use_terrain ? 100 - un.defense_modifier(terrain) : 50;
int village_bonus = use_terrain && map_.is_village(terrain) ? 3 : 2;
int rating = hp * defense * most_damage * village_bonus / 200;
if(rating > best_rating) {
gamemap::location *pos = std::find(beg_used, end_used, it->second);

View file

@ -95,6 +95,10 @@ std::vector<ai::target> ai::find_targets(unit_map::const_iterator leader, const
std::vector<target> targets;
std::map<location,paths> friends_possible_moves;
move_map friends_srcdst, friends_dstsrc;
calculate_possible_moves(friends_possible_moves,friends_srcdst,friends_dstsrc,false,true);
//if enemy units are in range of the leader, then we target the enemies who are in range.
if(has_leader) {
const double threat = power_projection(leader->first,enemy_dstsrc);
@ -117,7 +121,7 @@ std::vector<ai::target> ai::find_targets(unit_map::const_iterator leader, const
assert(threats.empty() == false);
const double value = threat/double(threats.size());
const double value = threat*lexical_cast_default<double>(current_team().ai_parameters()["protect_leader"], 3.0)/leader->second.hitpoints();
for(std::set<gamemap::location>::const_iterator i = threats.begin(); i != threats.end(); ++i) {
LOG_AI << "found threat target... " << *i << " with value: " << value << "\n";
targets.push_back(target(*i,value,target::THREAT));
@ -125,23 +129,39 @@ std::vector<ai::target> ai::find_targets(unit_map::const_iterator leader, const
}
}
double corner_distance = distance_between(gamemap::location(0,0), gamemap::location(map_.w(),map_.h()));
if(has_leader && current_team().village_value() > 0.0) {
const std::vector<location>& villages = map_.villages();
for(std::vector<location>::const_iterator t =
villages.begin(); t != villages.end(); ++t) {
assert(map_.on_board(*t));
bool get_village = true;
for(size_t i = 0; i != teams_.size(); ++i) {
if(!current_team().is_enemy(i+1) && teams_[i].owns_village(*t)) {
// check if our village is threatened
if (is_accessible(*t, enemy_dstsrc))
{
// Our village is threated by enemy
// We calculate enemy_power/our_power and multiple village value with that
// to get value for adding more defense
const double enemy = power_projection(*t, enemy_dstsrc)*1.7;
const double our = power_projection(*t, friends_dstsrc);
const double value = current_team().village_value()*our/enemy;
add_target(target(*t, value, target::SUPPORT));
}
get_village = false;
break;
}
}
if(get_village) {
LOG_AI << "found village target... " << *t << " with value: " << current_team().village_value() << "\n";
targets.push_back(target(*t,current_team().village_value(),target::VILLAGE));
double value = current_team().village_value();
value *= 1.0 - static_cast<double>(distance_between(*t,leader->first))/corner_distance;
ERR_AI << "found village target... " << *t << " with value: " << value << " distance: " << static_cast<double>(distance_between(*t,leader->first)) << "\n";
targets.push_back(target(*t,value,target::VILLAGE));
}
}
}
@ -329,6 +349,8 @@ bool ai::move_group(const location& dst, const std::vector<location>& route, con
return true;
}
units_.find(best_loc)->second.set_movement(0);
preferred_moves.erase(std::find(preferred_moves.begin(),preferred_moves.end(),best_loc));
//find locations that are 'perpendicular' to the direction of movement for further units to move to.
@ -388,7 +410,7 @@ double ai::compare_groups(const std::set<location>& our_group, const std::set<lo
return a/b;
}
std::pair<gamemap::location,gamemap::location> ai::choose_move(std::vector<target>& targets, const move_map& srcdst, const move_map& dstsrc, const move_map& enemy_dstsrc)
std::pair<gamemap::location,gamemap::location> ai::choose_move(std::vector<target>& targets, const move_map& srcdst, const move_map& dstsrc, const move_map& enemy_dstsrc, std::vector<target>::iterator& best_target)
{
log_scope2(ai, "choosing move");
@ -403,7 +425,7 @@ std::pair<gamemap::location,gamemap::location> ai::choose_move(std::vector<targe
unit_map::iterator best = units_.end();
double best_rating = 0.1;
std::vector<target>::iterator best_target = targets.end();
best_target = targets.end();
unit_map::iterator u;
@ -519,7 +541,7 @@ std::pair<gamemap::location,gamemap::location> ai::choose_move(std::vector<targe
//now see if any other unit can put a better bid forward
for(++u; u != units_.end(); ++u) {
if(u->second.side() != team_num_ || u->second.can_recruit() ||
u->second.movement_left() <= 0 || utils::string_bool(u->second.get_state("guardian")) || u->second.incapacitated()) {
u->second.movement_left() <= 0 || utils::string_bool(u->second.get_state("guardian")) || u->second.incapacitated()) {
continue;
}
@ -604,7 +626,7 @@ std::pair<gamemap::location,gamemap::location> ai::choose_move(std::vector<targe
for(std::vector<location>::const_iterator i = locs.begin(); i != locs.end(); ++i) {
const int distance = distance_between(*i,best_target->loc);
const int defense = best->second.defense_modifier(map_.get_terrain(*i));
const double vulnerability = power_projection(*i,enemy_dstsrc);
const double vulnerability = power_projection(*i,enemy_dstsrc) * 10 * current_team().caution();
if(best_loc.valid() == false || defense < best_defense || (defense == best_defense && vulnerability < best_vulnerability)) {
best_loc = *i;
@ -638,9 +660,9 @@ std::pair<gamemap::location,gamemap::location> ai::choose_move(std::vector<targe
//if any point along the path is too dangerous for our single unit, then we hold back
for(std::vector<location>::const_iterator i = best_route.steps.begin(); i != best_route.steps.end() && movement > 0; ++i) {
const double threat = power_projection(*i,enemy_dstsrc);
if((threat >= double(best->second.hitpoints()) && threat > power_projection(*i,fullmove_dstsrc)) ||
(i >= best_route.steps.end()-2 && unit_at_target != units_.end() && current_team().is_enemy(unit_at_target->second.side()))) {
const double threat = power_projection(*i,enemy_dstsrc)*10*current_team().caution();
if((threat >= double(best->second.hitpoints()) && threat > power_projection(*i,fullmove_dstsrc)*2) ||
(i >= best_route.steps.end()-2 && unit_at_target != units_.end() && current_team().is_enemy(unit_at_target->second.side()))) {
dangerous = true;
break;
}
@ -697,7 +719,7 @@ std::pair<gamemap::location,gamemap::location> ai::choose_move(std::vector<targe
for(move_map::const_iterator i = itors.first; i != itors.second; ++i) {
const int distance = distance_between(target_loc,i->second);
const int defense = un.defense_modifier(map_.get_terrain(i->second));
const double threat = (power_projection(i->second,enemy_dstsrc)*defense)/100;
const double threat = (power_projection(i->second,enemy_dstsrc)*defense)/100*10*current_team().caution();
if(best_loc.valid() == false || (threat < std::max<double>(best_threat,max_acceptable_threat) && distance < best_distance)) {
best_loc = i->second;
@ -714,6 +736,7 @@ std::pair<gamemap::location,gamemap::location> ai::choose_move(std::vector<targe
if(*j != best_loc && distance_between(*j,best_loc) < 3) {
LOG_AI << "found mass-to-attack target... " << *j << " with value: " << value*4.0 << "\n";
targets.push_back(target(*j,value*4.0,target::MASS));
best_target = targets.end() - 1;
}
}
@ -722,7 +745,7 @@ std::pair<gamemap::location,gamemap::location> ai::choose_move(std::vector<targe
}
for(std::vector<location>::reverse_iterator ri =
best_route.steps.rbegin(); ri != best_route.steps.rend(); ++ri) {
best_route.steps.rbegin(); ri != best_route.steps.rend(); ++ri) {
if(game_config::debug) {
//game_display::debug_highlight(*ri,static_cast<size_t>(0.2));
@ -737,7 +760,7 @@ std::pair<gamemap::location,gamemap::location> ai::choose_move(std::vector<targe
while(its.first != its.second) {
if(its.first->second == best->first) {
if(!should_retreat(its.first->first,best,fullmove_srcdst,fullmove_dstsrc,enemy_dstsrc,
current_team().caution())) {
current_team().caution())) {
const double value = best_target->value - best->second.cost()/20.0;
if(value > 0.0 && best_target->type != target::MASS) {
@ -772,6 +795,7 @@ std::pair<gamemap::location,gamemap::location> ai::choose_move(std::vector<targe
//this sounds like the road ahead might be dangerous, and that's why we don't advance.
//create this as a target, attempting to rally units around
targets.push_back(target(best->first,best_target->value));
best_target = targets.end() - 1;
return std::pair<location,location>(best->first,best->first);
}
@ -801,6 +825,20 @@ void ai::access_points(const move_map& srcdst, const location& u, const location
}
}
namespace {
struct match_turn{
match_turn(int turn) : turn_(turn)
{
}
bool operator()(const std::map<gamemap::location, paths::route::waypoint>::value_type& way)
{
return way.second.turns == turn_;
}
private:
int& turn_;
};
}
void ai::move_leader_to_keep(const move_map& enemy_dstsrc)
{
const unit_map::iterator leader = find_leader(units_,team_num_);
@ -825,17 +863,37 @@ void ai::move_leader_to_keep(const move_map& enemy_dstsrc)
int best_value = INT_MAX - 1;
gamemap::location best_target;
const shortest_path_calculator cost_calc(leader->second,
current_team(),
units_, teams_,
map_);
std::set<gamemap::location> allowed_teleports;
if(leader->second.get_ability_bool("teleport",leader->first)) {
// search all known empty friendly villages
for(std::set<gamemap::location>::const_iterator vil = current_team().villages().begin();
vil != current_team().villages().end(); ++vil) {
unit_map::const_iterator occupant = units_.find(*vil);
if (occupant != units_.end() && occupant != leader)
continue;
allowed_teleports.insert(*vil);
}
}
// The leader can't move to his keep, try to move to the closest location
// to the keep where there are no enemies in range.
for(std::set<location>::iterator i = keeps().begin();
i != keeps().end(); ++i) {
const shortest_path_calculator cost_calc(leader->second, current_team(), units_,
teams_,map_);
const double limit = std::min(10000, best_value + 1);
paths::routes_map::iterator route = path_itor->second.routes.insert(
std::make_pair(*i,
a_star_search(leader->first, *i, limit, &cost_calc,map_.w(), map_.h()))).first;
a_star_search(leader->first, *i, limit, &cost_calc,map_.w(), map_.h(), &allowed_teleports
))).first;
if (route->second.steps.empty())
{
path_itor->second.routes.erase(route);
@ -851,38 +909,39 @@ void ai::move_leader_to_keep(const move_map& enemy_dstsrc)
unit_map::const_iterator u = units_.find(*i);
const int reserved_penalty = leader->second.total_movement() *
(u != units_.end()
&& &teams_[u->second.side()-1] != &current_team()
&& !u->second.invisible(u->first, units_, teams_)
?(current_team().is_enemy(u->second.side())?6:3)
:0);
const int enemy = (power_projection(*i, enemy_dstsrc) * 15 * leader->second.total_movement())/leader->second.hitpoints();
&& &teams_[u->second.side()-1] != &current_team()
&& !u->second.invisible(u->first, units_, teams_)
?(current_team().is_enemy(u->second.side())?6:3)
:0);
int value = empty_slots + enemy + tactical_value + reserved_penalty;
int value = empty_slots + tactical_value + reserved_penalty;
// do enemy power projection so we know where enemy is
int enemy = leader->second.hitpoints() - (power_projection(*i, enemy_dstsrc) * 3);
if (enemy > 0)
enemy = ((leader->second.hitpoints() - enemy)*leader->second.total_movement()*4 / leader->second.hitpoints())*-1;
else
enemy = (enemy * leader->second.total_movement() * -4)/leader->second.hitpoints();
value += enemy;
// Make first quess if this is worth calculateing
if (value + static_cast<int>(route->second.steps.size()) - 1 >= best_value)
continue;
route->second.move_left = leader->second.movement_left();
int distance = 0;
gamemap::location target;
std::vector<gamemap::location>::iterator loc;
int distance = route_turns_to_complete(leader->second,
route->second,
current_team(),
units_,
teams_,
map_) * leader->second.total_movement();
for (loc = route->second.steps.begin() + 1;
loc != route->second.steps.end();
++loc)
{
distance += leader->second.movement_cost(map_.get_terrain(*loc));
if (route->second.move_left == 0)
continue;
route->second.move_left -= leader->second.movement_cost(map_.get_terrain(*loc));
if (route->second.move_left <= 0)
{
target = *loc;
route->second.move_left = 0;
}
}
if (route->second.move_left > 0)
target = *(loc-1);
gamemap::location target;
target = std::find_if(route->second.waypoints.begin(),
route->second.waypoints.end(),
match_turn(1))->first;
int multiturn_move_penalty = 0;
if (recruiting_prefered_)
@ -891,7 +950,7 @@ void ai::move_leader_to_keep(const move_map& enemy_dstsrc)
const int distance_value = (distance > leader->second.movement_left()?
((distance - leader->second.movement_left())/leader->second.total_movement()+multiturn_move_penalty)*leader->second.total_movement() : 0);
value += distance_value;
if (value >= best_value)
continue;
@ -905,13 +964,13 @@ void ai::move_leader_to_keep(const move_map& enemy_dstsrc)
" reserved_penalty: " << reserved_penalty <<
" target: " << target <<
" value: " << value <<
" route: " << route->second.steps.size() << " " << route->second.move_left <<
" route: " << route->second.steps.size() << " " << route->second.move_left <<
"\n";
}
}
// Find the location with the best value
if (leader->first != best_target
&& best_target != gamemap::location::null_location)
&& best_target != gamemap::location::null_location)
move_unit(leader->first,best_target,possible_moves);
}
}

View file

@ -1713,6 +1713,7 @@ PyObject* python_ai::wrapper_unit_attack_statistics(wesnoth_unit* self, PyObject
std::pair<gamemap::location,unit> *temp = inf.units.extract(*from->location_);
std::pair<gamemap::location,unit> *backup = temp;
std::pair<gamemap::location,unit> replace(*from->location_,*self->unit_);
replace.second.clone();
inf.units.add(&replace);
battle_context bc(

View file

@ -231,9 +231,9 @@ bool ai::get_villages(std::map<gamemap::location,paths>& possible_moves,
const unit_map::const_iterator new_unit = units_.find(loc);
if(new_unit != units_.end() &&
power_projection(i->first,enemy_dstsrc) >= new_unit->second.hitpoints()/4) {
power_projection(loc,enemy_dstsrc) >= new_unit->second.hitpoints()/4) {
LOG_AI << "found support target... " << new_unit->first << "\n";
add_target(target(new_unit->first,1.0,target::SUPPORT));
add_target(target(new_unit->first,25.0* current_team().caution() * power_projection(loc,enemy_dstsrc) / new_unit->second.hitpoints(),target::SUPPORT));
}
}
}
@ -344,8 +344,8 @@ void ai::find_villages(
}
const unit& un = u->second;
const size_t threat_multipler = (current_loc == leader_loc?4:1) * current_team().caution() * 4;
if(un.hitpoints() < (threat_multipler*threat*2*un.defense_modifier(map_.get_terrain(current_loc)))/100) {
const double threat_multipler = (current_loc == leader_loc?1.5:1.0);
if(un.hitpoints() < (threat_multipler*threat*2.0*un.defense_modifier(map_.get_terrain(current_loc)))/100) {
continue;
}

View file

@ -779,6 +779,8 @@ bool game_controller::play_multiplayer_mode()
std::cerr << "Could not find any non-random faction for side " << side_num << "\n";
return false;
}
std::cerr << " Faction " << (*side)["name"] <<
" selected for side " << side_num << ".\n";
}
char buf[20];

View file

@ -275,6 +275,10 @@ int route_turns_to_complete(const unit& u, paths::route& rt, const team &viewing
movement -= move_cost;
}
}
if (turns == 1)
rt.move_left = movement;
else
rt.move_left = 0;
return turns;
}

View file

@ -107,8 +107,8 @@ team::team_info::team_info(const config& cfg) :
ai_params(),
ai_memory_(),
villages_per_scout(),
leader_value(0.0),
village_value(0.0),
leader_value(3.0),
village_value(4.5),
aggression_(0.5),
caution_(0.25),
targets(),
@ -219,7 +219,7 @@ team::team_info::team_info(const config& cfg) :
}
if(scouts_val.empty()) {
villages_per_scout = 4;
villages_per_scout = 8;
} else {
villages_per_scout = atoi(scouts_val.c_str());
}
@ -245,7 +245,7 @@ team::team_info::team_info(const config& cfg) :
}
if(village_val.empty()) {
village_value = 1.0;
village_value = 4.5;
} else {
village_value = atof(village_val.c_str());
}
@ -258,7 +258,7 @@ team::team_info::team_info(const config& cfg) :
}
if(aggression_val.empty()) {
aggression_ = 0.5;
aggression_ = 0.4;
} else {
aggression_ = atof(aggression_val.c_str());
}