ai_composite: new candidate action: testing_ai_default::combat_phase

This commit is contained in:
Iurii Chernyi 2009-06-21 19:49:34 +00:00
parent 3b3419f3a6
commit 7a270d527c
21 changed files with 1069 additions and 531 deletions

View file

@ -0,0 +1,36 @@
[event]
name=preload
first_time_only=no
[lua]
code = << register_test('0003-simple_combat','Simple combat test'); >>
[/lua]
[/event]
[event]
name=0003-simple_combat
first_time_only=no
[message]
speaker=narrator
image=wesnoth-icon.png
message=_ "This situation should test the ability of AI to make an attack..."
[/message]
[unit]
side=2
x,y=20,7
type="Dwarvish Berserker"
[/unit]
[unit]
side=3
x,y=20,5
type="Dark Adept"
random_traits="no"
[modifications]
{TRAIT_RESILIENT}
[/modifications]
[/unit]
[modify_side]
side=3
switch_ai=$test_path_to_idle_ai
[/modify_side]
[/event]

View file

@ -54,6 +54,10 @@
name=test_path_to_formula_ai
value=ai/ais/formula_ai.cfg
[/set_variable]
[set_variable]
name=test_path_to_testing_ai_default
value=ai/dev/testing_ai_default.cfg
[/set_variable]
[set_variable]
name=test_id
value=0002-poisoning
@ -172,6 +176,15 @@
[option]
message=_ "I am happy with the current AI of team 2, [$test_path_to_ai]"
[/option]
[option]
message=_ "My AI is TESTING AI DEFAULT, implemented as ai_composite."
[command]
[set_variable]
name=test_path_to_ai
value=$test_path_to_testing_ai_default
[/set_variable]
[/command]
[/option]
[option]
message=_ "My AI is DEFAULT AI, and it strikes fear into the hearts of mortals."
[command]

View file

@ -36,8 +36,11 @@
#include "manager.hpp"
#include "../actions.hpp"
#include "../dialogs.hpp"
#include "../game_end_exceptions.hpp"
#include "../game_preferences.hpp"
#include "../log.hpp"
#include "../mouse_handler_base.hpp"
#include "../pathfind.hpp"
#include "../replay.hpp"
#include "../statistics.hpp"
@ -65,7 +68,7 @@ action_result::action_result( side_number side )
action_result::~action_result()
{
if (!return_value_checked_) {
ERR_AI_ACTIONS << "Return value of AI ACTION was not checked. This may cause bugs! " << std::endl;
ERR_AI_ACTIONS << "Return value of AI ACTION was not checked. This may cause bugs! " << std::endl;
}
}
@ -99,11 +102,18 @@ void action_result::execute()
void action_result::init_for_execution()
{
return_value_checked_ = false;
is_gamestate_changed_ = false;
status_ = action_result::AI_ACTION_SUCCESS;
do_init_for_execution();
}
bool action_result::is_gamestate_changed() const
{
return is_gamestate_changed_;
}
bool action_result::is_ok()
{
return_value_checked_ = true;
@ -117,6 +127,12 @@ void action_result::set_error(int error_code){
}
void action_result::set_gamestate_changed()
{
is_gamestate_changed_ = true;
}
int action_result::get_status(){
return status_;
}
@ -167,6 +183,7 @@ attack_result::attack_result( side_number side, const map_location& attacker_loc
void attack_result::do_check_before()
{
LOG_AI_ACTIONS << " check_before " << *this << std::endl;
}
@ -190,6 +207,90 @@ std::string attack_result::do_describe() const
void attack_result::do_execute()
{
LOG_AI_ACTIONS << "start of execution of: "<< *this << std::endl;
// Stop the user from issuing any commands while the unit is attacking
const events::command_disabler disable_commands;
if(!get_info().units.count(attacker_loc_))
{
ERR_AI_ACTIONS << "attempt to attack without attacker\n";
set_error(E_EMPTY_ATTACKER);
return;
}
if (!get_info().units.count(defender_loc_))
{
ERR_AI_ACTIONS << "attempt to attack without defender\n";
set_error(E_EMPTY_DEFENDER);
return;
}
if(get_info().units.find(attacker_loc_)->second.incapacitated()) {
ERR_AI_ACTIONS << "attempt to attack with unit that is petrified\n";
set_error(E_INCAPACITATED_ATTACKER);
return;
}
if(get_info().units.find(defender_loc_)->second.incapacitated()) {
ERR_AI_ACTIONS << "attempt to attack unit that is petrified\n";
set_error(E_INCAPACITATED_DEFENDER);
return;
}
if(!get_info().units.find(attacker_loc_)->second.attacks_left()) {
ERR_AI_ACTIONS << "attempt to attack with no attacks left\n";
set_error(E_NO_ATTACKS_LEFT);
return;
}
//CHECK OWN(ATTACKER)
//CHECK ENEMY(DEFENDER)
//CHECK ATTACKER WEAPON
battle_context bc(get_info().map, get_info().teams, get_info().units,
get_info().state, get_info().tod_manager_, attacker_loc_,
defender_loc_, attacker_weapon_, -1, get_my_team(get_info()).aggression());
int attacker_weapon = bc.get_attacker_stats().attack_num;
int defender_weapon = bc.get_defender_stats().attack_num;
if(attacker_weapon_ >= 0) {
recorder.add_attack(attacker_loc_,defender_loc_,attacker_weapon,defender_weapon);
}
try {
attack(get_info().disp, get_info().map, get_info().teams, attacker_loc_,
defender_loc_,attacker_weapon,defender_weapon, get_info().units,
get_info().state, get_info().tod_manager_);
}
catch (end_level_exception&)
{
dialogs::advance_unit(get_info().map,get_info().units,attacker_loc_,get_info().disp,true);
const unit_map::const_iterator defender = get_info().units.find(defender_loc_);
if(defender != get_info().units.end()) {
const size_t defender_team = size_t(defender->second.side()) - 1;
if(defender_team < get_info().teams.size()) {
dialogs::advance_unit(get_info().map, get_info().units,
defender_loc_, get_info().disp, !get_info().teams[defender_team].is_human());
}
}
throw;
}
dialogs::advance_unit(get_info().map,get_info().units,attacker_loc_,get_info().disp,true);
const unit_map::const_iterator defender = get_info().units.find(defender_loc_);
if(defender != get_info().units.end()) {
const size_t defender_team = size_t(defender->second.side()) - 1;
if(defender_team < get_info().teams.size()) {
dialogs::advance_unit(get_info().map, get_info().units,
defender_loc_ , get_info().disp, !get_info().teams[defender_team].is_human());
}
}
check_victory(get_info().state,get_info().units,get_info().teams, get_info().disp);
manager::raise_enemy_attacked();
}
@ -208,6 +309,7 @@ move_result::move_result(side_number side, const map_location& from,
, to_(to)
, remove_movement_(remove_movement)
, route_()
, unit_location_(from)
{
}
@ -252,7 +354,7 @@ bool move_result::test_route(const unit &un, const team &my_team, const unit_map
void move_result::do_check_before()
{
DBG_AI_ACTIONS << " check_before " << *this << std::endl;
LOG_AI_ACTIONS << " check_before " << *this << std::endl;
const game_info& s_info = get_subjective_info();
const game_info& info = get_info();
@ -280,6 +382,12 @@ void move_result::do_check_before()
}
const map_location& move_result::get_unit_location() const
{
return unit_location_;
}
void move_result::do_check_after()
{
}
@ -303,13 +411,13 @@ std::string move_result::do_describe() const
void move_result::do_execute()
{
DBG_AI_ACTIONS << " execute "<< *this << std::endl;
LOG_AI_ACTIONS << "start of execution of: "<< *this << std::endl;
assert(is_success());
game_info& info = get_info();
move_unit(
/*game_display* disp*/ NULL,
/*game_display* disp*/ &info.disp,
/*const gamemap& map*/ info.map,
/*unit_map& units*/ info.units,
/*std::vector<team>& teams*/ info.teams,
@ -317,9 +425,10 @@ void move_result::do_execute()
/*replay* move_recorder*/ &recorder,
/*undo_list* undo_stack*/ NULL,
/*map_location *next_unit*/ NULL,
/*bool continue_move*/ false,
/*bool continue_move*/ true, //@todo: 1.7 set to false after implemeting interrupt awareness
/*bool should_clear_shroud*/ true,
/*bool is_replay*/ false);
unit_location_ = to_;//@todo: 1.7 modify move_unit to get this info from it
}
@ -412,7 +521,7 @@ bool recruit_result::test_suitable_recruit_location(const gamemap &map, const un
void recruit_result::do_check_before()
{
DBG_AI_ACTIONS << " check_before " << *this << std::endl;
LOG_AI_ACTIONS << " check_before " << *this << std::endl;
const game_info& s_info = get_subjective_info();
const game_info& info = get_info();
@ -518,7 +627,7 @@ std::string recruit_result::do_describe() const
void recruit_result::do_execute()
{
DBG_AI_ACTIONS << " execute: " << *this << std::endl;
LOG_AI_ACTIONS << "start of execution of: " << *this << std::endl;
assert(is_success());
game_info& info = get_info();
// We have to add the recruit command now, because when the unit
@ -581,7 +690,7 @@ const unit *stopunit_result::get_unit(const unit_map &units, bool)
void stopunit_result::do_check_before()
{
DBG_AI_ACTIONS << " check_before " << *this << std::endl;
LOG_AI_ACTIONS << " check_before " << *this << std::endl;
const game_info& s_info = get_subjective_info();
const game_info& info = get_info();
@ -632,7 +741,7 @@ std::string stopunit_result::do_describe() const
void stopunit_result::do_execute()
{
DBG_AI_ACTIONS << " execute: " << *this << std::endl;
LOG_AI_ACTIONS << "start of execution of: " << *this << std::endl;
assert(is_success());
const game_info& info = get_info();
unit_map::iterator un = info.units.find(unit_location_);

View file

@ -48,6 +48,9 @@ public:
/* execute the action */
void execute();
/* has the game state changed during execution ? */
bool is_gamestate_changed() const;
/* check the return value of the action. mandatory to call. */
bool is_ok();
@ -97,6 +100,9 @@ protected:
/* is error code equal to 0 (no errors)? */
bool is_success() const;
/* note that the game state has been changed */
void set_gamestate_changed();
private:
/* Check after the execution */
@ -120,6 +126,8 @@ private:
/* are we going to execute the action now ? */
bool is_execution_;
bool is_gamestate_changed_;
};
class attack_result : public action_result {
@ -128,6 +136,14 @@ public:
const map_location& attacker_loc,
const map_location& defender_loc,
int attacker_weapon );
static const int E_EMPTY_ATTACKER = 1001;
static const int E_EMPTY_DEFENDER = 1002;
static const int E_INCAPACITATED_ATTACKER = 1003;
static const int E_INCAPACITATED_DEFENDER = 1004;
static const int E_NOT_OWN_ATTACKER = 1005;
static const int E_NOT_ENEMY_DEFENDER = 1006;
static const int E_NO_ATTACKS_LEFT = 1007;
static const int E_WRONG_ATTACKER_WEAPON = 1008;
virtual std::string do_describe() const;
protected:
virtual void do_check_before();
@ -151,6 +167,7 @@ public:
static const int E_NOT_OWN_UNIT = 2003;
static const int E_INCAPACITATED_UNIT = 2004;
virtual std::string do_describe() const;
virtual const map_location& get_unit_location() const;
protected:
virtual void do_check_before();
virtual void do_check_after();
@ -163,6 +180,7 @@ private:
const map_location& to_;
bool remove_movement_;
plain_route route_;
map_location unit_location_;
};
class recruit_result : public action_result {

View file

@ -46,6 +46,10 @@ public:
int get_recursion_count() const{
return recursion_counter_.get_count();
}
virtual void new_turn()
{
}
private:
recursion_counter recursion_counter_;

View file

@ -75,6 +75,19 @@ void ai_composite::play_turn(){
}
void ai_composite::new_turn()
{
//todo 1.7 replace with event system
recalculate_move_maps();
invalidate_attack_depth_cache();
invalidate_avoided_locations_cache();
invalidate_defensive_position_cache();
invalidate_recent_attacks_list();
invalidate_keeps_cache();
unit_stats_cache().clear();
}
engine_ptr ai_composite::get_engine(const config& cfg)
{
const std::string& engine_name = cfg["engine"];

View file

@ -64,6 +64,11 @@ public:
void play_turn();
/**
* On new turn
*/
virtual void new_turn();
/**
* get engine by cfg, creating it if it is not created yet but known
*/

View file

@ -89,7 +89,10 @@ void readwrite_context_impl::raise_enemy_attacked() const
attack_result_ptr readwrite_context_impl::execute_attack_action(const map_location& attacker_loc, const map_location& defender_loc, int attacker_weapon){
return actions::execute_attack_action(get_side(),true,attacker_loc,defender_loc,attacker_weapon);
attack_result_ptr r = actions::execute_attack_action(get_side(),true,attacker_loc,defender_loc,attacker_weapon);
recalculate_move_maps();//@todo 1.7 replace with event system
return r;
}
@ -99,7 +102,9 @@ attack_result_ptr readonly_context_impl::check_attack_action(const map_location&
move_result_ptr readwrite_context_impl::execute_move_action(const map_location& from, const map_location& to, bool remove_movement){
return actions::execute_move_action(get_side(),true,from,to,remove_movement);
move_result_ptr r = actions::execute_move_action(get_side(),true,from,to,remove_movement);
recalculate_move_maps();//@todo 1.7 replace with event system
return r;
}
@ -109,7 +114,9 @@ move_result_ptr readonly_context_impl::check_move_action(const map_location& fro
recruit_result_ptr readwrite_context_impl::execute_recruit_action(const std::string& unit_name, const map_location &where){
return actions::execute_recruit_action(get_side(),true,unit_name,where);
recruit_result_ptr r = actions::execute_recruit_action(get_side(),true,unit_name,where);
recalculate_move_maps();//@todo 1.7 replace with event system
return r;
}
@ -119,7 +126,9 @@ recruit_result_ptr readonly_context_impl::check_recruit_action(const std::string
stopunit_result_ptr readwrite_context_impl::execute_stopunit_action(const map_location& unit_location, bool remove_movement, bool remove_attacks){
return actions::execute_stopunit_action(get_side(),true,unit_location,remove_movement,remove_attacks);
stopunit_result_ptr r = actions::execute_stopunit_action(get_side(),true,unit_location,remove_movement,remove_attacks);
recalculate_move_maps();//@todo 1.7 replace with event system
return r;
}
@ -185,6 +194,7 @@ bool readwrite_context_impl::recruit(const std::string& unit_name, map_location
((data.net_income < 0) ? "" : "+") <<
data.net_income << "\n";
recorder.add_checksum_check(loc);
recalculate_move_maps();
return true;
} else {
const team_data data = calculate_team_data(current_team(),get_side(),get_info().units);
@ -195,6 +205,7 @@ bool readwrite_context_impl::recruit(const std::string& unit_name, map_location
" gold=" << data.gold <<
((data.net_income < 0) ? "" : "+") <<
data.net_income << "\n";
recalculate_move_maps();
return false;
}
}
@ -239,7 +250,7 @@ map_location readwrite_context_impl::move_unit(map_location from, map_location t
u->second.set_movement(0);
}
}
recalculate_move_maps();
return loc;
}
@ -257,11 +268,13 @@ map_location readwrite_context_impl::move_unit_partial(map_location from, map_lo
if(u_it == get_info().units.end()) {
ERR_AI << "Could not find unit at " << from << '\n';
assert(false);
recalculate_move_maps();
return map_location();
}
if(from == to) {
LOG_AI << "moving unit at " << from << " on spot. resetting moves\n";
recalculate_move_maps();
return to;
}
@ -404,7 +417,7 @@ map_location readwrite_context_impl::move_unit_partial(map_location from, map_lo
// would have to go via mousehandler to make this work:
//get_info().disp.unhighlight_reach();
raise_unit_moved();
recalculate_move_maps();
return to;
}
@ -559,6 +572,81 @@ void readwrite_context_impl::attack_enemy(const map_location u,
check_victory(get_info().state,get_info().units,get_info().teams, get_info().disp);
raise_enemy_attacked();
recalculate_move_maps();
}
const std::set<map_location>& readonly_context_impl::avoided_locations()
{
if(avoided_locations_.empty()) {
foreach (const config &av, current_team().ai_parameters().child_range("avoid"))
{
foreach (const map_location &loc, parse_location_range(av["x"], av["y"])) {
avoided_locations_.insert(loc);
}
}
if(avoided_locations_.empty()) {
avoided_locations_.insert(map_location());
}
}
return avoided_locations_;
}
const move_map& readonly_context_impl::get_dstsrc() const
{
return dstsrc_;
}
const move_map& readonly_context_impl::get_enemy_dstsrc() const
{
return enemy_dstsrc_;
}
const moves_map& readonly_context_impl::get_enemy_possible_moves() const
{
return enemy_possible_moves_;
}
const move_map& readonly_context_impl::get_enemy_srcdst() const
{
return enemy_srcdst_;
}
const moves_map& readonly_context_impl::get_possible_moves() const
{
return possible_moves_;
}
const move_map& readonly_context_impl::get_srcdst() const
{
return srcdst_;
}
void readonly_context_impl::invalidate_avoided_locations_cache(){
avoided_locations_.clear();
}
void readonly_context_impl::recalculate_move_maps()
{
dstsrc_ = move_map();
enemy_dstsrc_ = move_map();
enemy_srcdst_ = move_map();
enemy_possible_moves_ = moves_map();
possible_moves_ = moves_map();
srcdst_ = move_map();
calculate_possible_moves(possible_moves_,srcdst_,dstsrc_,false,false,&avoided_locations());
calculate_possible_moves(enemy_possible_moves_,enemy_srcdst_,enemy_dstsrc_,true);
}
} //of namespace ai

View file

@ -148,9 +148,39 @@ public:
move_map& dstsrc, bool enemy, bool assume_full_movement=false,
const std::set<map_location>* remove_destinations=NULL,
bool see_all=false) const = 0;
const virtual game_info& get_info() const = 0;
virtual void raise_user_interact() const = 0;
virtual const std::set<map_location>& avoided_locations() = 0;
virtual const move_map& get_dstsrc() const = 0;
virtual const move_map& get_enemy_dstsrc() const = 0;
virtual const moves_map& get_enemy_possible_moves() const = 0;
virtual const move_map& get_enemy_srcdst() const = 0;
virtual const moves_map& get_possible_moves() const = 0;
virtual const move_map& get_srcdst() const = 0;
virtual void invalidate_avoided_locations_cache() = 0;
virtual void recalculate_move_maps() = 0;
};
class readwrite_context;
@ -306,11 +336,66 @@ public:
target_->raise_user_interact();
}
virtual const std::set<map_location>& avoided_locations()
{
return target_->avoided_locations();
}
virtual int get_recursion_count() const
{
return target_->get_recursion_count();
}
virtual const move_map& get_dstsrc() const
{
return target_->get_dstsrc();
}
virtual const move_map& get_enemy_dstsrc() const
{
return target_->get_enemy_dstsrc();
}
virtual const moves_map& get_enemy_possible_moves() const
{
return target_->get_enemy_possible_moves();
}
virtual const move_map& get_enemy_srcdst() const
{
return target_->get_enemy_srcdst();
}
virtual const moves_map& get_possible_moves() const
{
return target_->get_possible_moves();
}
virtual const move_map& get_srcdst() const
{
return target_->get_srcdst();
}
virtual void invalidate_avoided_locations_cache()
{
target_->invalidate_avoided_locations_cache();
}
virtual void recalculate_move_maps()
{
target_->recalculate_move_maps();
}
private:
readonly_context *target_;
};
@ -467,7 +552,7 @@ public:
* Constructor
*/
readonly_context_impl(side_context &context)
: recursion_counter_(context.get_recursion_count())
: recursion_counter_(context.get_recursion_count()),dstsrc_(),enemy_dstsrc_(),enemy_possible_moves_(),enemy_srcdst_(),possible_moves_(),srcdst_(),avoided_locations_()
{
init_side_context_proxy(context);
}
@ -603,10 +688,46 @@ public:
*/
void raise_user_interact() const;
virtual const std::set<map_location>& avoided_locations();
virtual int get_recursion_count() const;
virtual const move_map& get_dstsrc() const;
virtual const move_map& get_enemy_dstsrc() const;
virtual const moves_map& get_enemy_possible_moves() const;
virtual const move_map& get_enemy_srcdst() const;
virtual const moves_map& get_possible_moves() const;
virtual const move_map& get_srcdst() const;
virtual void invalidate_avoided_locations_cache();
virtual void recalculate_move_maps();
private:
recursion_counter recursion_counter_;
move_map dstsrc_;
move_map enemy_dstsrc_;
moves_map enemy_possible_moves_;
move_map enemy_srcdst_;
moves_map possible_moves_;
move_map srcdst_;
std::set<map_location> avoided_locations_;
};

View file

@ -64,6 +64,12 @@ std::string idle_ai::describe_self()
return "[idle_ai]";
}
void idle_ai::new_turn()
{
}
void idle_ai::switch_side(side_number side)
{
set_side(side);
@ -249,8 +255,6 @@ ai_default::ai_default(default_ai_context &context) :
unit_movement_scores_(),
not_recommended_units_(),
unit_combat_scores_(),
avoid_(),
attack_depth_(0),
recruiting_preferred_(0),
formula_ai_()
{
@ -268,6 +272,8 @@ void ai_default::switch_side(side_number side){
void ai_default::new_turn()
{
invalidate_attack_depth_cache();
invalidate_avoided_locations_cache();
invalidate_defensive_position_cache();
invalidate_recent_attacks_list();
threats_found_ = false;
@ -277,13 +283,10 @@ void ai_default::new_turn()
not_recommended_units_.clear();
unit_combat_scores_.clear();
invalidate_keeps_cache();
avoid_.clear();
unit_stats_cache().clear();
attack_depth_ = 0;
if (formula_ai_ != NULL){
formula_ai_->new_turn();
}
interface::new_turn();
}
std::string ai_default::describe_self(){
@ -1753,36 +1756,6 @@ void ai_default::move_leader_after_recruit(const move_map& /*srcdst*/,
}
}
int ai_default::rate_terrain(const unit& u, const map_location& loc)
{
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, 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;
}
bool ai_default::is_accessible(const location& loc, const move_map& dstsrc) const
{
@ -1797,34 +1770,6 @@ bool ai_default::is_accessible(const location& loc, const move_map& dstsrc) cons
return dstsrc.count(loc) > 0;
}
const std::set<map_location>& ai_default::avoided_locations()
{
if(avoid_.empty()) {
foreach (const config &av, current_team().ai_parameters().child_range("avoid"))
{
foreach (const location &loc, parse_location_range(av["x"], av["y"])) {
avoid_.insert(loc);
}
}
if(avoid_.empty()) {
avoid_.insert(location());
}
}
return avoid_;
}
int ai_default::attack_depth()
{
if(attack_depth_ > 0) {
return attack_depth_;
}
const config& parms = current_team().ai_parameters();
attack_depth_ = std::max<int>(1,lexical_cast_default<int>(parms["attack_depth"],5));
return attack_depth_;
}
variant ai_default::get_value(const std::string& key) const
{

View file

@ -37,94 +37,12 @@ class formula_ai;
namespace ai {
struct attack_analysis : public game_logic::formula_callable
{
attack_analysis() :
game_logic::formula_callable(),
target(),
movements(),
target_value(0.0),
avg_losses(0.0),
chance_to_kill(0.0),
avg_damage_inflicted(0.0),
target_starting_damage(0),
avg_damage_taken(0.0),
resources_used(0.0),
terrain_quality(0.0),
alternative_terrain_quality(0.0),
vulnerability(0.0),
support(0.0),
leader_threat(false),
uses_leader(false),
is_surrounded(false)
{
}
void analyze(const gamemap& map, unit_map& units,
const std::vector<team>& teams,
const gamestatus& status, const tod_manager& tod_mng,
class default_ai_context& ai_obj,
const move_map& dstsrc, const move_map& srcdst,
const move_map& enemy_dstsrc, double aggression);
double rating(double aggression, class default_ai_context& ai_obj) const;
variant get_value(const std::string& key) const;
void get_inputs(std::vector<game_logic::formula_input>* inputs) const;
map_location target;
std::vector<std::pair<map_location,map_location> > movements;
/** The value of the unit being targeted. */
double target_value;
/** The value on average, of units lost in the combat. */
double avg_losses;
/** Estimated % chance to kill the unit. */
double chance_to_kill;
/** The average hitpoints damage inflicted. */
double avg_damage_inflicted;
int target_starting_damage;
/** The average hitpoints damage taken. */
double avg_damage_taken;
/** The sum of the values of units used in the attack. */
double resources_used;
/** The weighted average of the % chance to hit each attacking unit. */
double terrain_quality;
/**
* The weighted average of the % defense of the best possible terrain
* that the attacking units could reach this turn, without attacking
* (good for comparison to see just how good/bad 'terrain_quality' is).
*/
double alternative_terrain_quality;
/**
* The vulnerability is the power projection of enemy units onto the hex
* we're standing on. support is the power projection of friendly units.
*/
double vulnerability, support;
/** Is true if the unit is a threat to our leader. */
bool leader_threat;
/** Is true if this attack sequence makes use of the leader. */
bool uses_leader;
/** Is true if the units involved in this attack sequence are surrounded. */
bool is_surrounded;
};
/** A trivial ai that sits around doing absolutely nothing. */
class idle_ai : public readwrite_context_proxy, public interface {
public:
idle_ai(readwrite_context &context);
void play_turn();
void new_turn();
virtual std::string describe_self();
void switch_side(side_number side);
int get_recursion_count() const;
@ -232,22 +150,6 @@ public:
protected:
virtual void do_attack_analysis(
const 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 location* tiles, bool* used_locations,
std::vector<location>& units,
std::vector<attack_analysis>& result,
attack_analysis& cur_analysis
);
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
);
bool is_accessible(const location& loc, const move_map& dstsrc) const;
virtual std::vector<target> find_targets(unit_map::const_iterator leader,
@ -280,9 +182,6 @@ protected:
virtual std::pair<location,location> choose_move(std::vector<target>& targets,
const move_map& srcdst, const move_map& dstsrc, const move_map& enemy_dstsrc);
/** Rates the value of moving onto certain terrain for a unit. */
virtual int rate_terrain(const unit& u, const location& loc);
game_display& disp_;
gamemap& map_;
unit_map& units_;
@ -346,16 +245,7 @@ protected:
void access_points(const move_map& srcdst, const location& u,
const location& dst, std::vector<location>& out);
/**
* Function which gets the areas of the map that this AI has been
* instructed to avoid.
*/
const std::set<location>& avoided_locations();
std::set<location> avoid_;
int attack_depth();
int attack_depth_;
friend struct attack_analysis;
private:

View file

@ -32,271 +32,6 @@ static lg::log_domain log_ai("ai/attack");
namespace ai {
const int max_positions = 10000;
/** Analyze possibility of attacking target on 'loc'. */
void ai_default::do_attack_analysis(
const 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 location* tiles, bool* used_locations,
std::vector<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(attack_depth())) {
//std::cerr << "ANALYSIS " << cur_analysis.movements.size() << " >= " << attack_depth() << "\n";
return;
}
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(current_team().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 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_,&map_,&state_,&tod_manager_,&teams_,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<location,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<location,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_, teams_, state_, tod_manager_, *this, dstsrc, srcdst, enemy_dstsrc, current_team().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(current_team().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);
}
}
}
void attack_analysis::analyze(const gamemap& map, unit_map& units,
const std::vector<team>& teams,
const gamestatus& status, const tod_manager& tod_mng,
@ -599,59 +334,6 @@ double attack_analysis::rating(double aggression, default_ai_context& ai_obj) co
return value;
}
std::vector<attack_analysis> ai_default::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;
std::vector<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);
std::map<location,paths> 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_,teams_) == false) {
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;
}
/**
* There is no real hope for us: we should try to do some damage to the enemy.
* We can spend some cycles here, since it's rare.

View file

@ -22,6 +22,13 @@
#include "../../attack_prediction.hpp"
#include "../../foreach.hpp"
#include "../../log.hpp"
static lg::log_domain log_ai("ai/general");
#define DBG_AI LOG_STREAM(debug, log_ai)
#define LOG_AI LOG_STREAM(info, log_ai)
#define WRN_AI LOG_STREAM(warn, log_ai)
#define ERR_AI LOG_STREAM(err, log_ai)
// =======================================================================
namespace ai {
@ -55,6 +62,62 @@ void default_ai_context_impl::add_recent_attack(map_location loc)
attacks_.insert(loc);
}
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;
}
bool default_ai_context_impl::attack_close(const map_location& loc) const
{
@ -68,6 +131,18 @@ bool default_ai_context_impl::attack_close(const map_location& loc) const
}
int default_ai_context_impl::attack_depth()
{
if(attack_depth_ > 0) {
return attack_depth_;
}
const config& parms = current_team().ai_parameters();
attack_depth_ = std::max<int>(1,lexical_cast_default<int>(parms["attack_depth"],5));
return attack_depth_;
}
default_ai_context_impl::~default_ai_context_impl()
{
}
@ -131,12 +206,281 @@ std::map<map_location,defensive_position>& default_ai_context_impl::defensive_po
return defensive_position_cache_;
}
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(attack_depth())) {
//std::cerr << "ANALYSIS " << cur_analysis.movements.size() << " >= " << 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(current_team().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_,&map_,&get_info().state,&get_info().tod_manager_,&teams_,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_, teams_, get_info().state, get_info().tod_manager_, *this, dstsrc, srcdst, enemy_dstsrc, current_team().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(current_team().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;
}
void default_ai_context_impl::invalidate_attack_depth_cache(){
attack_depth_ = 0;
}
void default_ai_context_impl::invalidate_defensive_position_cache()
{
defensive_position_cache_.clear();
@ -331,6 +675,37 @@ double default_ai_context_impl::power_projection(const map_location& loc, const
return res / 100000.;
}
int default_ai_context_impl::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;
}
const map_location& default_ai_context_impl::suitable_keep(const map_location& leader_location, const paths& leader_paths){
if (get_info().map.is_keep(leader_location)) {
return leader_location; //if leader already on keep, then return leader_location

View file

@ -34,6 +34,90 @@
//============================================================================
namespace ai {
struct attack_analysis : public game_logic::formula_callable
{
attack_analysis() :
game_logic::formula_callable(),
target(),
movements(),
target_value(0.0),
avg_losses(0.0),
chance_to_kill(0.0),
avg_damage_inflicted(0.0),
target_starting_damage(0),
avg_damage_taken(0.0),
resources_used(0.0),
terrain_quality(0.0),
alternative_terrain_quality(0.0),
vulnerability(0.0),
support(0.0),
leader_threat(false),
uses_leader(false),
is_surrounded(false)
{
}
void analyze(const gamemap& map, unit_map& units,
const std::vector<team>& teams,
const gamestatus& status, const tod_manager& tod_mng,
class default_ai_context& ai_obj,
const move_map& dstsrc, const move_map& srcdst,
const move_map& enemy_dstsrc, double aggression);
double rating(double aggression, class default_ai_context& ai_obj) const;
variant get_value(const std::string& key) const;
void get_inputs(std::vector<game_logic::formula_input>* inputs) const;
map_location target;
std::vector<std::pair<map_location,map_location> > movements;
/** The value of the unit being targeted. */
double target_value;
/** The value on average, of units lost in the combat. */
double avg_losses;
/** Estimated % chance to kill the unit. */
double chance_to_kill;
/** The average hitpoints damage inflicted. */
double avg_damage_inflicted;
int target_starting_damage;
/** The average hitpoints damage taken. */
double avg_damage_taken;
/** The sum of the values of units used in the attack. */
double resources_used;
/** The weighted average of the % chance to hit each attacking unit. */
double terrain_quality;
/**
* The weighted average of the % defense of the best possible terrain
* that the attacking units could reach this turn, without attacking
* (good for comparison to see just how good/bad 'terrain_quality' is).
*/
double alternative_terrain_quality;
/**
* The vulnerability is the power projection of enemy units onto the hex
* we're standing on. support is the power projection of friendly units.
*/
double vulnerability, support;
/** Is true if the unit is a threat to our leader. */
bool leader_threat;
/** Is true if this attack sequence makes use of the leader. */
bool uses_leader;
/** Is true if the units involved in this attack sequence are surrounded. */
bool is_surrounded;
};
struct defensive_position {
defensive_position() :
loc(),
@ -47,13 +131,17 @@ struct defensive_position {
double vulnerability, support;
};
class default_ai_context;
class default_ai_context : public virtual readwrite_context{
public:
virtual void add_recent_attack(map_location loc) = 0;
/** 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;
/** Return true if there has been another attack this turn 'close' to this one. */
virtual bool attack_close(const map_location& loc) const = 0;
@ -74,9 +162,22 @@ public:
virtual std::map<map_location,defensive_position>& defensive_position_cache() = 0;
/** 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;
virtual void invalidate_attack_depth_cache() = 0;
virtual void invalidate_defensive_position_cache() = 0;
@ -109,6 +210,8 @@ public:
virtual double power_projection(const map_location& loc, const move_map& dstsrc) const = 0;
virtual int rate_terrain(const unit& u, const map_location& loc) const = 0;
/** get most suitable keep for leader - nearest free that can be reached in 1 turn, if none - return nearest occupied that can be reached in 1 turn, if none - return nearest keep, if none - return null_location */
virtual const map_location& suitable_keep( const map_location& leader_location, const paths& leader_paths ) = 0;
@ -130,6 +233,13 @@ 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);
}
bool attack_close(const map_location& loc) const
{
@ -159,6 +269,18 @@ public:
}
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();
@ -168,6 +290,12 @@ public:
void init_default_ai_context_proxy(default_ai_context &target);
virtual void invalidate_attack_depth_cache()
{
target_->invalidate_attack_depth_cache();
}
void invalidate_defensive_position_cache()
{
return target_->invalidate_defensive_position_cache();
@ -210,13 +338,18 @@ public:
}
virtual int rate_terrain(const unit& u, const map_location& loc) const
{
return target_->rate_terrain(u,loc);
}
virtual const map_location& suitable_keep( const map_location& leader_location, const paths& leader_paths )
{
return target_->suitable_keep(leader_location,leader_paths);
}
/** Weapon choice cache, to speed simulations. */
virtual std::map<std::pair<map_location,const unit_type *>,
std::pair<battle_context::unit_stats,battle_context::unit_stats> >& unit_stats_cache()
{
@ -235,14 +368,44 @@ public:
virtual void add_recent_attack(map_location loc);
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);
bool attack_close(const map_location& loc) const;
int attack_depth();
defensive_position const& best_defensive_position(const map_location& unit,
const move_map& dstsrc, const move_map& srcdst, const move_map& enemy_dstsrc);
default_ai_context_impl(readwrite_context &context)
: recursion_counter_(context.get_recursion_count()),unit_stats_cache_(),
defensive_position_cache_(),attacks_(),keeps_(),attack_depth_()
{
init_readwrite_context_proxy(context);
}
virtual ~default_ai_context_impl();
virtual std::map<map_location,defensive_position>& defensive_position_cache();
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();
@ -251,20 +414,8 @@ public:
return recursion_counter_.get_count();
}
virtual std::map<std::pair<map_location,const unit_type *>,
std::pair<battle_context::unit_stats,battle_context::unit_stats> >& unit_stats_cache();
default_ai_context_impl(readwrite_context &context)
: recursion_counter_(context.get_recursion_count()),unit_stats_cache_(),
defensive_position_cache_(),attacks_(),keeps_()
{
init_readwrite_context_proxy(context);
}
virtual std::map<map_location,defensive_position>& defensive_position_cache();
virtual void invalidate_attack_depth_cache();
virtual void invalidate_defensive_position_cache();
@ -288,9 +439,15 @@ public:
virtual double power_projection(const map_location& loc, const move_map& dstsrc) const;
virtual int rate_terrain(const unit& u, const map_location& loc) const;
virtual const map_location& suitable_keep( const map_location& leader_location, const paths& leader_paths );
virtual std::map<std::pair<map_location,const unit_type *>,
std::pair<battle_context::unit_stats,battle_context::unit_stats> >& unit_stats_cache();
private:
recursion_counter recursion_counter_;
@ -303,6 +460,8 @@ private:
std::set<map_location> keeps_;
int attack_depth_;
};
} //end of namespace ai

View file

@ -122,6 +122,9 @@ namespace dfool {
virtual int get_recursion_count() const{
return recursion_counter_.get_count();
}
virtual void new_turn()
{
}
private:
recursion_counter recursion_counter_;
// std::map<std::string,target> target_map_;

View file

@ -165,7 +165,7 @@ private:
game_logic::ai_function_symbol_table function_table;
game_logic::candidate_action_manager candidate_action_manager_;
friend class ai_default;
friend class ai::ai_default;
};
#ifdef _MSC_VER

View file

@ -47,10 +47,8 @@ public:
/**
* Function called when a a new turn is played
* Derived AIs should call this function each turn (expect first)
*/
virtual void new_turn() {
}
virtual void new_turn() = 0;
/**
* Function called after the new ai is created

View file

@ -692,6 +692,7 @@ void manager::play_turn( int side, events::observer* /*event_observer*/ ){
num_interact_ = 0;
const int turn_start_time = SDL_GetTicks();
interface& ai_obj = get_active_ai_for_side(side);
ai_obj.new_turn();
ai_obj.play_turn();
const int turn_end_time= SDL_GetTicks();
DBG_AI_MANAGER << "side " << side << ": number of user interactions: "<<num_interact_<<std::endl;

View file

@ -36,6 +36,7 @@ namespace ai {
static register_ai_factory<ai_default> ai_factory_default("");
static register_ai_factory<ai_default> ai_default_ai_factory("default_ai");
static register_ai_factory<ai2> ai2_ai_factory("ai2");
static register_ai_factory<idle_ai> ai_idle_ai_factory("idle_ai");
static register_ai_factory<dfool::dfool_ai> ai_dfool_ai_factory("dfool_ai");
static register_ai_factory<formula_ai> ai_formula_ai_factory("formula_ai");
static register_ai_factory<composite_ai::ai_composite> ai_composite_ai_factory("composite_ai");

View file

@ -83,7 +83,7 @@ bool recruitment_phase::execute()
//==============================================================
combat_phase::combat_phase( rca_context &context, const config &cfg )
: candidate_action(context,"testing_ai_default::combat_phase",cfg["type"])
: candidate_action(context,"testing_ai_default::combat_phase",cfg["type"]),best_analysis_(),choice_rating_(-1000.0)
{
}
@ -93,13 +93,86 @@ combat_phase::~combat_phase()
double combat_phase::evaluate()
{
ERR_AI_TESTING_AI_DEFAULT << get_name() << ": evaluate - not yet implemented!" << std::endl;
return BAD_SCORE;
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());
int time_taken = SDL_GetTicks() - ticks;
LOG_AI_TESTING_AI_DEFAULT << "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)
num_sims = 20;
if(num_sims > 40)
num_sims = 40;
LOG_AI_TESTING_AI_DEFAULT << "simulations: " << num_sims << "\n";
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();
it != analysis.end(); ++it) {
if(skip_num > 0 && ((it - analysis.begin())%skip_num) && it->movements.size() > 1)
continue;
const double rating = it->rating(current_team().aggression(),*this);
LOG_AI_TESTING_AI_DEFAULT << "attack option rated at " << rating << " ("
<< current_team().aggression() << ")\n";
if(rating > choice_rating_) {
choice_it = it;
choice_rating_ = rating;
}
}
time_taken = SDL_GetTicks() - ticks;
LOG_AI_TESTING_AI_DEFAULT << "analysis took " << time_taken << " ticks\n";
// suokko tested the rating against current_team().caution()
// Bad mistake -- the AI became extremely reluctant to attack anything.
// Documenting this in case someone has this bright idea again...*don't*...
if(choice_rating_ > 0.0) {
best_analysis_ = *choice_it;
return 40;//@todo: externalize
} else {
return BAD_SCORE;
}
}
bool combat_phase::execute()
{
ERR_AI_TESTING_AI_DEFAULT << get_name() << ": execute - not yet implemented!" << std::endl;
assert(choice_rating_ > 0.0);
map_location from = best_analysis_.movements[0].first;
map_location to = best_analysis_.movements[0].second;
map_location target_loc = best_analysis_.target;
if (from!=to) {
move_result_ptr move_res = execute_move_action(from,to,false);
if (!move_res->is_ok()) {
if (!move_res->is_gamestate_changed()) {
return false;
}
return true;
}
}
attack_result_ptr attack_res = execute_attack_action(to, target_loc, -1);
if (!attack_res->is_ok()) {
if (!attack_res->is_gamestate_changed()) {
return false;
}
return true;
}
return true;
}

View file

@ -22,6 +22,7 @@
#include "../../global.hpp"
#include "../default/ai.hpp"
#include "../composite/rca.hpp"
#include "../composite/engine_default.hpp"
@ -80,6 +81,9 @@ public:
virtual double evaluate();
virtual bool execute();
private:
attack_analysis best_analysis_;
double choice_rating_;
};