[[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:
parent
380c341e24
commit
0626d88862
11 changed files with 273 additions and 140 deletions
|
@ -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 "======================="
|
||||
|
|
|
@ -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)
|
||||
|
|
146
src/ai.cpp
146
src/ai.cpp
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
153
src/ai_move.cpp
153
src/ai_move.cpp
|
@ -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] != ¤t_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] != ¤t_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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
10
src/team.cpp
10
src/team.cpp
|
@ -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());
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue