used 'attacks' aspect to keep a cache of attacks...

...shared between ai_default/formula_ai/composite_ai code
This commit is contained in:
Iurii Chernyi 2009-08-03 22:03:13 +00:00
parent 15abe931ea
commit ba6a7aa2c4
17 changed files with 644 additions and 394 deletions

View file

@ -41,6 +41,17 @@
{DEFAULT_ASPECT_VALUE aggression 0.5}
{DEFAULT_ASPECT_VALUE attack_depth 5}
[aspect]
id=attacks
engine=cpp
name=composite_aspect
invalidate_on_gamestate_change=yes
[default]
engine=cpp
name=testing_ai_default::aspect_attacks
invalidate_on_gamestate_change=yes
[/default]
[/aspect]
{DEFAULT_ASPECT_EMPTY_SLF avoid}
{DEFAULT_ASPECT_VALUE caution 0.25}
{DEFAULT_ASPECT_VALUE grouping offensive}

View file

@ -98,6 +98,8 @@
<Unit filename="..\..\src\ai\registry.hpp" />
<Unit filename="..\..\src\ai\testing.cpp" />
<Unit filename="..\..\src\ai\testing.hpp" />
<Unit filename="..\..\src\ai\testing\aspect_attacks.cpp" />
<Unit filename="..\..\src\ai\testing\aspect_attacks.hpp" />
<Unit filename="..\..\src\ai\testing\ca.cpp" />
<Unit filename="..\..\src\ai\testing\ca.hpp" />
<Unit filename="..\..\src\ai\testing\stage_fallback.cpp" />

View file

@ -127,6 +127,8 @@
<Unit filename="..\..\src\ai\registry.hpp" />
<Unit filename="..\..\src\ai\testing.cpp" />
<Unit filename="..\..\src\ai\testing.hpp" />
<Unit filename="..\..\src\ai\testing\aspect_attacks.cpp" />
<Unit filename="..\..\src\ai\testing\aspect_attacks.hpp" />
<Unit filename="..\..\src\ai\testing\ca.cpp" />
<Unit filename="..\..\src\ai\testing\ca.hpp" />
<Unit filename="..\..\src\ai\testing\stage_fallback.cpp" />

View file

@ -4395,6 +4395,34 @@
<Filter
Name="testing"
>
<File
RelativePath="..\..\src\ai\testing\aspect_attacks.cpp"
>
<FileConfiguration
Name="Debug|Win32"
>
<Tool
Name="VCCLCompilerTool"
ObjectFile="$(IntDir)\ai\testing\"
/>
</FileConfiguration>
<FileConfiguration
Name="Release|Win32"
>
<Tool
Name="VCCLCompilerTool"
ObjectFile="$(IntDir)\ai\testing\"
/>
</FileConfiguration>
<FileConfiguration
Name="Debug (fast)|Win32"
>
<Tool
Name="VCCLCompilerTool"
ObjectFile="$(IntDir)\ai\testing\"
/>
</FileConfiguration>
</File>
<File
RelativePath="..\..\src\ai\testing\ca.cpp"
>
@ -5658,6 +5686,10 @@
<Filter
Name="testing"
>
<File
RelativePath="..\..\src\ai\testing\aspect_attacks.hpp"
>
</File>
<File
RelativePath="..\..\src\ai\testing\ca.hpp"
>

View file

@ -245,6 +245,7 @@ SET(wesnoth-main_SRC
ai/interface.cpp
ai/manager.cpp
ai/registry.cpp
ai/testing/aspect_attacks.cpp
ai/testing/ca.cpp
ai/testing/stage_rca.cpp
ai/testing/stage_fallback.cpp

View file

@ -68,6 +68,7 @@ wesnoth_source = \
ai/interface.cpp \
ai/manager.cpp \
ai/registry.cpp \
ai/testing/aspect_attacks.cpp \
ai/testing/ca.cpp \
ai/testing/stage_rca.cpp \
ai/testing/stage_fallback.cpp \

View file

@ -173,6 +173,7 @@ wesnoth_sources = Split("""
ai/interface.cpp
ai/manager.cpp
ai/registry.cpp
ai/testing/aspect_attacks.cpp
ai/testing/ca.cpp
ai/testing/stage_rca.cpp
ai/testing/stage_fallback.cpp

View file

@ -316,7 +316,7 @@ void attack_result::do_execute()
check_victory();
set_gamestate_changed();
//start of ugly hack. @todo 1.8 rework that via extended event system
//until event system is reworked, we note the attack this way
//until event system is reworked, we note the attack this way
get_info().recent_attacks.insert(defender_loc_);
//end of ugly hack
try {

View file

@ -798,17 +798,10 @@ void ai_default::do_move()
bool ai_default::do_combat(std::map<map_location,paths>& possible_moves, const move_map& srcdst,
const move_map& dstsrc, const move_map& enemy_srcdst, const move_map& enemy_dstsrc)
{
const std::vector<attack_analysis> &analysis = get_attacks();
int ticks = SDL_GetTicks();
std::vector<attack_analysis> analysis = analyze_targets(srcdst, dstsrc,
enemy_srcdst, enemy_dstsrc);
int time_taken = SDL_GetTicks() - ticks;
LOG_AI << "took " << time_taken << " ticks for " << analysis.size()
<< " positions. Analyzing...\n";
ticks = SDL_GetTicks();
const int max_sims = 50000;
int num_sims = analysis.empty() ? 0 : max_sims/analysis.size();
if(num_sims < 20)
@ -821,9 +814,9 @@ bool ai_default::do_combat(std::map<map_location,paths>& possible_moves, const m
const int max_positions = 30000;
const int skip_num = analysis.size()/max_positions;
std::vector<attack_analysis>::iterator choice_it = analysis.end();
std::vector<attack_analysis>::const_iterator choice_it = analysis.end();
double choice_rating = -1000.0;
for(std::vector<attack_analysis>::iterator it = analysis.begin();
for(std::vector<attack_analysis>::const_iterator it = analysis.begin();
it != analysis.end(); ++it) {
if(skip_num > 0 && ((it - analysis.begin())%skip_num) && it->movements.size() > 1)
@ -849,7 +842,7 @@ bool ai_default::do_combat(std::map<map_location,paths>& possible_moves, const m
}
}
time_taken = SDL_GetTicks() - ticks;
int time_taken = SDL_GetTicks() - ticks;
LOG_AI << "analysis took " << time_taken << " ticks\n";
// suokko tested the rating against current_team().caution()

View file

@ -34,7 +34,7 @@ static lg::log_domain log_ai("ai/attack");
namespace ai {
void attack_analysis::analyze(const gamemap& map, unit_map& units,
class readonly_context& ai_obj,
const readonly_context& ai_obj,
const move_map& dstsrc, const move_map& srcdst,
const move_map& enemy_dstsrc, double aggression)
{
@ -260,7 +260,7 @@ bool attack_analysis::attack_close(const map_location& loc) const
}
double attack_analysis::rating(double aggression, readonly_context& ai_obj) const
double attack_analysis::rating(double aggression, const readonly_context& ai_obj) const
{
if(leader_threat) {
aggression = 1.0;
@ -292,10 +292,6 @@ double attack_analysis::rating(double aggression, readonly_context& ai_obj) cons
#endif
LOG_AI << "attack option has base value " << value << " with exposure " << exposure << ": "
<< vulnerability << "/" << support << " = " << (vulnerability/std::max<double>(support,0.1)) << "\n";
if(uses_leader) {
ai_obj.log_message("attack option has value " + str_cast(value) + " with exposure " + str_cast(exposure) + ": " + str_cast(vulnerability) + "/" + str_cast(support));
}
value -= exposure*(1.0-aggression);
}

View file

@ -59,60 +59,6 @@ void default_ai_context_proxy::init_default_ai_context_proxy(default_ai_context
const int max_positions = 10000;
std::vector<attack_analysis> default_ai_context_impl::analyze_targets(
const move_map& srcdst, const move_map& dstsrc,
const move_map& enemy_srcdst, const move_map& enemy_dstsrc
)
{
log_scope2(log_ai, "analyzing targets...");
std::vector<attack_analysis> res;
unit_map units_ = get_info().units;
std::vector<map_location> unit_locs;
for(unit_map::const_iterator i = units_.begin(); i != units_.end(); ++i) {
if(i->second.side() == get_side() && i->second.attacks_left()) {
unit_locs.push_back(i->first);
}
}
bool used_locations[6];
std::fill(used_locations,used_locations+6,false);
moves_map dummy_moves;
move_map fullmove_srcdst, fullmove_dstsrc;
calculate_possible_moves(dummy_moves,fullmove_srcdst,fullmove_dstsrc,false,true);
unit_stats_cache().clear();
for(unit_map::const_iterator j = units_.begin(); j != units_.end(); ++j) {
// Attack anyone who is on the enemy side,
// and who is not invisible or petrified.
if(current_team().is_enemy(j->second.side()) && !j->second.incapacitated() &&
j->second.invisible(j->first,units_,get_info().teams) == false) {
map_location adjacent[6];
get_adjacent_tiles(j->first,adjacent);
attack_analysis analysis;
analysis.target = j->first;
analysis.vulnerability = 0.0;
analysis.support = 0.0;
// const int ticks = SDL_GetTicks();
do_attack_analysis(j->first,srcdst,dstsrc,fullmove_srcdst,fullmove_dstsrc,enemy_srcdst,enemy_dstsrc,
adjacent,used_locations,unit_locs,res,analysis);
// const int time_taken = SDL_GetTicks() - ticks;
// static int max_time = 0;
// if(time_taken > max_time)
// max_time = time_taken;
}
}
return res;
}
default_ai_context_impl::~default_ai_context_impl()
{
@ -145,270 +91,6 @@ int default_ai_context_impl::count_free_hexes_in_castle(const map_location &loc,
}
void default_ai_context_impl::do_attack_analysis(
const map_location& loc,
const move_map& srcdst, const move_map& dstsrc,
const move_map& fullmove_srcdst, const move_map& fullmove_dstsrc,
const move_map& enemy_srcdst, const move_map& enemy_dstsrc,
const map_location* tiles, bool* used_locations,
std::vector<map_location>& units,
std::vector<attack_analysis>& result,
attack_analysis& cur_analysis
)
{
// This function is called fairly frequently, so interact with the user here.
raise_user_interact();
if(cur_analysis.movements.size() >= size_t(get_attack_depth())) {
//std::cerr << "ANALYSIS " << cur_analysis.movements.size() << " >= " << get_attack_depth() << "\n";
return;
}
gamemap &map_ = get_info().map;
unit_map &units_ = get_info().units;
std::vector<team> &teams_ = get_info().teams;
static double best_results[6];
if(result.empty()) {
for(int i = 0; i != 6; ++i) {
best_results[i] = 0.0;
}
}
const size_t max_positions = 1000;
if(result.size() > max_positions && !cur_analysis.movements.empty()) {
LOG_AI << "cut analysis short with number of positions\n";
return;
}
const double cur_rating = cur_analysis.movements.empty() ? -1.0 :
cur_analysis.rating(get_aggression(),*this);
double rating_to_beat = cur_rating;
if(!cur_analysis.movements.empty()) {
assert(cur_analysis.movements.size() < 6);
double& best_res = best_results[cur_analysis.movements.size()-1];
rating_to_beat = best_res = std::max(best_res,cur_rating);
}
for(size_t i = 0; i != units.size(); ++i) {
const map_location current_unit = units[i];
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.
//
// See if the unit has the slow ability -- units with slow only attack first.
bool backstab = false, slow = false;
std::vector<attack_type>& attacks = unit_itor->second.attacks();
for(std::vector<attack_type>::iterator a = attacks.begin(); a != attacks.end(); ++a) {
a->set_specials_context(map_location(), map_location(), units_, true, NULL);
if(a->get_special_bool("backstab")) {
backstab = true;
}
if(a->get_special_bool("slow")) {
slow = true;
}
}
if(slow && cur_analysis.movements.empty() == false) {
continue;
}
// Check if the friendly unit is surrounded,
// A unit is surrounded if it is flanked by enemy units
// and at least one other enemy unit is nearby
// or if the unit is totaly surrounded by enemies
// with max. one tile to escape.
bool is_surrounded = false;
bool is_flanked = false;
int enemy_units_around = 0;
int accessible_tiles = 0;
map_location adj[6];
get_adjacent_tiles(current_unit, adj);
size_t tile;
for(tile = 0; tile != 3; ++tile) {
const unit_map::const_iterator tmp_unit = units_.find(adj[tile]);
bool possible_flanked = false;
if(map_.on_board(adj[tile]))
{
accessible_tiles++;
if(tmp_unit != units_.end() && get_side() != tmp_unit->second.side())
{
enemy_units_around++;
possible_flanked = true;
}
}
const unit_map::const_iterator tmp_opposite_unit = units_.find(adj[tile + 3]);
if(map_.on_board(adj[tile + 3]))
{
accessible_tiles++;
if(tmp_opposite_unit != units_.end() && get_side() != tmp_opposite_unit->second.side())
{
enemy_units_around++;
if(possible_flanked)
{
is_flanked = true;
}
}
}
}
if((is_flanked && enemy_units_around > 2) || enemy_units_around >= accessible_tiles - 1)
is_surrounded = true;
double best_vulnerability = 0.0, best_support = 0.0;
int best_rating = 0;
int cur_position = -1;
// Iterate over positions adjacent to the unit, finding the best rated one.
for(int j = 0; j != 6; ++j) {
// If in this planned attack, a unit is already in this location.
if(used_locations[j]) {
continue;
}
// See if the current unit can reach that position.
if (tiles[j] != current_unit) {
typedef std::multimap<map_location,map_location>::const_iterator Itor;
std::pair<Itor,Itor> its = dstsrc.equal_range(tiles[j]);
while(its.first != its.second) {
if(its.first->second == current_unit)
break;
++its.first;
}
// If the unit can't move to this location.
if(its.first == its.second || units_.find(tiles[j]) != units_.end()) {
continue;
}
}
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) {
LOG_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;
if(tiles[(j+3)%6] != current_unit) {
const unit_map::const_iterator itor = units_.find(tiles[(j+3)%6]);
// Note that we *could* also check if a unit plans to move there
// before we're at this stage, but we don't because, since the
// attack calculations don't actually take backstab into account (too complicated),
// this could actually make our analysis look *worse* instead of better.
// So we only check for 'concrete' backstab opportunities.
// That would also break backstab_check, since it assumes
// the defender is in place.
if(itor != units_.end() &&
backstab_check(tiles[j], loc, units_, teams_)) {
if(backstab) {
backstab_bonus = 2;
}
// No surround bonus if target is skirmisher
if (!itor->second.get_ability_bool("skirmisker"))
surround_bonus = 1.2;
}
}
// See if this position is the best rated we've seen so far.
const int rating = static_cast<int>(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.
//FIXME: suokko's r29531 multiplied this by a constant 1.5. ?
const double vulnerability = power_projection(tiles[j],enemy_dstsrc);
// Calculate how much support we have on this hex from allies.
const double support = power_projection(tiles[j], fullmove_dstsrc);
// If this is a position with equal defense to another position,
// but more vulnerability then we don't want to use it.
#ifdef SUOKKO
//FIXME: this code was in sukko's r29531 Correct?
// 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;
}
#else
if(cur_position >= 0 && rating == best_rating && vulnerability/surround_bonus - support*surround_bonus >= best_vulnerability - best_support) {
continue;
}
#endif
cur_position = j;
best_rating = rating;
#ifdef SUOKKO
//FIXME: this code was in sukko's r29531 Correct?
best_vulnerability = (vulnerability/surround_bonus*30.0)/unit_itor->second.hitpoints();
best_support = (support*surround_bonus*30.0)/unit_itor->second.max_hitpoints();
#else
best_vulnerability = vulnerability/surround_bonus;
best_support = support*surround_bonus;
#endif
}
if(cur_position != -1) {
units.erase(units.begin() + i);
cur_analysis.movements.push_back(std::pair<map_location,map_location>(current_unit,tiles[cur_position]));
cur_analysis.vulnerability += best_vulnerability;
cur_analysis.support += best_support;
cur_analysis.is_surrounded = is_surrounded;
cur_analysis.analyze(map_, units_, *this, dstsrc, srcdst, enemy_dstsrc, get_aggression());
//This logic to sometimes not add the attack because it doesn't
//rate high enough seems to remove attacks from consideration
//that should not be removed, so it has been removed.
// -- David.
// if(cur_analysis.rating(get_aggression(),*this) > rating_to_beat) {
result.push_back(cur_analysis);
used_locations[cur_position] = true;
do_attack_analysis(loc,srcdst,dstsrc,fullmove_srcdst,fullmove_dstsrc,enemy_srcdst,enemy_dstsrc,
tiles,used_locations,
units,result,cur_analysis);
used_locations[cur_position] = false;
// }
cur_analysis.vulnerability -= best_vulnerability;
cur_analysis.support -= best_support;
cur_analysis.movements.pop_back();
units.insert(units.begin() + i, current_unit);
}
}
}
default_ai_context& default_ai_context_impl::get_default_ai_context(){
return *this;
}

View file

@ -59,11 +59,11 @@ public:
}
void analyze(const gamemap& map, unit_map& units,
class readonly_context& ai_obj,
const readonly_context& ai_obj,
const move_map& dstsrc, const move_map& srcdst,
const move_map& enemy_dstsrc, double aggression);
double rating(double aggression, class readonly_context& ai_obj) const;
double rating(double aggression, const readonly_context& ai_obj) const;
variant get_value(const std::string& key) const;
void get_inputs(std::vector<game_logic::formula_input>* inputs) const;
@ -124,12 +124,6 @@ class default_ai_context;
class default_ai_context : public virtual readwrite_context{
public:
/** Return a vector of all possible attack analysisis */
virtual std::vector<attack_analysis> analyze_targets(
const move_map& srcdst, const move_map& dstsrc,
const move_map& enemy_srcdst, const move_map& enemy_dstsrc) = 0;
virtual int count_free_hexes_in_castle(const map_location& loc, std::set<map_location> &checked_hexes) = 0;
@ -141,16 +135,6 @@ public:
virtual ~default_ai_context();
/** Analyze possibility of attacking target on 'loc'. */
virtual void do_attack_analysis(
const map_location& loc, const move_map& srcdst, const move_map& dstsrc,
const move_map& fullmove_srcdst, const move_map& fullmove_dstsrc,
const move_map& enemy_srcdst, const move_map& enemy_dstsrc,
const map_location* tiles, bool* used_locations,
std::vector<map_location>& units,
std::vector<attack_analysis>& result, attack_analysis& cur_analysis) = 0;
virtual default_ai_context& get_default_ai_context() = 0;
@ -171,14 +155,6 @@ public:
class default_ai_context_proxy : public virtual default_ai_context, public virtual readwrite_context_proxy {
public:
virtual std::vector<attack_analysis> analyze_targets(
const move_map& srcdst, const move_map& dstsrc,
const move_map& enemy_srcdst, const move_map& enemy_dstsrc)
{
return target_->analyze_targets(srcdst,dstsrc,enemy_srcdst,enemy_dstsrc);
}
int count_free_hexes_in_castle(const map_location& loc, std::set<map_location> &checked_hexes)
{
return target_->count_free_hexes_in_castle(loc, checked_hexes);
@ -194,19 +170,6 @@ public:
virtual ~default_ai_context_proxy();
virtual void do_attack_analysis(
const map_location& loc, const move_map& srcdst, const move_map& dstsrc,
const move_map& fullmove_srcdst, const move_map& fullmove_dstsrc,
const move_map& enemy_srcdst, const move_map& enemy_dstsrc,
const map_location* tiles, bool* used_locations,
std::vector<map_location>& units,
std::vector<attack_analysis>& result, attack_analysis& cur_analysis)
{
target_->do_attack_analysis(loc,srcdst,dstsrc,fullmove_srcdst,fullmove_dstsrc,enemy_srcdst,enemy_dstsrc,tiles,used_locations,units,result,cur_analysis);
}
virtual default_ai_context& get_default_ai_context()
{
return target_->get_default_ai_context();
@ -244,11 +207,6 @@ private:
class default_ai_context_impl : public virtual readwrite_context_proxy, public default_ai_context {
public:
virtual std::vector<attack_analysis> analyze_targets(
const move_map& srcdst, const move_map& dstsrc,
const move_map& enemy_srcdst, const move_map& enemy_dstsrc);
int count_free_hexes_in_castle(const map_location& loc, std::set<map_location> &checked_hexes);
@ -262,15 +220,6 @@ public:
virtual ~default_ai_context_impl();
virtual void do_attack_analysis(
const map_location& loc, const move_map& srcdst, const move_map& dstsrc,
const move_map& fullmove_srcdst, const move_map& fullmove_dstsrc,
const move_map& enemy_srcdst, const move_map& enemy_dstsrc,
const map_location* tiles, bool* used_locations,
std::vector<map_location>& units,
std::vector<attack_analysis>& result, attack_analysis& cur_analysis);
virtual default_ai_context& get_default_ai_context();

View file

@ -789,7 +789,7 @@ variant formula_ai::get_value(const std::string& key) const
return attacks_cache_;
}
std::vector<attack_analysis> attacks = const_cast<formula_ai*>(this)->analyze_targets(srcdst_, dstsrc_, enemy_srcdst_, enemy_dstsrc_);
const std::vector<attack_analysis> attacks = get_attacks();
std::vector<variant> vars;
for(std::vector<attack_analysis>::const_iterator i = attacks.begin(); i != attacks.end(); ++i) {
vars.push_back(variant(new attack_analysis(*i)));

View file

@ -26,6 +26,7 @@
#include "dfool/ai.hpp"
#include "formula/ai.hpp"
#include "registry.hpp"
#include "testing/aspect_attacks.hpp"
#include "testing/ca.hpp"
#include "testing/stage_rca.hpp"
#include "testing/stage_fallback.hpp"
@ -108,6 +109,9 @@ static register_aspect_factory< composite_aspect<double> >
static register_aspect_factory< composite_aspect<int> >
attack_depth__composite_aspect_factory("attack_depth*composite_aspect");
static register_aspect_factory< composite_aspect< attacks_vector > >
attacks__composite_aspect_factory("attacks*composite_aspect");
static register_aspect_factory< composite_aspect< terrain_filter > >
avoid__composite_aspect_factory("avoid*composite_aspect");
@ -164,6 +168,9 @@ static register_aspect_factory< standard_aspect<double> >
static register_aspect_factory< standard_aspect<int> >
attack_depth__standard_aspect_factory("attack_depth*standard_aspect");
static register_aspect_factory< testing_ai_default::aspect_attacks >
attacks__testing_ai_default_aspect_attacks_factory("attacks*testing_ai_default::aspect_attacks");
static register_aspect_factory< standard_aspect< terrain_filter > >
avoid__standard_aspect_factory("avoid*standard_aspect");
@ -220,6 +227,9 @@ static register_aspect_factory< standard_aspect<double> >
static register_aspect_factory< standard_aspect<int> >
attack_depth__standard_aspect_factory2("attack_depth*");
static register_aspect_factory< testing_ai_default::aspect_attacks >
attacks__testing_ai_default_aspect_attacks_factory2("attacks*");
static register_aspect_factory< standard_aspect< terrain_filter > >
avoid__standard_aspect_factory2("avoid*");

View file

@ -0,0 +1,488 @@
/* $Id$ */
/*
Copyright (C) 2009 by Yurii Chernyi <terraninfo@terraninfo.net>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
or at your option any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
/**
* Stage: fallback to other AI
* @file ai/testing/aspect_attacks.cpp
*/
#include "aspect_attacks.hpp"
#include "../manager.hpp"
#include "../../foreach.hpp"
#include "../../log.hpp"
namespace ai {
namespace testing_ai_default {
static lg::log_domain log_ai_testing_aspect_attacks("ai/aspect/attacks");
#define DBG_AI LOG_STREAM(debug, log_ai_testing_aspect_attacks)
#define LOG_AI LOG_STREAM(info, log_ai_testing_aspect_attacks)
#define ERR_AI LOG_STREAM(err, log_ai_testing_aspect_attacks)
aspect_attacks::aspect_attacks(readonly_context &context, const config &cfg, const std::string &id)
: typesafe_aspect<attacks_vector>(context,cfg,id)
{
}
aspect_attacks::~aspect_attacks()
{
}
void aspect_attacks::recalculate() const
{
this->value_ = analyze_targets();
}
boost::shared_ptr<attacks_vector> aspect_attacks::analyze_targets() const
{
const move_map& srcdst = get_srcdst();
const move_map& dstsrc = get_dstsrc();
const move_map& enemy_srcdst = get_enemy_srcdst();
const move_map& enemy_dstsrc = get_enemy_dstsrc();
boost::shared_ptr<attacks_vector> res(new attacks_vector());
unit_map& units_ = get_info().units;
std::vector<map_location> unit_locs;
for(unit_map::const_iterator i = units_.begin(); i != units_.end(); ++i) {
if(i->second.side() == get_side() && i->second.attacks_left()) {
unit_locs.push_back(i->first);
}
}
bool used_locations[6];
std::fill(used_locations,used_locations+6,false);
moves_map dummy_moves;
move_map fullmove_srcdst, fullmove_dstsrc;
calculate_possible_moves(dummy_moves,fullmove_srcdst,fullmove_dstsrc,false,true);
unit_stats_cache().clear();
for(unit_map::const_iterator j = units_.begin(); j != units_.end(); ++j) {
// Attack anyone who is on the enemy side,
// and who is not invisible or petrified.
if(current_team().is_enemy(j->second.side()) && !j->second.incapacitated() &&
j->second.invisible(j->first,units_,get_info().teams) == false) {
map_location adjacent[6];
get_adjacent_tiles(j->first,adjacent);
attack_analysis analysis;
analysis.target = j->first;
analysis.vulnerability = 0.0;
analysis.support = 0.0;
do_attack_analysis(j->first,srcdst,dstsrc,fullmove_srcdst,fullmove_dstsrc,enemy_srcdst,enemy_dstsrc,
adjacent,used_locations,unit_locs,*res,analysis);
}
}
return res;
}
void aspect_attacks::do_attack_analysis(
const map_location& loc,
const move_map& srcdst, const move_map& dstsrc,
const move_map& fullmove_srcdst, const move_map& fullmove_dstsrc,
const move_map& enemy_srcdst, const move_map& enemy_dstsrc,
const map_location* tiles, bool* used_locations,
std::vector<map_location>& units,
std::vector<attack_analysis>& result,
attack_analysis& cur_analysis
) const
{
// This function is called fairly frequently, so interact with the user here.
raise_user_interact();
if(cur_analysis.movements.size() >= size_t(get_attack_depth())) {
//std::cerr << "ANALYSIS " << cur_analysis.movements.size() << " >= " << get_attack_depth() << "\n";
return;
}
gamemap &map_ = get_info().map;
unit_map &units_ = get_info().units;
std::vector<team> &teams_ = get_info().teams;
static double best_results[6];
if(result.empty()) {
for(int i = 0; i != 6; ++i) {
best_results[i] = 0.0;
}
}
const size_t max_positions = 1000;
if(result.size() > max_positions && !cur_analysis.movements.empty()) {
LOG_AI << "cut analysis short with number of positions\n";
return;
}
const double cur_rating = cur_analysis.movements.empty() ? -1.0 :
cur_analysis.rating(get_aggression(),*this);
double rating_to_beat = cur_rating;
if(!cur_analysis.movements.empty()) {
assert(cur_analysis.movements.size() < 6);
double& best_res = best_results[cur_analysis.movements.size()-1];
rating_to_beat = best_res = std::max(best_res,cur_rating);
}
for(size_t i = 0; i != units.size(); ++i) {
const map_location current_unit = units[i];
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.
//
// See if the unit has the slow ability -- units with slow only attack first.
bool backstab = false, slow = false;
std::vector<attack_type>& attacks = unit_itor->second.attacks();
for(std::vector<attack_type>::iterator a = attacks.begin(); a != attacks.end(); ++a) {
a->set_specials_context(map_location(), map_location(), units_, true, NULL);
if(a->get_special_bool("backstab")) {
backstab = true;
}
if(a->get_special_bool("slow")) {
slow = true;
}
}
if(slow && cur_analysis.movements.empty() == false) {
continue;
}
// Check if the friendly unit is surrounded,
// A unit is surrounded if it is flanked by enemy units
// and at least one other enemy unit is nearby
// or if the unit is totaly surrounded by enemies
// with max. one tile to escape.
bool is_surrounded = false;
bool is_flanked = false;
int enemy_units_around = 0;
int accessible_tiles = 0;
map_location adj[6];
get_adjacent_tiles(current_unit, adj);
size_t tile;
for(tile = 0; tile != 3; ++tile) {
const unit_map::const_iterator tmp_unit = units_.find(adj[tile]);
bool possible_flanked = false;
if(map_.on_board(adj[tile]))
{
accessible_tiles++;
if(tmp_unit != units_.end() && get_side() != tmp_unit->second.side())
{
enemy_units_around++;
possible_flanked = true;
}
}
const unit_map::const_iterator tmp_opposite_unit = units_.find(adj[tile + 3]);
if(map_.on_board(adj[tile + 3]))
{
accessible_tiles++;
if(tmp_opposite_unit != units_.end() && get_side() != tmp_opposite_unit->second.side())
{
enemy_units_around++;
if(possible_flanked)
{
is_flanked = true;
}
}
}
}
if((is_flanked && enemy_units_around > 2) || enemy_units_around >= accessible_tiles - 1)
is_surrounded = true;
double best_vulnerability = 0.0, best_support = 0.0;
int best_rating = 0;
int cur_position = -1;
// Iterate over positions adjacent to the unit, finding the best rated one.
for(int j = 0; j != 6; ++j) {
// If in this planned attack, a unit is already in this location.
if(used_locations[j]) {
continue;
}
// See if the current unit can reach that position.
if (tiles[j] != current_unit) {
typedef std::multimap<map_location,map_location>::const_iterator Itor;
std::pair<Itor,Itor> its = dstsrc.equal_range(tiles[j]);
while(its.first != its.second) {
if(its.first->second == current_unit)
break;
++its.first;
}
// If the unit can't move to this location.
if(its.first == its.second || units_.find(tiles[j]) != units_.end()) {
continue;
}
}
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) {
LOG_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;
if(tiles[(j+3)%6] != current_unit) {
const unit_map::const_iterator itor = units_.find(tiles[(j+3)%6]);
// Note that we *could* also check if a unit plans to move there
// before we're at this stage, but we don't because, since the
// attack calculations don't actually take backstab into account (too complicated),
// this could actually make our analysis look *worse* instead of better.
// So we only check for 'concrete' backstab opportunities.
// That would also break backstab_check, since it assumes
// the defender is in place.
if(itor != units_.end() &&
backstab_check(tiles[j], loc, units_, teams_)) {
if(backstab) {
backstab_bonus = 2;
}
// No surround bonus if target is skirmisher
if (!itor->second.get_ability_bool("skirmisker"))
surround_bonus = 1.2;
}
}
// See if this position is the best rated we've seen so far.
const int rating = static_cast<int>(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.
//FIXME: suokko's r29531 multiplied this by a constant 1.5. ?
const double vulnerability = power_projection(tiles[j],enemy_dstsrc);//?
// Calculate how much support we have on this hex from allies.
const double support = power_projection(tiles[j], fullmove_dstsrc);//?
// If this is a position with equal defense to another position,
// but more vulnerability then we don't want to use it.
#ifdef SUOKKO
//FIXME: this code was in sukko's r29531 Correct?
// 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;
}
#else
if(cur_position >= 0 && rating == best_rating && vulnerability/surround_bonus - support*surround_bonus >= best_vulnerability - best_support) {
continue;
}
#endif
cur_position = j;
best_rating = rating;
#ifdef SUOKKO
//FIXME: this code was in sukko's r29531 Correct?
best_vulnerability = (vulnerability/surround_bonus*30.0)/unit_itor->second.hitpoints();
best_support = (support*surround_bonus*30.0)/unit_itor->second.max_hitpoints();
#else
best_vulnerability = vulnerability/surround_bonus;
best_support = support*surround_bonus;
#endif
}
if(cur_position != -1) {
units.erase(units.begin() + i);
cur_analysis.movements.push_back(std::pair<map_location,map_location>(current_unit,tiles[cur_position]));
cur_analysis.vulnerability += best_vulnerability;
cur_analysis.support += best_support;
cur_analysis.is_surrounded = is_surrounded;
cur_analysis.analyze(map_, units_, *this, dstsrc, srcdst, enemy_dstsrc, get_aggression());
result.push_back(cur_analysis);
used_locations[cur_position] = true;
do_attack_analysis(loc,srcdst,dstsrc,fullmove_srcdst,fullmove_dstsrc,enemy_srcdst,enemy_dstsrc,
tiles,used_locations,
units,result,cur_analysis);
used_locations[cur_position] = false;
cur_analysis.vulnerability -= best_vulnerability;
cur_analysis.support -= best_support;
cur_analysis.movements.pop_back();
units.insert(units.begin() + i, current_unit);
}
}
}
int aspect_attacks::rate_terrain(const unit& u, const map_location& loc) const
{
gamemap &map_ = get_info().map;
const t_translation::t_terrain terrain = map_.get_terrain(loc);
const int defense = u.defense_modifier(terrain);
int rating = 100 - defense;
const int healing_value = 10;
const int friendly_village_value = 5;
const int neutral_village_value = 10;
const int enemy_village_value = 15;
if(map_.gives_healing(terrain) && u.get_ability_bool("regenerates",loc) == false) {
rating += healing_value;
}
if(map_.is_village(terrain)) {
int owner = village_owner(loc, get_info().teams) + 1;
if(owner == get_side()) {
rating += friendly_village_value;
} else if(owner == 0) {
rating += neutral_village_value;
} else {
rating += enemy_village_value;
}
}
return rating;
}
double aspect_attacks::power_projection(const map_location& loc, const move_map& dstsrc) const
{
map_location used_locs[6];
int ratings[6];
int num_used_locs = 0;
map_location locs[6];
get_adjacent_tiles(loc,locs);
const int lawful_bonus = get_info().tod_manager_.get_time_of_day().lawful_bonus;
gamemap& map_ = get_info().map;
unit_map& units_ = get_info().units;
int res = 0;
bool changed = false;
for (int i = 0;; ++i) {
if (i == 6) {
if (!changed) break;
// Loop once again, in case a unit found a better spot
// and freed the place for another unit.
changed = false;
i = 0;
}
if (map_.on_board(locs[i]) == false) {
continue;
}
const t_translation::t_terrain terrain = map_[locs[i]];
typedef move_map::const_iterator Itor;
typedef std::pair<Itor,Itor> Range;
Range its = dstsrc.equal_range(locs[i]);
map_location* const beg_used = used_locs;
map_location* end_used = used_locs + num_used_locs;
int best_rating = 0;
map_location best_unit;
for(Itor it = its.first; it != its.second; ++it) {
const unit_map::const_iterator u = units_.find(it->second);
// Unit might have been killed, and no longer exist
if(u == units_.end()) {
continue;
}
const unit& un = u->second;
int tod_modifier = 0;
if(un.alignment() == unit_type::LAWFUL) {
tod_modifier = lawful_bonus;
} else if(un.alignment() == unit_type::CHAOTIC) {
tod_modifier = -lawful_bonus;
}
// The 0.5 power avoids underestimating too much the damage of a wounded unit.
int hp = int(sqrt(double(un.hitpoints()) / un.max_hitpoints()) * 1000);
int most_damage = 0;
foreach (const attack_type &att, un.attacks())
{
int damage = att.damage() * att.num_attacks() * (100 + tod_modifier);
if (damage > most_damage) {
most_damage = damage;
}
}
int village_bonus = map_.is_village(terrain) ? 3 : 2;
int defense = 100 - un.defense_modifier(terrain);
int rating = hp * defense * most_damage * village_bonus / 200;
if(rating > best_rating) {
map_location *pos = std::find(beg_used, end_used, it->second);
// Check if the spot is the same or better than an older one.
if (pos == end_used || rating >= ratings[pos - beg_used]) {
best_rating = rating;
best_unit = it->second;
}
}
}
if (!best_unit.valid()) continue;
map_location *pos = std::find(beg_used, end_used, best_unit);
int index = pos - beg_used;
if (index == num_used_locs)
++num_used_locs;
else if (best_rating == ratings[index])
continue;
else {
// The unit was in another spot already, so remove its older rating
// from the final result, and require a new run to fill its old spot.
res -= ratings[index];
changed = true;
}
used_locs[index] = best_unit;
ratings[index] = best_rating;
res += best_rating;
}
return res / 100000.;
}
} // end of namespace testing_ai_default
} // end of namespace ai

View file

@ -0,0 +1,82 @@
/* $Id$ */
/*
Copyright (C) 2009 by Yurii Chernyi <terraninfo@terraninfo.net>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
or at your option any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
/**
* Aspect: attacks
* @file ai/testing/aspect_attacks.hpp
*/
#ifndef AI_TESTING_ASPECT_ATTACKS_HPP_INCLUDED
#define AI_TESTING_ASPECT_ATTACKS_HPP_INCLUDED
#include "../../global.hpp"
#include "../composite/aspect.hpp"
#include "../interface.hpp"
#include "../../config.hpp"
#include <vector>
#include <boost/shared_ptr.hpp>
#ifdef _MSC_VER
#pragma warning(push)
//silence "inherits via dominance" warnings
#pragma warning(disable:4250)
#endif
namespace ai {
namespace testing_ai_default {
class aspect_attacks: public typesafe_aspect<attacks_vector> {
public:
aspect_attacks(readonly_context &context, const config &cfg, const std::string &id);
virtual ~aspect_attacks();
virtual void recalculate() const;
protected:
boost::shared_ptr<attacks_vector> analyze_targets() const;
void do_attack_analysis(
const map_location& loc,
const move_map& srcdst, const move_map& dstsrc,
const move_map& fullmove_srcdst, const move_map& fullmove_dstsrc,
const move_map& enemy_srcdst, const move_map& enemy_dstsrc,
const map_location* tiles, bool* used_locations,
std::vector<map_location>& units,
std::vector<attack_analysis>& result,
attack_analysis& cur_analysis
) const;
int rate_terrain(const unit& u, const map_location& loc) const;
double power_projection(const map_location& loc, const move_map& dstsrc) const;
};
} // end of namespace testing_ai_default
} // end of namespace ai
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#endif

View file

@ -559,7 +559,7 @@ double combat_phase::evaluate()
choice_rating_ = -1000.0;
int ticks = SDL_GetTicks();
std::vector<attack_analysis> analysis = analyze_targets(get_srcdst(), get_dstsrc(), get_enemy_srcdst(), get_enemy_dstsrc());
const std::vector<attack_analysis> analysis = get_attacks();
int time_taken = SDL_GetTicks() - ticks;
LOG_AI_TESTING_AI_DEFAULT << "took " << time_taken << " ticks for " << analysis.size()
@ -579,8 +579,8 @@ double combat_phase::evaluate()
const int max_positions = 30000;
const int skip_num = analysis.size()/max_positions;
std::vector<attack_analysis>::iterator choice_it = analysis.end();
for(std::vector<attack_analysis>::iterator it = analysis.begin();
std::vector<attack_analysis>::const_iterator choice_it = analysis.end();
for(std::vector<attack_analysis>::const_iterator it = analysis.begin();
it != analysis.end(); ++it) {
if(skip_num > 0 && ((it - analysis.begin())%skip_num) && it->movements.size() > 1)