wesnoth/src/ai/manager.cpp
Alexander van Gessel 15459fca86 Rename utils::paranthetical_split()...
...to utils::parenthetical_split(). Patch by Espreon
2010-05-24 02:45:45 +01:00

788 lines
24 KiB
C++

/* $Id$ */
/*
Copyright (C) 2009 - 2010 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.
*/
/**
* Managing the AI lifecycle and interface for the rest of Wesnoth
* @file ai/manager.cpp
*/
#include "composite/ai.hpp"
#include "configuration.hpp"
#include "contexts.hpp"
#include "default/ai.hpp"
#include "manager.hpp"
#include "formula/ai.hpp"
#include "registry.hpp"
#include "../game_events.hpp"
#include "../game_preferences.hpp"
#include "../foreach.hpp"
#include "../log.hpp"
#include "../replay.hpp"
#include "../serialization/string_utils.hpp"
#include "../statistics.hpp"
#include "composite/component.hpp"
#include <map>
#include <stack>
#include <vector>
namespace ai {
const std::string manager::AI_TYPE_COMPOSITE_AI = "composite_ai";
const std::string manager::AI_TYPE_SAMPLE_AI = "sample_ai";
const std::string manager::AI_TYPE_IDLE_AI = "idle_ai";
const std::string manager::AI_TYPE_FORMULA_AI = "formula_ai";
const std::string manager::AI_TYPE_DEFAULT = "default";
static lg::log_domain log_ai_manager("ai/manager");
#define DBG_AI_MANAGER LOG_STREAM(debug, log_ai_manager)
#define LOG_AI_MANAGER LOG_STREAM(info, log_ai_manager)
#define ERR_AI_MANAGER LOG_STREAM(err, log_ai_manager)
static lg::log_domain log_ai_mod("ai/mod");
#define DBG_AI_MOD LOG_STREAM(debug, log_ai_mod)
#define LOG_AI_MOD LOG_STREAM(info, log_ai_mod)
#define WRN_AI_MOD LOG_STREAM(warn, log_ai_mod)
#define ERR_AI_MOD LOG_STREAM(err, log_ai_mod)
holder::holder( side_number side, const config &cfg )
: ai_(), side_context_(NULL), readonly_context_(NULL), readwrite_context_(NULL), default_ai_context_(NULL), side_(side), cfg_(cfg)
{
DBG_AI_MANAGER << describe_ai() << "Preparing new AI holder" << std::endl;
}
void holder::init( side_number side )
{
if (side_context_ == NULL) {
side_context_ = new side_context_impl(side,cfg_);
} else {
side_context_->set_side(side);
}
if (readonly_context_ == NULL){
readonly_context_ = new readonly_context_impl(*side_context_,cfg_);
readonly_context_->on_readonly_context_create();
}
if (readwrite_context_ == NULL){
readwrite_context_ = new readwrite_context_impl(*readonly_context_,cfg_);
}
if (default_ai_context_ == NULL){
default_ai_context_ = new default_ai_context_impl(*readwrite_context_,cfg_);
}
if (!this->ai_){
ai_ = boost::shared_ptr<ai_composite>(new ai_composite(*default_ai_context_,cfg_));
}
if (this->ai_) {
ai_->on_create();
foreach (config &mod_ai, cfg_.child_range("modify_ai")) {
if (!mod_ai.has_attribute("side")) {
mod_ai["side"] = side;
}
modify_ai(mod_ai);
}
cfg_.clear_children("modify_ai");
} else {
ERR_AI_MANAGER << describe_ai()<<"AI lazy initialization error!" << std::endl;
}
}
holder::~holder()
{
if (this->ai_) {
LOG_AI_MANAGER << describe_ai() << "Managed AI will be deleted" << std::endl;
}
delete this->default_ai_context_;
delete this->readwrite_context_;
delete this->readonly_context_;
delete this->side_context_;
}
interface& holder::get_ai_ref()
{
if (!this->ai_) {
this->init(this->side_);
}
assert(this->ai_);
return *this->ai_;
}
void holder::modify_ai_config_old( const config::const_child_itors &ai_parameters)
{
// only handle aspects
// transform ai_parameters to new-style config
config cfg;
configuration::upgrade_aspect_configs_from_1_07_02_to_1_07_03(this->side_,ai_parameters,cfg);
//at this point we have a single config which contains [aspect][facet] tags
DBG_AI_MANAGER << "after transforming [modify_side][ai] into new syntax, config contains:"<< std::endl << cfg << std::endl;
if (this->readonly_context_ == NULL) {
// if not initialized, append that config to the bottom of base cfg
// then, merge aspects with the same id
cfg_.merge_with(cfg);
cfg_.merge_children_by_attribute("aspect","id");
} else {
// else run 'add_facet' command on each [aspect][facet]
foreach (const config &cfg_a, cfg.child_range("aspect")) {
foreach (const config &cfg_f, cfg_a.child_range("facet")) {
readonly_context_->add_facet(cfg_a["id"],cfg_f);
}
}
}
}
void holder::modify_ai(const config &cfg)
{
if (!this->ai_) {
// if not initialized, initialize now.
get_ai_ref();
}
const std::string &act = cfg["action"];
LOG_AI_MOD << "side "<< side_ << " [modify_ai] "<<act<<" \""<<cfg["path"]<<"\""<<std::endl;
DBG_AI_MOD << std::endl << cfg << std::endl;
DBG_AI_MOD << "side "<< side_ << " before [modify_ai]"<<std::endl << to_config() << std::endl;
bool res = false;
if (act == "add") {
res = component_manager::add_component(&*this->ai_,cfg["path"],cfg);
} else if (act == "change") {
res = component_manager::change_component(&*this->ai_,cfg["path"],cfg);
} else if (act == "delete") {
res = component_manager::delete_component(&*this->ai_,cfg["path"]);
} else if (act == "try_delete") {
res = component_manager::delete_component(&*this->ai_,cfg["path"]);
if (!res) {
LOG_AI_MOD << "[modify_ai] "<<act<<" failed, ignoring because it's a try_delete"<< std::endl;
res = true;
}
} else {
ERR_AI_MOD << "modify_ai tag has invalid 'action' attribute " << act << std::endl;
}
DBG_AI_MOD << "side "<< side_ << " after [modify_ai]"<<act<<std::endl << to_config() << std::endl;
if (!res) {
LOG_AI_MOD << "[modify_ai] "<<act<<" failed"<< std::endl;
} else {
LOG_AI_MOD << "[modify_ai] "<<act<<" success"<< std::endl;
}
}
config holder::to_config() const
{
if (!this->ai_) {
return cfg_;
} else {
config cfg = ai_->to_config();
cfg["version"] = "10703";
if (this->side_context_!=NULL) {
cfg.merge_with(this->side_context_->to_side_context_config());
}
if (this->readonly_context_!=NULL) {
cfg.merge_with(this->readonly_context_->to_readonly_context_config());
}
if (this->readwrite_context_!=NULL) {
cfg.merge_with(this->readwrite_context_->to_readwrite_context_config());
}
if (this->default_ai_context_!=NULL) {
cfg.merge_with(this->default_ai_context_->to_default_ai_context_config());
}
return cfg;
}
}
const std::string holder::describe_ai()
{
std::string sidestr = lexical_cast<std::string>(this->side_);
if (this->ai_!=NULL) {
return this->ai_->describe_self()+std::string(" for side ")+sidestr+std::string(" : ");
} else {
return std::string("not initialized ai with id=[")+cfg_["id"]+std::string("] for side ")+sidestr+std::string(" : ");
}
}
const std::string holder::get_ai_overview()
{
if (!this->ai_) {
get_ai_ref();
}
std::stringstream s;
s << "aggression: " << this->ai_->get_aggression() << std::endl;
s << "attack_depth: " << this->ai_->get_attack_depth() << std::endl;
s << "caution: " << this->ai_->get_caution() << std::endl;
s << "grouping: " << this->ai_->get_grouping() << std::endl;
s << "leader_aggression: " << this->ai_->get_leader_aggression() << std::endl;
s << "leader_value: " << this->ai_->get_leader_value() << std::endl;
s << "number_of_possible_recruits_to_force_recruit: " << this->ai_->get_number_of_possible_recruits_to_force_recruit() << std::endl;
s << "passive_leader: " << this->ai_->get_passive_leader() << std::endl;
s << "passive_leader_shares_keep: " << this->ai_->get_passive_leader_shares_keep() << std::endl;
s << "recruitment_ignore_bad_combat: " << this->ai_->get_recruitment_ignore_bad_movement() << std::endl;
s << "recruitment_ignore_bad_movement: " << this->ai_->get_recruitment_ignore_bad_combat() << std::endl;
// s << "recruitment_pattern: ";
// for(std::vector<std::string>::const_iterator i = this->ai_->get_recruitment_pattern().begin(); i != this->ai_->get_recruitment_pattern().end(); ++i) {
// if(i != this->ai_->get_recruitment_pattern().begin())
// s << ",";
//
// s << *i;
// }
// s << std::endl;
s << "scout_village_targeting: " << this->ai_->get_scout_village_targeting() << std::endl;
s << "simple_targeting: " << this->ai_->get_simple_targeting() << std::endl;
s << "support_villages: " << this->ai_->get_support_villages() << std::endl;
s << "village_value: " << this->ai_->get_village_value() << std::endl;
s << "villages_per_scout: " << this->ai_->get_villages_per_scout() << std::endl;
return s.str();
}
const std::string holder::get_ai_structure()
{
if (!this->ai_) {
get_ai_ref();
}
return component_manager::print_component_tree(&*this->ai_,"");
}
const std::string holder::get_ai_identifier() const
{
return cfg_["id"];
}
// =======================================================================
// LIFECYCLE
// =======================================================================
manager::AI_map_of_stacks manager::ai_map_;
game_info *manager::ai_info_;
events::generic_event manager::user_interact_("ai_user_interact");
events::generic_event manager::sync_network_("ai_sync_network");
events::generic_event manager::gamestate_changed_("ai_gamestate_changed");
events::generic_event manager::turn_started_("ai_turn_started");
events::generic_event manager::recruit_list_changed_("ai_recruit_list_changed");
events::generic_event manager::map_changed_("ai_map_changed");
int manager::last_interact_ = 0;
int manager::num_interact_ = 0;
void manager::set_ai_info(const game_info& i)
{
if (ai_info_!=NULL){
clear_ai_info();
}
ai_info_ = new game_info(i);
registry::init();
}
void manager::clear_ai_info(){
if (ai_info_ != NULL){
delete ai_info_;
ai_info_ = NULL;
}
}
void manager::add_observer( events::observer* event_observer){
user_interact_.attach_handler(event_observer);
sync_network_.attach_handler(event_observer);
turn_started_.attach_handler(event_observer);
gamestate_changed_.attach_handler(event_observer);
}
void manager::remove_observer(events::observer* event_observer){
user_interact_.detach_handler(event_observer);
sync_network_.detach_handler(event_observer);
turn_started_.detach_handler(event_observer);
gamestate_changed_.detach_handler(event_observer);
}
void manager::add_gamestate_observer( events::observer* event_observer){
gamestate_changed_.attach_handler(event_observer);
turn_started_.attach_handler(event_observer);
map_changed_.attach_handler(event_observer);
}
void manager::remove_gamestate_observer(events::observer* event_observer){
gamestate_changed_.detach_handler(event_observer);
turn_started_.detach_handler(event_observer);
map_changed_.detach_handler(event_observer);
}
void manager::add_map_changed_observer( events::observer* event_observer )
{
map_changed_.attach_handler(event_observer);
}
void manager::add_recruit_list_changed_observer( events::observer* event_observer )
{
recruit_list_changed_.attach_handler(event_observer);
}
void manager::add_turn_started_observer( events::observer* event_observer )
{
turn_started_.attach_handler(event_observer);
}
void manager::remove_recruit_list_changed_observer( events::observer* event_observer )
{
recruit_list_changed_.detach_handler(event_observer);
}
void manager::remove_map_changed_observer( events::observer* event_observer )
{
map_changed_.detach_handler(event_observer);
}
void manager::remove_turn_started_observer( events::observer* event_observer )
{
turn_started_.detach_handler(event_observer);
}
void manager::raise_user_interact() {
const int interact_time = 30;
const int time_since_interact = SDL_GetTicks() - last_interact_;
if(time_since_interact < interact_time) {
return;
}
++num_interact_;
user_interact_.notify_observers();
last_interact_ = SDL_GetTicks();
}
void manager::raise_sync_network() {
sync_network_.notify_observers();
}
void manager::raise_gamestate_changed() {
gamestate_changed_.notify_observers();
}
void manager::raise_turn_started() {
turn_started_.notify_observers();
}
void manager::raise_recruit_list_changed() {
recruit_list_changed_.notify_observers();
}
void manager::raise_map_changed() {
map_changed_.notify_observers();
}
// =======================================================================
// EVALUATION
// =======================================================================
const std::string manager::evaluate_command( side_number side, const std::string& str )
{
//insert new command into history
history_.push_back(command_history_item(history_item_counter_++,str));
//prune history - erase 1/2 of it if it grows too large
if (history_.size()>MAX_HISTORY_SIZE){
history_.erase(history_.begin(),history_.begin()+MAX_HISTORY_SIZE/2);
LOG_AI_MANAGER << "AI MANAGER: pruned history" << std::endl;
}
if (!should_intercept(str)){
interface& ai = get_active_ai_for_side(side);
raise_gamestate_changed();
return ai.evaluate(str);
}
return internal_evaluate_command(side,str);
}
bool manager::should_intercept( const std::string& str )
{
if (str.length()<1) {
return false;
}
if (str.at(0)=='!'){
return true;
}
if (str.at(0)=='?'){
return true;
}
return false;
}
std::deque< command_history_item > manager::history_;
long manager::history_item_counter_ = 1;
//this is stub code to allow testing of basic 'history', 'repeat-last-command', 'add/remove/replace ai' capabilities.
//yes, it doesn't look nice. but it is usable.
//to be refactored at earliest opportunity
//@todo 1.9 extract to separate class which will use fai or lua parser
const std::string manager::internal_evaluate_command( side_number side, const std::string& str ){
const int MAX_HISTORY_VISIBLE = 30;
//repeat last command
if (str=="!") {
//this command should not be recorded in history
if (!history_.empty()){
history_.pop_back();
history_item_counter_--;
}
if (history_.empty()){
return "AI MANAGER: empty history";
}
return evaluate_command(side, history_.back().get_command());//no infinite loop since '!' commands are not present in history
};
//show last command
if (str=="?") {
//this command should not be recorded in history
if (!history_.empty()){
history_.pop_back();
history_item_counter_--;
}
if (history_.empty()){
return "AI MANAGER: History is empty";
}
int n = std::min<int>( MAX_HISTORY_VISIBLE, history_.size() );
std::stringstream strstream;
strstream << "AI MANAGER: History - last "<< n <<" commands:\n";
std::deque< command_history_item >::reverse_iterator j = history_.rbegin();
for (int cmd_id=n; cmd_id>0; --cmd_id){
strstream << j->get_number() << " :" << j->get_command() << '\n';
j++;//this is *reverse* iterator
}
return strstream.str();
};
std::vector< std::string > cmd = utils::parenthetical_split(str, ' ',"'","'");
if (cmd.size()==3){
//!add_ai side file
if (cmd.at(0)=="!add_ai"){
side_number side = lexical_cast<side_number>(cmd.at(1));
std::string file = cmd.at(2);
if (add_ai_for_side_from_file(side,file,false)){
return std::string("AI MANAGER: added [")+manager::get_active_ai_identifier_for_side(side)+std::string("] AI for side ")+lexical_cast<std::string>(side)+std::string(" from file ")+file;
} else {
return std::string("AI MANAGER: failed attempt to add AI for side ")+lexical_cast<std::string>(side)+std::string(" from file ")+file;
}
}
//!replace_ai side file
if (cmd.at(0)=="!replace_ai"){
side_number side = lexical_cast<side_number>(cmd.at(1));
std::string file = cmd.at(2);
if (add_ai_for_side_from_file(side,file,true)){
return std::string("AI MANAGER: added [")+manager::get_active_ai_identifier_for_side(side)+std::string("] AI for side ")+lexical_cast<std::string>(side)+std::string(" from file ")+file;
} else {
return std::string("AI MANAGER: failed attempt to add AI for side ")+lexical_cast<std::string>(side)+std::string(" from file ")+file;
}
}
} else if (cmd.size()==2){
//!remove_ai side
if (cmd.at(0)=="!remove_ai"){
side_number side = lexical_cast<side_number>(cmd.at(1));
remove_ai_for_side(side);
return std::string("AI MANAGER: made an attempt to remove AI for side ")+lexical_cast<std::string>(side);
}
if (cmd.at(0)=="!"){
//this command should not be recorded in history
if (!history_.empty()){
history_.pop_back();
history_item_counter_--;
}
int command = lexical_cast<int>(cmd.at(1));
std::deque< command_history_item >::reverse_iterator j = history_.rbegin();
//yes, the iterator could be precisely positioned (since command numbers go 1,2,3,4,..). will do it later.
while ( (j!=history_.rend()) && (j->get_number()!=command) ){
j++;// this is *reverse* iterator
}
if (j!=history_.rend()){
return evaluate_command(side,j->get_command());//no infinite loop since '!' commands are not present in history
}
return "AI MANAGER: no command with requested number found";
}
} else if (cmd.size()==1){
if (cmd.at(0)=="!help") {
return
"known commands:\n"
"! - repeat last command (? and ! do not count)\n"
"! NUMBER - repeat numbered command\n"
"? - show a history list\n"
"!add_ai TEAM FILE - add a AI to side (0 - command AI, N - AI for side #N) from file\n"
"!remove_ai TEAM - remove AI from side (0 - command AI, N - AI for side #N)\n"
"!replace_ai TEAM FILE - replace AI of side (0 - command AI, N - AI for side #N) from file\n"
"!help - show this help message";
}
}
return "AI MANAGER: nothing to do";
}
// =======================================================================
// ADD, CREATE AIs, OR LIST AI TYPES
// =======================================================================
//@todo 1.9 add error reporting
bool manager::add_ai_for_side_from_file( side_number side, const std::string& file, bool replace )
{
config cfg;
if (!configuration::get_side_config_from_file(file,cfg)){
ERR_AI_MANAGER << " unable to read [SIDE] config for side "<< side << "from file [" << file <<"]"<< std::endl;
return false;
}
return add_ai_for_side_from_config(side,cfg,replace);
}
bool manager::add_ai_for_side_from_config( side_number side, const config& cfg, bool replace ){
config parsed_cfg;
configuration::parse_side_config(side, cfg, parsed_cfg);
if (replace) {
remove_ai_for_side(side);
}
holder new_holder(side,parsed_cfg);
std::stack<holder>& ai_stack_for_specific_side = get_or_create_ai_stack_for_side(side);
ai_stack_for_specific_side.push(new_holder);
return true;
}
//@todo 1.9 add error reporting
bool manager::add_ai_for_side( side_number side, const std::string& ai_algorithm_type, bool replace )
{
if (replace) {
remove_ai_for_side (side);
}
config cfg;
cfg["ai_algorithm"] = ai_algorithm_type;
holder new_holder(side,cfg);
std::stack<holder>& ai_stack_for_specific_side = get_or_create_ai_stack_for_side(side);
ai_stack_for_specific_side.push(new_holder);
return true;
}
ai_ptr manager::create_transient_ai(const std::string &ai_algorithm_type, const config &cfg, ai_context *ai_context )
{
assert(ai_context!=NULL);
//to add your own ai, register it in registry,cpp
ai_factory::factory_map::iterator aii = ai_factory::get_list().find(ai_algorithm_type);
if (aii == ai_factory::get_list().end()){
aii = ai_factory::get_list().find("");
if (aii == ai_factory::get_list().end()){
throw game::game_error("no default ai set!");
}
}
LOG_AI_MANAGER << "Creating new AI of type [" << ai_algorithm_type << "]"<< std::endl;
ai_ptr new_ai = aii->second->get_new_instance(*ai_context,cfg);
return new_ai;
}
// =======================================================================
// REMOVE
// =======================================================================
void manager::remove_ai_for_side( side_number side )
{
std::stack<holder>& ai_stack_for_specific_side = get_or_create_ai_stack_for_side(side);
if (!ai_stack_for_specific_side.empty()){
ai_stack_for_specific_side.pop();
}
}
void manager::remove_all_ais_for_side( side_number side )
{
std::stack<holder>& ai_stack_for_specific_side = get_or_create_ai_stack_for_side(side);
//clear the stack. std::stack doesn't have a '.clear()' method to do it
while (!ai_stack_for_specific_side.empty()){
ai_stack_for_specific_side.pop();
}
}
void manager::clear_ais()
{
ai_map_.clear();
}
// =======================================================================
// Work with active AI parameters
// =======================================================================
void manager::modify_active_ai_config_old_for_side ( side_number side, const config::const_child_itors &ai_parameters )
{
get_active_ai_holder_for_side(side).modify_ai_config_old(ai_parameters);
}
void manager::modify_active_ai_for_side ( side_number side, const config &cfg )
{
if (ai_info_==NULL) {
//replay ?
return;
}
get_active_ai_holder_for_side(side).modify_ai(cfg);
}
std::string manager::get_active_ai_overview_for_side( side_number side)
{
return get_active_ai_holder_for_side(side).get_ai_overview();
}
std::string manager::get_active_ai_structure_for_side( side_number side)
{
return get_active_ai_holder_for_side(side).get_ai_structure();
}
std::string manager::get_active_ai_identifier_for_side( side_number side )
{
return get_active_ai_holder_for_side(side).get_ai_identifier();
}
config manager::to_config( side_number side )
{
return get_active_ai_holder_for_side(side).to_config();
}
game_info& manager::get_active_ai_info_for_side( side_number /*side*/ )
{
return *ai_info_;
}
game_info& manager::get_ai_info()
{
return *ai_info_;
}
// =======================================================================
// PROXY
// =======================================================================
void manager::play_turn( side_number side ){
last_interact_ = 0;
num_interact_ = 0;
const int turn_start_time = SDL_GetTicks();
/*hack. @todo 1.9 rework via extended event system*/
get_ai_info().recent_attacks.clear();
interface& ai_obj = get_active_ai_for_side(side);
game_events::fire("ai turn");
raise_turn_started();
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;
DBG_AI_MANAGER << "side " << side << ": total turn time: "<<turn_end_time - turn_start_time << " ms "<< std::endl;
}
// =======================================================================
// PRIVATE
// =======================================================================
// =======================================================================
// AI STACKS
// =======================================================================
std::stack<holder>& manager::get_or_create_ai_stack_for_side( side_number side )
{
AI_map_of_stacks::iterator iter = ai_map_.find(side);
if (iter!=ai_map_.end()){
return iter->second;
}
return ai_map_.insert(std::make_pair(side, std::stack<holder>())).first->second;
}
// =======================================================================
// AI HOLDERS
// =======================================================================
holder& manager::get_active_ai_holder_for_side( side_number side )
{
std::stack<holder>& ai_stack_for_specific_side = get_or_create_ai_stack_for_side(side);
if (!ai_stack_for_specific_side.empty()){
return ai_stack_for_specific_side.top();
} else {
config cfg = configuration::get_default_ai_parameters();
holder new_holder(side, cfg);
ai_stack_for_specific_side.push(new_holder);
return ai_stack_for_specific_side.top();
}
}
// =======================================================================
// AI POINTERS
// =======================================================================
interface& manager::get_active_ai_for_side( side_number side )
{
return get_active_ai_holder_for_side(side).get_ai_ref();
}
// =======================================================================
// MISC
// =======================================================================
} //end of namespace ai