AI improvements to make it group better

This commit is contained in:
Dave White 2004-05-21 17:26:13 +00:00
parent 91c2736d50
commit 4e5608d631
15 changed files with 587 additions and 113 deletions

View file

@ -1,3 +1,29 @@
[multiplayer]
name=ai-test
map=ai-test
turns=90
id=multiplayer-ai-test
{DAWN}
{MORNING}
{AFTERNOON}
{DUSK}
{FIRST_WATCH}
{SECOND_WATCH}
[side]
side=1
enemy=2
canrecruit=1
controller=human
[/side]
[side]
side=2
enemy=1
canrecruit=1
controller=human
[/side]
[/multiplayer]
[multiplayer]
name="Across The River"
map_data="ffffffffffffffgggggggggggggggggggggggg

View file

@ -313,7 +313,7 @@ language_button="Language"
configure_sides="Configure Sides:"
choose_side="Choose Team Settings:"
client_choose_side="Choose your side:"
no_sides_available="There are no available sides, and observers are not allowed in this game."
no_sides_available="There are no available sides in this game."
start_game="Start Game!"
game_cancelled="The game has been cancelled"
side_unavailable="The side you have chosen is no longer available"

View file

@ -25,6 +25,7 @@
#include "show_dialog.hpp"
#include "statistics.hpp"
#include "unit_display.hpp"
#include "util.hpp"
#include <iostream>
@ -143,6 +144,20 @@ void ai_interface::user_interact()
last_interact_ = SDL_GetTicks();
}
void ai_interface::diagnostic(const std::string& msg)
{
if(game_config::debug) {
info_.disp.set_diagnostic(msg);
}
}
void ai_interface::log_message(const std::string& msg)
{
if(game_config::debug) {
info_.disp.add_chat_message("ai",info_.team_num,msg,display::MESSAGE_PUBLIC);
}
}
team& ai_interface::current_team()
{
return info_.teams[info_.team_num-1];
@ -455,6 +470,8 @@ void ai::do_move()
{
log_scope("doing ai move");
game_config::debug = true;
invalidate_defensive_position_cache();
user_interact();
@ -480,6 +497,8 @@ void ai::do_move()
std::vector<attack_analysis> analysis;
AI_DIAGNOSTIC("combat phase");
if(consider_combat_) {
std::cerr << "combat...\n";
consider_combat_ = do_combat(possible_moves,srcdst,dstsrc,enemy_srcdst,enemy_dstsrc);
@ -489,6 +508,8 @@ void ai::do_move()
}
}
AI_DIAGNOSTIC("get villages phase");
std::cerr << "villages...\n";
const bool got_village = get_villages(possible_moves,srcdst,dstsrc,enemy_srcdst,enemy_dstsrc,leader);
if(got_village) {
@ -496,6 +517,8 @@ void ai::do_move()
return;
}
AI_DIAGNOSTIC("healing phase");
std::cerr << "healing...\n";
const bool healed_unit = get_healing(possible_moves,srcdst,dstsrc,enemy_srcdst,enemy_dstsrc);
if(healed_unit) {
@ -503,6 +526,8 @@ void ai::do_move()
return;
}
AI_DIAGNOSTIC("retreat phase");
std::cerr << "retreating...\n";
const bool retreated_unit = retreat_units(possible_moves,srcdst,dstsrc,enemy_srcdst,enemy_dstsrc,leader);
if(retreated_unit) {
@ -514,6 +539,8 @@ void ai::do_move()
remove_unit_from_moves(leader->first,srcdst,dstsrc);
}
AI_DIAGNOSTIC("move/targetting phase");
const bool met_invisible_unit = move_to_targets(possible_moves,srcdst,dstsrc,enemy_srcdst,enemy_dstsrc,leader);
if(met_invisible_unit) {
std::cerr << "met_invisible_unit\n";
@ -523,6 +550,8 @@ void ai::do_move()
std::cerr << "done move to targets\n";
AI_DIAGNOSTIC("leader/recruitment phase");
//recruitment phase and leader movement phase
if(leader != units_.end()) {
move_leader_to_keep(enemy_dstsrc);
@ -533,6 +562,8 @@ void ai::do_move()
}
}
AI_DIAGNOSTIC("");
recorder.end_turn();
}
@ -565,7 +596,7 @@ bool ai::do_combat(std::map<gamemap::location,paths>& possible_moves, const move
if(skip_num > 0 && ((it - analysis.begin())%skip_num) && it->movements.size() > 1)
continue;
const double rating = it->rating(current_team().aggression());
const double rating = it->rating(current_team().aggression(),*this);
std::cerr << "attack option rated at " << rating << " (" << current_team().aggression() << ")\n";
if(rating > choice_rating) {
choice_it = it;
@ -599,7 +630,7 @@ bool ai::do_combat(std::map<gamemap::location,paths>& possible_moves, const move
//if this is the only unit in the planned attack, and the target
//is still alive, then also summon reinforcements
if(choice_it->movements.size() == 1 && units_.count(target_loc)) {
additional_targets_.push_back(target(target_loc,3.0));
additional_targets_.push_back(target(target_loc,3.0,target::BATTLE_AID));
}
return true;
@ -615,7 +646,7 @@ bool ai::do_combat(std::map<gamemap::location,paths>& possible_moves, const move
if(already_done.count(loc) > 0)
continue;
additional_targets_.push_back(target(loc,3.0));
additional_targets_.push_back(target(loc,3.0,target::BATTLE_AID));
already_done.insert(loc);
}
@ -712,6 +743,8 @@ bool ai::get_villages(std::map<gamemap::location,paths>& possible_moves, const m
start_pos = nearest_keep(leader->first);
}
std::map<location,double> vulnerability;
//we want to build up a list of possible moves we can make that will capture villages.
//limit the moves to 'max_village_moves' to make sure things don't get out of hand.
const size_t max_village_moves = 50;
@ -750,6 +783,25 @@ bool ai::get_villages(std::map<gamemap::location,paths>& possible_moves, const m
continue;
}
double threat = 0.0;
const std::map<location,double>::const_iterator vuln = vulnerability.find(j->first);
if(vuln != vulnerability.end()) {
threat = vuln->second;
} else {
threat = power_projection(j->first,enemy_srcdst,enemy_dstsrc);
vulnerability.insert(std::pair<location,double>(j->first,threat));
}
const unit_map::const_iterator u = units_.find(j->second);
if(u == units_.end()) {
continue;
}
const unit& un = u->second;
if(un.hitpoints() < (threat*2*un.defense_modifier(map_,map_.get_terrain(j->first)))/100) {
continue;
}
village_moves.push_back(*j);
}
@ -950,7 +1002,7 @@ bool ai::move_to_targets(std::map<gamemap::location,paths>& possible_moves, move
}
std::cerr << "choosing move...\n";
std::pair<location,location> move = choose_move(targets,dstsrc,enemy_srcdst,enemy_dstsrc);
std::pair<location,location> move = choose_move(targets,srcdst,dstsrc,enemy_srcdst,enemy_dstsrc);
for(std::vector<target>::const_iterator ittg = targets.begin(); ittg != targets.end(); ++ittg) {
assert(map_.on_board(ittg->loc));
}
@ -959,6 +1011,10 @@ bool ai::move_to_targets(std::map<gamemap::location,paths>& possible_moves, move
break;
}
if(move.second.valid() == false) {
return true;
}
std::cerr << "move: " << move.first.x << ", " << move.first.y << " - " << move.second.x << ", " << move.second.y << "\n";
//search to see if there are any enemy units next
@ -1223,9 +1279,6 @@ void ai::do_recruitment()
analyze_potential_recruit_movements();
analyze_potential_recruit_combat();
//currently just spend all the gold we can!
const int min_gold = 0;
size_t neutral_villages = 0;
//we recruit the initial allocation of scouts based on how many neutral villages
@ -1239,12 +1292,10 @@ void ai::do_recruitment()
bool closest = true;
for(std::vector<team>::const_iterator i = teams_.begin(); i != teams_.end(); ++i) {
const int index = i - teams_.begin() + 1;
if(team_num_ != index) {
const gamemap::location& loc = nearest_keep(leader->first);
if(distance_between(loc,*v) < distance) {
closest = false;
break;
}
const gamemap::location& loc = map_.starting_position(index);
if(loc != start_pos && distance_between(loc,*v) < distance) {
closest = false;
break;
}
}
@ -1262,7 +1313,18 @@ void ai::do_recruitment()
//get scouts depending on how many neutral villages there are
int scouts_wanted = villages_per_scout > 0 ? neutral_villages/villages_per_scout : 0;
std::cerr << "scouts_wanted: " << neutral_villages << "/" << villages_per_scout << " = " << scouts_wanted << "\n";
std::map<std::string,int> unit_types;
for(unit_map::const_iterator u = units_.begin(); u != units_.end(); ++u) {
if(u->second.side() == team_num_) {
++unit_types[u->second.type().usage()];
}
}
std::cerr << "we have " << unit_types["scout"] << " scouts already and we want " << scouts_wanted << " in total\n";
while(unit_types["scout"] < scouts_wanted) {
if(recruit_usage("scout") == false)
break;
@ -1342,39 +1404,39 @@ void ai::move_leader_after_recruit(const move_map& enemy_dstsrc)
std::map<gamemap::location,paths> possible_moves;
possible_moves.insert(std::pair<gamemap::location,paths>(leader->first,leader_paths));
//see if the leader can capture a village safely
//if the keep is accessible by an enemy unit, we don't want to leave it
if(is_accessible(leader->first,enemy_dstsrc)) {
return;
}
if(current_team().gold() < 20 && is_accessible(leader->first,enemy_dstsrc) == false) {
//see if we want to ward any enemy units off from getting our villages
for(move_map::const_iterator i = enemy_dstsrc.begin(); i != enemy_dstsrc.end(); ++i) {
//search through villages finding one to capture
const std::vector<gamemap::location>& villages = map_.villages();
for(std::vector<gamemap::location>::const_iterator v = villages.begin();
v != villages.end(); ++v) {
const paths::routes_map::const_iterator itor = leader_paths.routes.find(*v);
if(itor == leader_paths.routes.end() || units_.count(*v) != 0) {
continue;
}
//if this is a village of ours, that an enemy can capture on their turn, and
//which we might be able to reach in two turns.
if(map_.is_village(i->first) && current_team().owns_village(i->first) &&
distance_between(i->first,leader->first) <= leader->second.total_movement()*2) {
int current_distance = distance_between(i->first,leader->first);
location current_loc;
const int owner = village_owner(*v,teams_);
if(owner == -1 || current_team().is_enemy(owner+1) || leader->second.hitpoints() < leader->second.max_hitpoints()) {
for(paths::routes_map::const_iterator j = leader_paths.routes.begin(); j != leader_paths.routes.end(); ++j) {
const int distance = distance_between(i->first,j->first);
if(distance < current_distance && is_accessible(j->first,enemy_dstsrc) == false) {
current_distance = distance;
current_loc = j->first;
}
}
//check that no enemies can reach the village
gamemap::location adj[6];
get_adjacent_tiles(*v,adj);
size_t n;
for(n = 0; n != 6; ++n) {
if(enemy_dstsrc.count(adj[n]))
break;
//if this location is in range of the village, then we consider it
if(current_loc.valid()) {
AI_LOG("considering movement to " + str_cast(current_loc.x+1) + "," + str_cast(current_loc.y+1));
unit_map temp_units;
temp_units.insert(std::pair<location,unit>(current_loc,leader->second));
const paths p(map_,state_,gameinfo_,temp_units,current_loc,teams_,false,false);
if(p.routes.count(i->first)) {
move_unit(leader->first,current_loc,possible_moves);
return;
}
}
}
if(n != 6)
continue;
move_unit(leader->first,*v,possible_moves);
return;
}
}

View file

@ -22,6 +22,9 @@
#include <map>
#define AI_DIAGNOSTIC(MSG) if(game_config::debug) { diagnostic(MSG); std::cerr << "AI_DIAGNOSTIC: " << MSG << "\n"; }
#define AI_LOG(MSG) if(game_config::debug) { log_message(MSG); std::cerr << "AI_LOG: " << MSG << "\n";}
class ai_interface {
public:
@ -89,6 +92,12 @@ public:
///function to update network players as to what the AI has done so far this turn
void sync_network();
///function to show a diagnostic message on the screen
void diagnostic(const std::string& msg);
///function to display a debug message as a chat message is displayed
void log_message(const std::string& msg);
protected:
///this function should be called to attack an enemy.
///'attacking_unit': the location of the attacking unit
@ -299,10 +308,14 @@ public:
battle_stats& cur_stats, gamemap::TERRAIN terrain);
struct target {
target(const location& pos, double val) : loc(pos), value(val)
enum TYPE { VILLAGE, LEADER, EXPLICIT, THREAT, BATTLE_AID, MASS };
target(const location& pos, double val, TYPE target_type=VILLAGE) : loc(pos), value(val), type(target_type)
{}
location loc;
double value;
TYPE type;
};
struct defensive_position {
@ -360,7 +373,7 @@ protected:
class ai& ai_obj, const move_map& dstsrc, const move_map& srcdst,
const move_map& enemy_dstsrc, const move_map& enemy_srcdst);
double rating(double aggression) const;
double rating(double aggression, class ai& ai_obj) const;
gamemap::location target;
std::vector<std::pair<gamemap::location,gamemap::location> > movements;
@ -412,6 +425,7 @@ 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,
@ -420,6 +434,13 @@ protected:
);
//function which finds how much 'power' a side can attack a certain location with. This is basically
//the maximum hp of damage that can be inflicted upon a unit on loc by full-health units, multiplied by
//the defense these units will have. (if 'use_terrain' is false, then it will be multiplied by 0.5)
//
//Example: 'loc' can be reached by two units, one of whom has a 10-3 attack and has 48/48 hp, and
//can defend at 40% on the adjacent grassland. The other has a 8-2 attack, and has 30/40 hp, and
//can defend at 60% on the adjacent mountain. The rating will be 10*3*1.0*0.4 + 8*2*0.75*0.6 = 19.2
virtual double power_projection(const gamemap::location& loc, const move_map& srcdst, const move_map& dstsrc, bool use_terrain=true) const;
virtual std::vector<attack_analysis> analyze_targets(
@ -431,7 +452,21 @@ protected:
virtual std::vector<target> find_targets(unit_map::const_iterator leader, const move_map& enemy_srcdst, const move_map& enemy_dstsrc);
virtual std::pair<location,location> choose_move(std::vector<target>& targets,const move_map& dstsrc, const move_map& enemy_srcdst, const move_map& enemy_dstsrc);
//function to form a group of units suitable for moving along the route, 'route'.
//returns the location which the group may reach this turn.
//stores the locations of the units in the group in 'units'
virtual location form_group(const std::vector<location>& route, const move_map& dstsrc, const move_map& srcdst, std::set<location>& units);
//function to return the group of enemies that threaten a certain path
virtual void enemies_along_path(const std::vector<location>& route, const move_map& dstsrc, const move_map& srcdst, std::set<location>& units);
virtual bool move_group(const location& dst, const std::vector<location>& route, const std::set<location>& units);
virtual double rate_group(const std::set<location>& group, const std::vector<location>& battlefield) const;
virtual double compare_groups(const std::set<location>& our_group, const std::set<location>& enemy_groups, const std::vector<location>& battlefield) const;
virtual std::pair<location,location> choose_move(std::vector<target>& targets,const move_map& srcdst, const move_map& dstsrc, const move_map& enemy_srcdst, const move_map& enemy_dstsrc);
//function which rates the value of moving onto certain terrain for a unit
virtual int rate_terrain(const unit& u, const location& loc);

View file

@ -30,6 +30,7 @@ const int max_positions = 10000;
void ai::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,
@ -56,8 +57,8 @@ void ai::do_attack_analysis(
return;
}
const double cur_rating = cur_analysis.movements.empty() ? 0 :
cur_analysis.rating(0.0);
const double cur_rating = cur_analysis.movements.empty() ? -1.0 :
cur_analysis.rating(0.0,*this);
double rating_to_beat = cur_rating;
@ -145,7 +146,7 @@ void ai::do_attack_analysis(
//calculate how much support we have on this hex from allies. Support does not
//take into account terrain, because we don't want to move into a hex that is
//surrounded by good defensive terrain
const double support = power_projection(tiles[j],srcdst,dstsrc,false);
const double support = power_projection(tiles[j],fullmove_srcdst,fullmove_dstsrc,false);
//if this is a position with equal defense to another position, but more vulnerability
//then we don't want to use it
@ -170,11 +171,11 @@ void ai::do_attack_analysis(
cur_analysis.analyze(map_,units_,state_,gameinfo_,50,*this,dstsrc,srcdst,enemy_dstsrc,enemy_srcdst);
if(cur_analysis.rating(0.0) > rating_to_beat) {
if(cur_analysis.rating(0.0,*this) > rating_to_beat) {
result.push_back(cur_analysis);
used_locations[cur_position] = true;
do_attack_analysis(loc,srcdst,dstsrc,enemy_srcdst,enemy_dstsrc,
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;
@ -502,7 +503,7 @@ void ai::attack_analysis::analyze(const gamemap& map,
}
}
double ai::attack_analysis::rating(double aggression) const
double ai::attack_analysis::rating(double aggression, ai& ai_obj) const
{
if(leader_threat) {
aggression = 1.0;
@ -522,16 +523,24 @@ double ai::attack_analysis::rating(double aggression) const
//of their optimal terrain into sub-optimal terrain.
//calculate the 'exposure' of our units to risk
const double exposure = resources_used*(terrain_quality - alternative_terrain_quality);
std::cerr << "attack option has base value " << value << " with exposure " << exposure << "\n";
const double exposure_mod = uses_leader ? 2.0 : ai_obj.current_team().caution();
const double exposure = exposure_mod*resources_used*(terrain_quality - alternative_terrain_quality)*vulnerability/maximum<double>(0.01,support);
std::cerr << "attack option has base value " << value << " with exposure " << exposure << ": " << vulnerability << "/" << support << " = " << (vulnerability/support) << "\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;
}
//prefer to attack already damaged targets
value += ((target_starting_damage/3 + avg_damage_inflicted)*
(target_value/resources_used) -
(1.0-aggression)*avg_damage_taken*(resources_used/target_value))/10.0;
value += ((target_starting_damage/3 + avg_damage_inflicted) - (1.0-aggression)*avg_damage_taken)/10.0;
//sanity check: if we're putting ourselves at major risk, and have no chance to kill
//then don't do it
if(vulnerability > 50.0 && vulnerability > support*5.0 && chance_to_kill < 0.02) {
return -1.0;
}
if(!leader_threat && vulnerability*terrain_quality > 0.0) {
value *= support/(vulnerability*terrain_quality);
@ -543,7 +552,9 @@ double ai::attack_analysis::rating(double aggression) const
value *= 5.0;
}
std::cerr << "value: " << value << " vulnerability: " << vulnerability << " quality: " << terrain_quality << " alternative quality: " << alternative_terrain_quality << "\n";
std::cerr << "attack on " << (target.x+1) << "," << (target.y+1) << ": attackers: " << movements.size() << " value: " << value
<< " chance to kill: " << chance_to_kill << " damage inflicted: " << avg_damage_inflicted << " damage taken: " << avg_damage_taken
<< " vulnerability: " << vulnerability << " support: " << support << " quality: " << terrain_quality << " alternative quality: " << alternative_terrain_quality << "\n";
return value;
}
@ -569,6 +580,10 @@ std::vector<ai::attack_analysis> ai::analyze_targets(
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);
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 turned to stone
@ -585,7 +600,7 @@ std::vector<ai::attack_analysis> ai::analyze_targets(
const int ticks = SDL_GetTicks();
do_attack_analysis(j->first,srcdst,dstsrc,enemy_srcdst,enemy_dstsrc,
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;
@ -607,6 +622,8 @@ double ai::power_projection(const gamemap::location& loc, const move_map& srcdst
static gamemap::location locs[6];
get_adjacent_tiles(loc,locs);
const int lawful_bonus = state_.get_time_of_day().lawful_bonus;
double res = 0.0;
for(int i = 0; i != 6; ++i) {
@ -640,19 +657,27 @@ double ai::power_projection(const gamemap::location& loc, const move_map& srcdst
}
const unit& un = u->second;
int tod_modifier = 0;
if(un.type().alignment() == unit_type::LAWFUL) {
tod_modifier = lawful_bonus;
} else if(un.type().alignment() == unit_type::CHAOTIC) {
tod_modifier = -lawful_bonus;
}
const double hp = double(un.hitpoints())/
double(un.max_hitpoints());
int most_damage = 0;
for(std::vector<attack_type>::const_iterator att =
un.attacks().begin(); att != un.attacks().end(); ++att) {
const int damage = att->damage()*att->num_attacks();
if(damage > most_damage)
const int damage = (att->damage()*att->num_attacks()*(100+tod_modifier))/100;
if(damage > most_damage) {
most_damage = damage;
}
}
const bool village = map_.is_village(terrain);
const double village_bonus = (use_terrain && village) ? 2.0 : 1.0;
const double village_bonus = (use_terrain && village) ? 1.5 : 1.0;
const double defense = use_terrain ? double(100 - un.defense_modifier(map_,terrain))/100.0 : 0.5;
const double rating = village_bonus*hp*defense*double(most_damage);

View file

@ -21,13 +21,17 @@
struct move_cost_calculator
{
enum MODE { NO_AVOID_ENEMIES, AVOID_ENEMIES };
move_cost_calculator(const unit& u, const gamemap& map,
const game_data& data,
const unit_map& units,
const gamemap::location& loc,
const std::multimap<gamemap::location,gamemap::location>& dstsrc)
const ai::move_map& dstsrc,
const ai::move_map& enemy_dstsrc)
: unit_(u), map_(map), data_(data), units_(units),
move_type_(u.type().movement_type()), loc_(loc), dstsrc_(dstsrc)
move_type_(u.type().movement_type()), loc_(loc), dstsrc_(dstsrc), enemy_dstsrc_(enemy_dstsrc),
avoid_enemies_(u.type().usage() == "scout")
{}
double cost(const gamemap::location& loc, double so_far) const
@ -52,24 +56,8 @@ struct move_cost_calculator
const double modifier = 1.0;//move_type_.defense_modifier(map_,terrain);
const double move_cost = move_type_.movement_cost(map_,terrain);
double enemies = 0;
/* //is this stuff responsible for making it take a long time?
location adj[7];
adj[0] = loc;
get_adjacent_tiles(loc,adj+1);
for(int i = 0; i != 7; ++i) {
const std::map<location,unit>::const_iterator en=units_.find(adj[i]);
//at the moment, don't allow any units to be in the path
if(i == 0 && en != units_.end()) {
return 1000.0;
}
if(en != units_.end() && en->second.side() == enemy_) {
enemies += 1.0;
}
}
*/
double res = modifier*move_cost + enemies*2.0;
const int enemies = 1 + (avoid_enemies_ ? enemy_dstsrc_.count(loc) : 0);
double res = modifier*move_cost*double(enemies);
//if there is a unit (even a friendly one) on this tile, we increase the cost to
//try discourage going through units, to thwart the 'single file effect'
@ -87,8 +75,8 @@ private:
const unit_map& units_;
const unit_movement_type& move_type_;
const gamemap::location loc_;
const ai::move_map dstsrc_;
const ai::move_map dstsrc_, enemy_dstsrc_;
const bool avoid_enemies_;
};
std::vector<ai::target> ai::find_targets(unit_map::const_iterator leader, const move_map& enemy_srcdst, const move_map& enemy_dstsrc)
@ -123,7 +111,7 @@ std::vector<ai::target> ai::find_targets(unit_map::const_iterator leader, const
const double value = threat/double(threats.size());
for(std::set<gamemap::location>::const_iterator i = threats.begin(); i != threats.end(); ++i) {
targets.push_back(target(*i,value));
targets.push_back(target(*i,value,target::THREAT));
}
}
}
@ -141,7 +129,7 @@ std::vector<ai::target> ai::find_targets(unit_map::const_iterator leader, const
}
if(get_village) {
targets.push_back(target(*t,current_team().village_value()));
targets.push_back(target(*t,current_team().village_value(),target::VILLAGE));
}
}
}
@ -155,7 +143,7 @@ std::vector<ai::target> ai::find_targets(unit_map::const_iterator leader, const
//is an enemy leader
if(u->second.can_recruit() && current_team().is_enemy(u->second.side())) {
assert(map_.on_board(u->first));
targets.push_back(target(u->first,current_team().leader_value()));
targets.push_back(target(u->first,current_team().leader_value(),target::LEADER));
}
//explicit targets for this team
@ -163,7 +151,7 @@ std::vector<ai::target> ai::find_targets(unit_map::const_iterator leader, const
j != team_targets.end(); ++j) {
if(u->second.matches_filter(j->criteria)) {
std::cerr << "found explicit target..." << j->value << "\n";
targets.push_back(target(u->first,j->value));
targets.push_back(target(u->first,j->value,target::EXPLICIT));
}
}
}
@ -195,7 +183,192 @@ std::vector<ai::target> ai::find_targets(unit_map::const_iterator leader, const
return targets;
}
std::pair<gamemap::location,gamemap::location> ai::choose_move(std::vector<target>& targets,const move_map& dstsrc, const move_map& enemy_srcdst, const move_map& enemy_dstsrc)
gamemap::location ai::form_group(const std::vector<location>& route, const move_map& dstsrc, const move_map& srcdst, std::set<location>& res)
{
if(route.empty()) {
return location();
}
std::vector<location>::const_iterator i;
for(i = route.begin(); i != route.end(); ++i) {
if(units_.count(*i) > 0) {
continue;
}
size_t n = 0, nunits = res.size();
const std::pair<move_map::const_iterator,move_map::const_iterator> itors = dstsrc.equal_range(*i);
for(move_map::const_iterator j = itors.first; j != itors.second; ++j) {
if(res.count(j->second) != 0) {
++n;
} else {
const unit_map::const_iterator un = units_.find(j->second);
if(un == units_.end() || un->second.can_recruit() || un->second.movement_left() < un->second.total_movement()) {
continue;
}
res.insert(j->second);
}
}
//if not all our units can reach this position.
if(n < nunits) {
break;
}
}
if(i != route.begin()) {
--i;
}
return *i;
}
void ai::enemies_along_path(const std::vector<location>& route, const move_map& dstsrc, const move_map& srcdst, std::set<location>& res)
{
for(std::vector<location>::const_iterator i = route.begin(); i != route.end(); ++i) {
gamemap::location adj[6];
get_adjacent_tiles(*i,adj);
for(size_t n = 0; n != 6; ++n) {
const std::pair<move_map::const_iterator,move_map::const_iterator> itors = dstsrc.equal_range(adj[n]);
for(move_map::const_iterator j = itors.first; j != itors.second; ++j) {
res.insert(j->second);
}
}
}
}
bool ai::move_group(const location& dst, const std::vector<location>& route, const std::set<location>& units)
{
const std::vector<location>::const_iterator itor = std::find(route.begin(),route.end(),dst);
if(itor == route.end()) {
return false;
}
AI_DIAGNOSTIC("group has " + str_cast(units.size()) + " members");
location next;
size_t direction = 0;
//find the direction the group is moving in
if(itor+1 != route.end()) {
next = *(itor+1);
} else if(itor != route.begin()) {
next = *(itor-1);
}
if(next.valid()) {
location adj[6];
get_adjacent_tiles(dst,adj);
direction = std::find(adj,adj+6,next) - adj;
}
std::deque<location> preferred_moves;
preferred_moves.push_back(dst);
std::map<location,paths> possible_moves;
move_map srcdst, dstsrc;
calculate_possible_moves(possible_moves,srcdst,dstsrc,false);
bool res = false;
for(std::set<location>::const_iterator i = units.begin(); i != units.end(); ++i) {
const unit_map::const_iterator un = units_.find(*i);
if(un == units_.end()) {
continue;
}
location best_loc;
int best_defense = -1;
for(std::deque<location>::const_iterator j = preferred_moves.begin(); j != preferred_moves.end(); ++j) {
if(units_.count(*j)) {
continue;
}
const std::pair<move_map::const_iterator,move_map::const_iterator> itors = dstsrc.equal_range(*j);
move_map::const_iterator m;
for(m = itors.first; m != itors.second; ++m) {
if(m->second == *i) {
break;
}
}
if(m == itors.second) {
continue;
}
const int defense = un->second.defense_modifier(map_,map_.get_terrain(*j));
if(best_loc.valid() == false || defense < best_defense) {
best_loc = *j;
best_defense = defense;
}
}
if(best_loc.valid()) {
res = true;
move_unit(*i,best_loc,possible_moves);
preferred_moves.erase(std::find(preferred_moves.begin(),preferred_moves.end(),best_loc));
//find locations that are 'perpendicular' to the direction of movement for further units to move to.
location adj[6];
get_adjacent_tiles(best_loc,adj);
for(size_t n = 0; n != 6; ++n) {
if(n != direction && ((n+3)%6) != direction && map_.on_board(adj[n]) &&
units_.count(adj[n]) == 0 && std::count(preferred_moves.begin(),preferred_moves.end(),adj[n]) == 0) {
preferred_moves.push_front(adj[n]);
AI_LOG("added moves: " + str_cast(adj[n].x+1) + "," + str_cast(adj[n].y+1));
}
}
} else {
AI_DIAGNOSTIC("Could not move group member to any of " + str_cast(preferred_moves.size()) + " locations");
}
}
return res;
}
double ai::rate_group(const std::set<location>& group, const std::vector<location>& battlefield) const
{
double strength = 0.0;
for(std::set<location>::const_iterator i = group.begin(); i != group.end(); ++i) {
const unit_map::const_iterator u = units_.find(*i);
if(u == units_.end()) {
continue;
}
const unit& un = u->second;
int defense = 0;
for(std::vector<location>::const_iterator j = battlefield.begin(); j != battlefield.end(); ++j) {
defense += un.defense_modifier(map_,map_.get_terrain(*j));
}
defense /= battlefield.size();
int best_attack = 0;
const std::vector<attack_type>& attacks = un.attacks();
for(std::vector<attack_type>::const_iterator a = attacks.begin(); a != attacks.end(); ++a) {
const int strength = a->num_attacks()*a->damage();
best_attack = maximum<int>(strength,best_attack);
}
const int rating = (defense*best_attack*un.hitpoints())/(100*un.max_hitpoints());
strength += double(rating);
}
return strength;
}
double ai::compare_groups(const std::set<location>& our_group, const std::set<location>& their_group, const std::vector<location>& battlefield) const
{
const double a = rate_group(our_group,battlefield);
const double b = maximum<double>(rate_group(their_group,battlefield),0.01);
return a/b;
}
std::pair<gamemap::location,gamemap::location> ai::choose_move(std::vector<target>& targets, const move_map& srcdst, const move_map& dstsrc, const move_map& enemy_srcdst, const move_map& enemy_dstsrc)
{
log_scope("choosing move");
@ -231,7 +404,7 @@ std::pair<gamemap::location,gamemap::location> ai::choose_move(std::vector<targe
return std::pair<location,location>(u->first,u->first);
}
const move_cost_calculator cost_calc(u->second,map_,gameinfo_,units_,u->first,dstsrc);
const move_cost_calculator cost_calc(u->second,map_,gameinfo_,units_,u->first,dstsrc,enemy_dstsrc);
//choose the best target for that unit
for(std::vector<target>::iterator tg = targets.begin(); tg != targets.end(); ++tg) {
@ -240,7 +413,19 @@ std::pair<gamemap::location,gamemap::location> ai::choose_move(std::vector<targe
assert(map_.on_board(tg->loc));
const paths::route cur_route = a_star_search(u->first,tg->loc,
minimum(tg->value/best_rating,500.0),cost_calc);
const double rating = tg->value/cur_route.move_left;
double rating = tg->value/cur_route.move_left;
//scouts do not like encountering enemies on their paths
if(u->second.type().usage() == "scout") {
std::set<location> enemies_guarding;
enemies_along_path(cur_route.steps,enemy_dstsrc,enemy_srcdst,enemies_guarding);
if(enemies_guarding.size() > 1) {
rating /= enemies_guarding.size();
}
}
std::cerr << tg->value << "/" << cur_route.move_left << " = " << rating << "\n";
if(best_target == targets.end() || rating > best_rating) {
best_rating = rating;
@ -270,10 +455,21 @@ std::pair<gamemap::location,gamemap::location> ai::choose_move(std::vector<targe
user_interact();
const move_cost_calculator calc(u->second,map_,gameinfo_,units_,u->first,dstsrc);
const move_cost_calculator calc(u->second,map_,gameinfo_,units_,u->first,dstsrc,enemy_dstsrc);
const paths::route cur_route = a_star_search(u->first,best_target->loc,
minimum(best_target->value/best_rating,100.0),calc);
const double rating = best_target->value/cur_route.move_left;
double rating = best_target->value/cur_route.move_left;
//scouts do not like encountering enemies on their paths
if(u->second.type().usage() == "scout") {
std::set<location> enemies_guarding;
enemies_along_path(cur_route.steps,enemy_dstsrc,enemy_srcdst,enemies_guarding);
if(enemies_guarding.size() > 1) {
rating /= enemies_guarding.size();
}
}
if(best == units_.end() || rating > best_rating) {
best_rating = rating;
best = u;
@ -295,11 +491,93 @@ std::pair<gamemap::location,gamemap::location> ai::choose_move(std::vector<targe
move_map fullmove_dstsrc;
calculate_possible_moves(dummy_possible_moves,fullmove_srcdst,fullmove_dstsrc,false,true);
bool dangerous = false;
//we stop and consider whether the route to this
//target is dangerous, and whether we need to group some units to move in unison toward the target
//if any point along the path is too dangerous for our single unit, then we hold back
for(std::vector<location>::const_iterator i = best_route.steps.begin(); i != best_route.steps.end(); ++i) {
const unit_map::const_iterator unit_at_target = units_.find(best_target->loc);
const double threat = power_projection(*i,enemy_srcdst,enemy_dstsrc);
if(threat >= double(best->second.hitpoints()) && threat > power_projection(*i,fullmove_srcdst,fullmove_dstsrc) ||
unit_at_target != units_.end() && current_team().is_enemy(unit_at_target->second.side())) {
dangerous = true;
break;
}
}
if(dangerous) {
AI_DIAGNOSTIC("dangerous path");
std::set<location> group, enemies;
const location dst = form_group(best_route.steps,dstsrc,srcdst,group);
enemies_along_path(best_route.steps,enemy_dstsrc,enemy_srcdst,enemies);
const double our_strength = compare_groups(group,enemies,best_route.steps);
if(our_strength > 1.0 + current_team().caution()) {
AI_DIAGNOSTIC("moving group");
const bool res = move_group(dst,best_route.steps,group);
AI_DIAGNOSTIC("");
if(res) {
return std::pair<location,location>(location(1,1),location());
} else {
AI_LOG("group didn't move " + str_cast(group.size()));
//the group didn't move, so end the first unit in the group's turn, to prevent an infinite loop
return std::pair<location,location>(*group.begin(),*group.begin());
}
} else {
AI_DIAGNOSTIC("massing to attack " + str_cast(best_target->loc.x+1) + "," + str_cast(best_target->loc.y+1) + " " + str_cast(our_strength));
const double value = best_target->value;
const location target_loc = best_target->loc;
const location loc = best->first;
const unit& un = best->second;
targets.erase(best_target);
//find the best location to mass units at for an attack on the enemies
location best_loc;
double best_threat = 0.0;
int best_distance = 0;
const double max_acceptable_threat = un.hitpoints()/4;
std::set<location> mass_locations;
const std::pair<move_map::const_iterator,move_map::const_iterator> itors = srcdst.equal_range(loc);
for(move_map::const_iterator i = itors.first; i != itors.second; ++i) {
const int distance = distance_between(target_loc,i->second);
const int defense = un.defense_modifier(map_,map_.get_terrain(i->second));
const double threat = (power_projection(i->second,enemy_srcdst,enemy_dstsrc)*defense)/100;
if(best_loc.valid() == false || threat < maximum<double>(best_threat,max_acceptable_threat) && distance < best_distance) {
best_loc = i->second;
best_threat = threat;
best_distance = distance;
}
if(threat < max_acceptable_threat) {
mass_locations.insert(i->second);
}
}
for(std::set<location>::const_iterator j = mass_locations.begin(); j != mass_locations.end(); ++j) {
if(*j != best_loc && distance_between(*j,best_loc) < 3) {
targets.push_back(target(*j,value*4.0,target::MASS));
}
}
return std::pair<location,location>(loc,best_loc);
}
}
for(std::vector<location>::reverse_iterator ri =
best_route.steps.rbegin(); ri != best_route.steps.rend(); ++ri) {
if(game_config::debug) {
display::debug_highlight(*ri,0.2);
//display::debug_highlight(*ri,0.2);
}
//this is set to 'true' if we are hesitant to proceed because of enemy units,
@ -313,11 +591,11 @@ std::pair<gamemap::location,gamemap::location> ai::choose_move(std::vector<targe
if(!should_retreat(its.first->first,best,fullmove_srcdst,fullmove_dstsrc,enemy_srcdst,enemy_dstsrc)) {
const double value = best_target->value - best->second.type().cost()/20.0;
if(value > 0.0) {
if(value > 0.0 && best_target->type != target::MASS) {
//there are enemies ahead. Rally troops around us to
//try to take the target
if(is_dangerous) {
targets.push_back(target(its.first->first,value*2.0));
targets.push_back(target(its.first->first,value*2.0,target::BATTLE_AID));
}
best_target->value = value;

View file

@ -66,7 +66,7 @@ display::display(unit_map& units, CVideo& video, const gamemap& map,
turbo_(false), grid_(false), sidebarScaling_(1.0),
theme_(theme_cfg,screen_area()), builder_(built_terrains, map),
first_turn_(true), in_game_(false), map_labels_(*this,map),
tod_hex_mask1(NULL), tod_hex_mask2(NULL)
tod_hex_mask1(NULL), tod_hex_mask2(NULL), diagnostic_label_(0)
{
if(non_interactive())
updatesLocked_++;
@ -2194,6 +2194,18 @@ void display::prune_chat_messages(bool remove_all)
}
}
void display::set_diagnostic(const std::string& msg)
{
if(diagnostic_label_ != 0) {
font::remove_floating_label(diagnostic_label_);
diagnostic_label_ = 0;
}
if(msg != "") {
diagnostic_label_ = font::add_floating_label(msg,16,font::YELLOW_COLOUR,300.0,50.0,0.0,0.0,-1,map_area());
}
}
void display::rebuild_terrain(const gamemap::location &loc) {
builder_.rebuild_terrain(loc);
}

View file

@ -291,6 +291,8 @@ public:
map_labels& labels() { return map_labels_; }
const map_labels& labels() const { return map_labels_; }
void set_diagnostic(const std::string& msg);
enum MESSAGE_TYPE { MESSAGE_PUBLIC, MESSAGE_PRIVATE };
void add_chat_message(const std::string& speaker, int side, const std::string& msg, MESSAGE_TYPE type);
@ -311,7 +313,7 @@ public:
//rebuild the dynamic terrain at the given location.
void rebuild_terrain(const gamemap::location &location);
//Add a location to highlight. Note that this has nothign to do with
//Add a location to highlight. Note that this has nothing to do with
//selecting hexes, it is pure highlighting.
void add_highlighted_loc(const gamemap::location &hex);
@ -455,6 +457,8 @@ private:
static std::map<gamemap::location,double> debugHighlights_;
std::set<gamemap::location> highlighted_locations_;
int diagnostic_label_;
};
//an object which will lock the display for the duration of its lifetime.

View file

@ -138,25 +138,36 @@ std::vector<hotkey::hotkey_item> hotkeys;
}
struct hotkey_pressed {
hotkey_pressed(const SDL_KeyboardEvent& event);
//this distinguishes between two modes of operation. If mods are disallowed,
//then any match must be exact. I.e. "shift+a" does not match "a". If they are allowed,
//then shift+a will match "a"
enum ALLOW_MOD_KEYS { DISALLOW_MODS, ALLOW_MODS };
hotkey_pressed(const SDL_KeyboardEvent& event, ALLOW_MOD_KEYS allow_mods=DISALLOW_MODS);
bool operator()(const hotkey::hotkey_item& hk) const;
private:
int keycode_;
bool shift_, ctrl_, alt_, command_;
bool mods_;
};
hotkey_pressed::hotkey_pressed(const SDL_KeyboardEvent& event)
hotkey_pressed::hotkey_pressed(const SDL_KeyboardEvent& event, ALLOW_MOD_KEYS mods)
: keycode_(event.keysym.sym), shift_(event.keysym.mod&KMOD_SHIFT),
ctrl_(event.keysym.mod&KMOD_CTRL), alt_(event.keysym.mod&KMOD_ALT),
command_(event.keysym.mod&KMOD_LMETA)
command_(event.keysym.mod&KMOD_LMETA), mods_(mods == ALLOW_MODS)
{}
bool hotkey_pressed::operator()(const hotkey::hotkey_item& hk) const
{
return hk.keycode == keycode_ && shift_ == hk.shift &&
ctrl_ == hk.ctrl && alt_ == hk.alt && command_ == hk.command;
if(mods_) {
return hk.keycode == keycode_ && (shift_ == hk.shift || shift_ == true)
&& (ctrl_ == hk.ctrl || ctrl_ == true)
&& (alt_ == hk.alt || alt_ == true);
} else {
return hk.keycode == keycode_ && shift_ == hk.shift &&
ctrl_ == hk.ctrl && alt_ == hk.alt && command_ == hk.command;
}
}
namespace {
@ -255,10 +266,16 @@ void key_event(display& disp, const SDL_KeyboardEvent& event, command_executor*
}
}
const std::vector<hotkey_item>::iterator i = std::find_if(hotkeys.begin(),hotkeys.end(),hotkey_pressed(event));
std::vector<hotkey_item>::iterator i = std::find_if(hotkeys.begin(),hotkeys.end(),hotkey_pressed(event));
if(i == hotkeys.end())
if(i == hotkeys.end()) {
//no matching hotkey was found, but try an in-exact match.
i = std::find_if(hotkeys.begin(),hotkeys.end(),hotkey_pressed(event,hotkey_pressed::ALLOW_MODS));
}
if(i == hotkeys.end()) {
return;
}
execute_command(disp,i->action,executor);
}

View file

@ -171,7 +171,6 @@ void set_wm_icon()
#if !(defined(__APPLE__))
scoped_sdl_surface icon(get_image(game_config::game_icon,UNSCALED));
if(icon != NULL) {
std::cerr << "setting icon...\n";
::SDL_WM_SetIcon(icon,NULL);
}
#endif

View file

@ -78,8 +78,7 @@ int mp_connect::load_map(const std::string& era, int map, int num_turns, int vil
const config::child_list& levels = cfg_->get_children("multiplayer");
if(map == levels.size() )
{
if(map == levels.size()) {
//Load a saved game
save_ = true;
bool show_replay = false;

View file

@ -264,8 +264,9 @@ RESULT enter(display& disp, config& game_data, const config& terrain_data, dialo
key[SDLK_UP],key[SDLK_DOWN],
key[SDLK_PAGEUP],key[SDLK_PAGEDOWN]);
const bool observe = observe_game.pressed();
if(games_available && (observe || join_game.pressed() || games_menu.double_clicked())) {
const bool double_click = games_menu.double_clicked();
const bool observe = observe_game.pressed() || !games_available && double_click;
if(games_available && (observe || join_game.pressed() || double_click)) {
const size_t index = size_t(games_menu.selection());
const config::const_child_itors i = gamelist->child_range("game");
assert(index < size_t(i.second - i.first));

View file

@ -1549,6 +1549,11 @@ void turn_info::write_game_snapshot(config& start) const
game_events::write_events(start);
write_game(state_of_game_,start);
//clear any unnecessary children that we don't want in a snapshot
start.clear_children("snapshot");
start.clear_children("replay_start");
start["gold"] = "-1000000"; //just make sure gold is read in from the teams
//write out the current state of the map
@ -1850,9 +1855,12 @@ void turn_info::recall()
bool turn_info::has_friends() const
{
if(is_observer()) {
return false;
}
for(size_t n = 0; n != teams_.size(); ++n) {
if(n != gui_.viewing_team() && teams_[gui_.viewing_team()].team_name() == teams_[n].team_name() &&
teams_[n].is_network()) {
if(n != gui_.viewing_team() && teams_[gui_.viewing_team()].team_name() == teams_[n].team_name() && teams_[n].is_network()) {
return true;
}
}

View file

@ -101,7 +101,8 @@ unit::unit(const unit_type* t, const unit& u) :
attacks_(t->attacks()), backupAttacks_(t->attacks()),
modifications_(u.modifications_),
traitsDescription_(u.traitsDescription_),
guardian_(false), upkeep_(u.upkeep_)
guardian_(false), upkeep_(u.upkeep_),
overlays_(u.overlays_), variables_(u.variables_)
{
validate_side(side_);

View file

@ -15,6 +15,7 @@
#include <cmath>
#include <map>
#include <sstream>
//instead of playing with VC++'s crazy definitions of min and max,
//just define our own
@ -73,6 +74,12 @@ To lexical_cast_default(From a, To def=To())
}
}
template<typename From>
std::string str_cast(From a)
{
return lexical_cast<std::string,From>(a);
}
inline bool chars_equal_insensitive(char a, char b) { return tolower(a) == tolower(b); }
//a definition of 'push_back' for strings, since some implementations