diff --git a/src/ai/contexts.cpp b/src/ai/contexts.cpp index 845af9cbbaf..989a1d98a75 100644 --- a/src/ai/contexts.cpp +++ b/src/ai/contexts.cpp @@ -238,7 +238,7 @@ void readonly_context_impl::log_message(const std::string& msg) map_location readwrite_context_impl::move_unit(map_location from, map_location to, - std::map& possible_moves) + const moves_map &possible_moves) { const map_location loc = move_unit_partial(from,to,possible_moves); const unit_map::iterator u = get_info().units.find(loc); @@ -256,7 +256,7 @@ map_location readwrite_context_impl::move_unit(map_location from, map_location t map_location readwrite_context_impl::move_unit_partial(map_location from, map_location to, - std::map& possible_moves) + const moves_map &possible_moves) { LOG_AI << "readwrite_context_impl::move_unit " << from << " -> " << to << '\n'; assert(to.valid() && to.x <= MAX_MAP_AREA && to.y <= MAX_MAP_AREA); @@ -280,12 +280,12 @@ map_location readwrite_context_impl::move_unit_partial(map_location from, map_lo const bool show_move = preferences::show_ai_moves(); - const std::map::iterator p_it = possible_moves.find(from); + const std::map::const_iterator p_it = possible_moves.find(from); std::vector steps; if(p_it != possible_moves.end()) { - paths& p = p_it->second; + const paths& p = p_it->second; paths::dest_vect::const_iterator rt = p.destinations.find(to); if (rt != p.destinations.end()) { diff --git a/src/ai/contexts.hpp b/src/ai/contexts.hpp index 2ef30dd5ad3..2e8e33c144d 100644 --- a/src/ai/contexts.hpp +++ b/src/ai/contexts.hpp @@ -195,8 +195,8 @@ public: virtual stopunit_result_ptr execute_stopunit_action(const map_location& unit_location, bool remove_movement = true, bool remove_attacks = false) = 0; virtual team& current_team_w() = 0; virtual void attack_enemy(const map_location u, const map_location target, int att_weapon, int def_weapon) = 0; - virtual map_location move_unit(map_location from, map_location to, std::map& possible_moves) = 0; - virtual map_location move_unit_partial(map_location from, map_location to, std::map& possible_moves) = 0; + virtual map_location move_unit(map_location from, map_location to, const moves_map &possible_moves) = 0; + virtual map_location move_unit_partial(map_location from, map_location to, const moves_map &possible_moves) = 0; virtual bool recruit(const std::string& unit_name, map_location loc=map_location()) = 0; virtual void raise_unit_recruited() const = 0; virtual void raise_unit_moved() const = 0; @@ -458,13 +458,13 @@ public: } - virtual map_location move_unit(map_location from, map_location to, std::map& possible_moves) + virtual map_location move_unit(map_location from, map_location to, const moves_map &possible_moves) { return target_->move_unit(from, to, possible_moves); } - virtual map_location move_unit_partial(map_location from, map_location to, std::map& possible_moves) + virtual map_location move_unit_partial(map_location from, map_location to, const moves_map &possible_moves) { return target_->move_unit_partial(from, to, possible_moves); } @@ -826,14 +826,14 @@ public: * @param possible_moves The map of possible moves, as obtained from * 'calculate_possible_moves'. */ - virtual map_location move_unit(map_location from, map_location to, std::map& possible_moves); + virtual map_location move_unit(map_location from, map_location to, const moves_map &possible_moves); /** * @deprecated * Identical to 'move_unit', except that the unit's movement * isn't set to 0 after the move is complete. */ - map_location move_unit_partial(map_location from, map_location to, std::map& possible_moves); + map_location move_unit_partial(map_location from, map_location to, const moves_map &possible_moves); /** diff --git a/src/ai/default/contexts.cpp b/src/ai/default/contexts.cpp index a2bf30c461d..c6544fea56d 100644 --- a/src/ai/default/contexts.cpp +++ b/src/ai/default/contexts.cpp @@ -550,6 +550,51 @@ bool default_ai_context_impl::leader_can_reach_keep() } +bool default_ai_context_impl::multistep_move_possible(const map_location& from, + const map_location& to, const map_location& via, + const moves_map& possible_moves) const +{ + unit_map &units_ = get_info().units; + const unit_map::const_iterator i = units_.find(from); + if(i != units_.end()) { + if(from != via && to != via && units_.count(via) == 0) { + LOG_AI << "when seeing if leader can move from " + << from << " -> " << to + << " seeing if can detour to keep at " << via << '\n'; + const std::map::const_iterator moves = possible_moves.find(from); + if(moves != possible_moves.end()) { + + LOG_AI << "found leader moves..\n"; + + // See if the unit can make it to 'via', and if it can, + // how much movement it will have left when it gets there. + paths::dest_vect::const_iterator itor = + moves->second.destinations.find(via); + if (itor != moves->second.destinations.end()) + { + LOG_AI << "Can make it to keep with " << itor->move_left << " movement left.\n"; + unit temp_unit(i->second); + temp_unit.set_movement(itor->move_left); + const temporary_unit_placer unit_placer(units_,via,temp_unit); + const paths unit_paths(get_info().map,units_,via,get_info().teams,false,false,current_team()); + + LOG_AI << "Found " << unit_paths.destinations.size() << " moves for temp leader.\n"; + + // See if this leader could make it back to the keep. + if (unit_paths.destinations.contains(to)) { + LOG_AI << "can make it back to the keep\n"; + return true; + } + } + } + } + } + + return false; +} + + + const map_location& default_ai_context_impl::nearest_keep(const map_location& loc) { const std::set& keeps = this->keeps(); diff --git a/src/ai/default/contexts.hpp b/src/ai/default/contexts.hpp index 06a53b99ab1..6ffdbcc673f 100644 --- a/src/ai/default/contexts.hpp +++ b/src/ai/default/contexts.hpp @@ -193,6 +193,11 @@ public: virtual bool leader_can_reach_keep() = 0; + virtual bool multistep_move_possible(const map_location& from, + const map_location& to, const map_location& via, + const moves_map& possible_moves) const = 0; + + virtual const map_location& nearest_keep(const map_location& loc) = 0; @@ -326,6 +331,14 @@ public: } + virtual bool multistep_move_possible(const map_location& from, + const map_location& to, const map_location& via, + const moves_map& possible_moves) const + { + return target_->multistep_move_possible(from,to,via,possible_moves); + } + + virtual const map_location& nearest_keep( const map_location& loc ) { return target_->nearest_keep(loc); @@ -433,6 +446,11 @@ public: virtual const std::set& keeps(); + virtual bool multistep_move_possible(const map_location& from, + const map_location& to, const map_location& via, + const moves_map& possible_moves) const; + + virtual const map_location& nearest_keep( const map_location& loc ); diff --git a/src/ai/testing/ca.cpp b/src/ai/testing/ca.cpp index dc158a23820..d975461f21a 100644 --- a/src/ai/testing/ca.cpp +++ b/src/ai/testing/ca.cpp @@ -16,13 +16,14 @@ * Default AI (Testing) * @file ai/testing/ca.cpp */ -#include "../../global.hpp" #include "ca.hpp" #include "../composite/engine.hpp" #include "../composite/rca.hpp" #include "../../log.hpp" +#include + static lg::log_domain log_ai_testing_ai_default("ai/testing/ai_default"); #define DBG_AI_TESTING_AI_DEFAULT LOG_STREAM(debug, log_ai_testing_ai_default) #define LOG_AI_TESTING_AI_DEFAULT LOG_STREAM(info, log_ai_testing_ai_default) @@ -202,7 +203,8 @@ bool move_leader_to_goals_phase::execute() //============================================================== get_villages_phase::get_villages_phase( rca_context &context, const config &cfg ) - : candidate_action(context,"testing_ai_default::get_villages_phase",cfg["type"]) + : candidate_action(context,"testing_ai_default::get_villages_phase",cfg["type"]), keep_loc_(), + leader_loc_(),best_leader_loc_(),debug_(false) { } @@ -212,14 +214,803 @@ get_villages_phase::~get_villages_phase() double get_villages_phase::evaluate() { - ERR_AI_TESTING_AI_DEFAULT << get_name() << ": evaluate - not yet implemented!" << std::endl; + moves_.clear(); + unit_map::const_iterator leader = get_info().units.find_leader(get_side()); + get_villages(get_possible_moves(),get_dstsrc(),get_enemy_dstsrc(),leader); + if (moves_.size()>0) { + return 25; + } return BAD_SCORE; } bool get_villages_phase::execute() { - ERR_AI_TESTING_AI_DEFAULT << get_name() << ": execute - not yet implemented!" << std::endl; - return true; + bool gamestate_changed = false; + unit_map &units_ = get_info().units; + unit_map::const_iterator leader = units_.find_leader(get_side()); + // Move all the units to get villages, however move the leader last, + // so that the castle will be cleared if it wants to stop to recruit along the way. + std::pair leader_move; + + for(tmoves::const_iterator i = moves_.begin(); i != moves_.end(); ++i) { + + if(leader != units_.end() && leader->first == i->second) { + leader_move = *i; + } else { + if(units_.count(i->first) == 0) { + move_result_ptr move_res = execute_move_action(i->second,i->first,true); + gamestate_changed |= move_res->is_gamestate_changed(); + if (!move_res->is_ok()) { + return gamestate_changed; + } + + const map_location loc = move_res->get_unit_location(); + leader = units_.find_leader(get_side()); + const unit_map::const_iterator new_unit = units_.find(loc); + + if(new_unit != units_.end() && + power_projection(i->first,get_enemy_dstsrc()) >= new_unit->second.hitpoints()/4) { + LOG_AI_TESTING_AI_DEFAULT << "found support target... " << new_unit->first << "\n"; + //FIXME: suokko tweaked the constant 1.0 to the formula: + //25.0* current_team().caution() * power_projection(loc,enemy_dstsrc) / new_unit->second.hitpoints() + //Is this an improvement? + + //@todo 1.7 fix this to account for RCA semantics + //add_target(target(new_unit->first,1.0,target::SUPPORT)); + } + } + } + } + + if(leader_move.second.valid()) { + if(units_.count(leader_move.first) == 0 && get_info().map.is_village(leader_move.first)) { + move_result_ptr move_res = execute_move_action(leader_move.second,leader_move.first,true); + gamestate_changed |= move_res->is_gamestate_changed(); + if (!move_res->is_ok()) { + return gamestate_changed; + } + } + } + + return gamestate_changed; +} + +bool get_villages_phase::get_villages(const moves_map& possible_moves, + const move_map& dstsrc, const move_map& enemy_dstsrc, + unit_map::const_iterator &leader) +{ + DBG_AI_TESTING_AI_DEFAULT << "deciding which villages we want...\n"; + unit_map &units_ = get_info().units; + const int ticks = SDL_GetTicks(); + best_leader_loc_ = map_location::null_location; + if(leader != units_.end()) { + keep_loc_ = nearest_keep(leader->first); + leader_loc_ = leader->first; + } else { + keep_loc_ = map_location::null_location; + leader_loc_ = map_location::null_location; + } + + debug_ = !lg::debug.dont_log(log_ai_testing_ai_default); + + // Find our units who can move. + treachmap reachmap; + for(unit_map::const_iterator u_itor = units_.begin(); + u_itor != units_.end(); ++u_itor) { + + if(u_itor->second.side() == get_side() + && u_itor->second.movement_left()) { + reachmap.insert(std::make_pair(u_itor->first, std::vector())); + } + } + + + DBG_AI_TESTING_AI_DEFAULT << reachmap.size() << " units found who can try to capture a village.\n"; + + find_villages(reachmap, moves_, dstsrc, possible_moves, enemy_dstsrc); + + treachmap::iterator itor = reachmap.begin(); + while(itor != reachmap.end()) { + if(itor->second.size() == 0) { + itor = remove_unit(reachmap, moves_, itor); + } else { + ++itor; + } + } + + if(reachmap.size()) { + DBG_AI_TESTING_AI_DEFAULT << reachmap.size() << " units left after removing the ones who " + "can't reach a village, send the to the dispatcher.\n"; + + dump_reachmap(reachmap); + + dispatch(reachmap, moves_); + } else { + DBG_AI_TESTING_AI_DEFAULT << "No more units left after removing the ones who can't reach a village.\n"; + } + + LOG_AI_TESTING_AI_DEFAULT << "Village assignment done: " << (SDL_GetTicks() - ticks) + << " ms, resulted in " << moves_.size() << " units being dispatched.\n"; + +} + +void get_villages_phase::find_villages( + treachmap& reachmap, + tmoves& moves, + const std::multimap& dstsrc, + const std::map& possible_moves, + const std::multimap& enemy_dstsrc) + +{ + std::map vulnerability; + + const bool passive_leader = utils::string_bool(current_team().ai_parameters()["passive_leader"]);//@todo: make an aspect + + size_t min_distance = 100000; + gamemap &map_ = get_info().map; + std::vector &teams_ = get_info().teams; + + // When a unit is dispatched we need to make sure we don't + // dispatch this unit a second time, so store them here. + std::vector dispatched_units; + for(std::multimap::const_iterator + j = dstsrc.begin(); + j != dstsrc.end(); ++j) { + + const map_location ¤t_loc = j->first; + + if(j->second == leader_loc_) { + if(passive_leader) { + continue; + } + + const size_t distance = distance_between(keep_loc_, current_loc); + if(distance < min_distance) { + min_distance = distance; + best_leader_loc_ = current_loc; + } + } + + if(std::find(dispatched_units.begin(), dispatched_units.end(), + j->second) != dispatched_units.end()) { + continue; + } + + if(map_.is_village(current_loc) == false) { + continue; + } + + bool want_village = true, owned = false; + for(size_t n = 0; n != teams_.size(); ++n) { + owned = teams_[n].owns_village(current_loc); + if(owned && !current_team().is_enemy(n+1)) { + want_village = false; + } + + if(owned) { + break; + } + } + + if(want_village == false) { + continue; + } + + // If it is a neutral village, and we have no leader, + // then the village is of no use to us, and we don't want it. + if(!owned && leader_loc_ == map_location::null_location) { + continue; + } + + // If we have a decent amount of gold, and the leader can't access + // the keep this turn if they get this village, + // then don't get this village with them. + if(want_village && + current_team().gold() > 20 && + leader_loc_ == current_loc && + leader_loc_ != keep_loc_ && + multistep_move_possible(j->second, current_loc, keep_loc_, possible_moves) == false) { + continue; + } + + double threat = 0.0; + const std::map::const_iterator vuln = vulnerability.find(current_loc); + if(vuln != vulnerability.end()) { + threat = vuln->second; + } else { + threat = power_projection(current_loc,enemy_dstsrc); + vulnerability.insert(std::pair(current_loc,threat)); + } + + const unit_map::const_iterator u = get_info().units.find(j->second); + if(u == get_info().units.end() || utils::string_bool(u->second.get_state("guardian"))) { + continue; + } + + const unit& un = u->second; + //FIXME: suokko turned this 2:1 to 1.5:1.0. + //and dropped the second term of the multiplication. Is that better? + //const double threat_multipler = (current_loc == leader_loc?2:1) * current_team().caution() * 10; + if(un.hitpoints() < (threat*2*un.defense_modifier(map_.get_terrain(current_loc)))/100) { + continue; + } + + // If the next and previous destination differs from our current destination, + // we're the only one who can reach the village -> dispatch. + std::multimap::const_iterator next = j; + ++next; // j + 1 fails + const bool at_begin = (j == dstsrc.begin()); + std::multimap::const_iterator prev = j; //FIXME seems not to work + if(!at_begin) { + --prev; + } +#if 1 + if((next == dstsrc.end() || next->first != current_loc) + && (at_begin || prev->first != current_loc)) { + + move_result_ptr move_check_res = check_move_action(j->second,j->first,true); + if (move_check_res->is_ok()) { + DBG_AI_TESTING_AI_DEFAULT << "Dispatched unit at " << j->second << " to village " << j->first << '\n'; + moves.push_back(std::make_pair(j->first, j->second)); + } + reachmap.erase(j->second); + dispatched_units.push_back(j->second); + continue; + } +#endif + reachmap[j->second].push_back(current_loc); + } + + DBG_AI_TESTING_AI_DEFAULT << moves.size() << " units already dispatched, " + << reachmap.size() << " left to evaluate.\n"; +} + +void get_villages_phase::dispatch(treachmap& reachmap, tmoves& moves) +{ + DBG_AI_TESTING_AI_DEFAULT << "Starting simple dispatch.\n"; + + // we now have a list with units with the villages they can reach. + // keep trying the following steps as long as one of them changes + // the state. + // 1. Dispatch units who can reach 1 village (if more units can reach that + // village only one can capture it, so use the first in the list.) + // 2. Villages which can only be reached by one unit get that unit dispatched + // to them. + size_t village_count = 0; + bool dispatched = true; + while(dispatched) { + dispatched = false; + + if(dispatch_unit_simple(reachmap, moves)) { + dispatched = true; + } else { + if(reachmap.empty()) { + DBG_AI_TESTING_AI_DEFAULT << "dispatch_unit_simple() found a final solution.\n"; + break; + } else { + DBG_AI_TESTING_AI_DEFAULT << "dispatch_unit_simple() couldn't dispatch more units.\n"; + } + } + + if(dispatch_village_simple(reachmap, moves, village_count)) { + dispatched = true; + } else { + if(reachmap.empty()) { + DBG_AI_TESTING_AI_DEFAULT << "dispatch_village_simple() found a final solution.\n"; + break; + } else { + DBG_AI_TESTING_AI_DEFAULT << "dispatch_village_simple() couldn't dispatch more units.\n"; + } + } + + if(reachmap.size() != 0 && dispatched) { + DBG_AI_TESTING_AI_DEFAULT << reachmap.size() << " unit(s) left restarting simple dispatching.\n"; + + dump_reachmap(reachmap); + } + } + + if(reachmap.size() == 0) { + DBG_AI_TESTING_AI_DEFAULT << "No units left after simple dispatcher.\n"; + return; + } + + DBG_AI_TESTING_AI_DEFAULT << reachmap.size() << " units left for complex dispatch with " + << village_count << " villages left.\n"; + + dump_reachmap(reachmap); + + dispatch_complex(reachmap, moves, village_count); +} + +// Returns need further processing +// false Nothing has been modified or no units left +bool get_villages_phase::dispatch_unit_simple(treachmap& reachmap, tmoves& moves) +{ + bool result = false; + + treachmap::iterator itor = reachmap.begin(); + while(itor != reachmap.end()) { + if(itor->second.size() == 1) { + const map_location village = itor->second[0]; + result = true; + + DBG_AI_TESTING_AI_DEFAULT << "Dispatched unit at " << itor->first << " to village " << village << '\n'; + moves.push_back(std::make_pair(village, itor->first)); + reachmap.erase(itor++); + + if(remove_village(reachmap, moves, village)) { + itor = reachmap.begin(); + } + + } else { + ++itor; + } + } + + // Test special cases. + if(reachmap.empty()) { + // We're done. + return false; + } + + if(reachmap.size() == 1) { + // One unit left. + DBG_AI_TESTING_AI_DEFAULT << "Dispatched _last_ unit at " << reachmap.begin()->first + << " to village " << reachmap.begin()->second[0] << '\n'; + + moves.push_back(std::make_pair( + reachmap.begin()->second[0], reachmap.begin()->first)); + + reachmap.clear(); + // We're done. + return false; + } + + return result; +} + +bool get_villages_phase::dispatch_village_simple( + treachmap& reachmap, tmoves& moves, size_t& village_count) +{ + + bool result = false; + bool dispatched = true; + while(dispatched) { + dispatched = false; + + // build the reverse map + std::map >reversemap; + + treachmap::const_iterator itor = reachmap.begin(); + for(;itor != reachmap.end(); ++itor) { + + for(std::vector::const_iterator + v_itor = itor->second.begin(); + v_itor != itor->second.end(); ++v_itor) { + + reversemap[*v_itor].push_back(itor->first); + + } + } + + village_count = reversemap.size(); + + itor = reversemap.begin(); + while(itor != reversemap.end()) { + if(itor->second.size() == 1) { + // One unit can reach this village. + const map_location village = itor->first; + dispatched = true; + result = true; + + DBG_AI_TESTING_AI_DEFAULT << "Dispatched unit at " << itor->second[0] << " to village " << itor->first << '\n'; + moves.push_back(std::make_pair(itor->first, itor->second[0])); + + reachmap.erase(itor->second[0]); + remove_village(reachmap, moves, village); + // Get can go to some trouble to remove the unit from the other villages + // instead we abort this loop end do a full rebuild on the map. + break; + } else { + ++itor; + } + } + } + + return result; +} + +bool get_villages_phase::remove_village( + treachmap& reachmap, tmoves& moves, const map_location& village) +{ + bool result = false; + treachmap::iterator itor = reachmap.begin(); + while(itor != reachmap.end()) { + itor->second.erase(std::remove(itor->second.begin(), itor->second.end(), village), itor->second.end()); + if(itor->second.empty()) { + result = true; + itor = remove_unit(reachmap, moves, itor); + } else { + ++itor; + } + } + return result; +} + +get_villages_phase::treachmap::iterator get_villages_phase::remove_unit( + treachmap& reachmap, tmoves& moves, treachmap::iterator unit) +{ + assert(unit->second.empty()); + + if(unit->first == leader_loc_ && best_leader_loc_ != map_location::null_location) { + DBG_AI_TESTING_AI_DEFAULT << "Dispatch leader at " << leader_loc_ << " closer to the keep at " + << best_leader_loc_ << '\n'; + + moves.push_back(std::make_pair(best_leader_loc_, leader_loc_)); + } + + reachmap.erase(unit++); + return unit; +} + +void get_villages_phase::dispatch_complex( + treachmap& reachmap, tmoves& moves, const size_t village_count) +{ + // ***** ***** Init and dispatch if every unit can reach every village. + + const size_t unit_count = reachmap.size(); + // The maximum number of villages we can capture with the available units. + const size_t max_result = unit_count < village_count ? unit_count : village_count; + + assert(unit_count >= 2 && village_count >= 2); + + // Every unit can reach every village. + if(unit_count == 2 && village_count == 2) { + DBG_AI_TESTING_AI_DEFAULT << "Every unit can reach every village for 2 units, dispatch them.\n"; + full_dispatch(reachmap, moves); + return; + } + + std::vector units(unit_count); + std::vector villages_per_unit(unit_count); + std::vector villages; + std::vector units_per_village(village_count); + + // We want to test the units, the ones who can reach the least + // villages first so this is our lookup map. + std::multimap unit_lookup; + + std::vector > + matrix(reachmap.size(), std::vector(village_count, false)); + + treachmap::const_iterator itor = reachmap.begin(); + for(size_t u = 0; u < unit_count; ++u, ++itor) { + units[u] = itor->first; + villages_per_unit[u] = itor->second.size(); + unit_lookup.insert(std::make_pair(villages_per_unit[u], u)); + + assert(itor->second.size() >= 2); + + for(size_t v = 0; v < itor->second.size(); ++v) { + + size_t v_index; + // find the index of the v in the villages + std::vector::const_iterator v_itor = + std::find(villages.begin(), villages.end(), itor->second[v]); + if(v_itor == villages.end()) { + v_index = villages.size(); // will be the last element after push_back. + villages.push_back(itor->second[v]); + } else { + v_index = v_itor - villages.begin(); + } + + units_per_village[v_index]++; + + matrix[u][v_index] = true; + } + } + for(std::vector::const_iterator upv_it = units_per_village.begin(); + upv_it != units_per_village.end(); ++upv_it) { + + assert(*upv_it >=2); + } + + if(debug_) { + // Print header + std::cerr << "Reach matrix:\n\nvillage"; + size_t u, v; + for(v = 0; v < village_count; ++v) { + std::cerr << '\t' << villages[v]; + } + std::cerr << "\ttotal\nunit\n"; + + // Print data + for(u = 0; u < unit_count; ++u) { + std::cerr << units[u]; + + for(size_t v = 0; v < village_count; ++v) { + std::cerr << '\t' << matrix[u][v]; + } + std::cerr << "\t" << villages_per_unit[u] << '\n'; + } + + // Print footer + std::cerr << "total"; + for(v = 0; v < village_count; ++v) { + std::cerr << '\t' << units_per_village[v]; + } + std::cerr << '\n'; + } + + // Test the special case, everybody can reach all villages + const bool reach_all = ((village_count == unit_count) + && (std::accumulate(villages_per_unit.begin(), villages_per_unit.end(), size_t()) + == (village_count * unit_count))); + + if(reach_all) { + DBG_AI_TESTING_AI_DEFAULT << "Every unit can reach every village, dispatch them\n"; + full_dispatch(reachmap, moves); + reachmap.clear(); + return; + } + + // ***** ***** Find a square + std::multimap + ::const_iterator src_itor = unit_lookup.begin(); + + while(src_itor != unit_lookup.end() && src_itor->first == 2) { + + for(std::multimap::const_iterator + dst_itor = unit_lookup.begin(); + dst_itor != unit_lookup.end(); ++ dst_itor) { + + // avoid comparing us with ourselves. + if(src_itor == dst_itor) { + continue; + } + + std::vector result; + std::transform(matrix[src_itor->second].begin(), matrix[src_itor->second].end(), + matrix[dst_itor->second].begin(), + std::back_inserter(result), + std::logical_and() + ); + + size_t matched = std::count(result.begin(), result.end(), true); + + // we found a solution, dispatch + if(matched == 2) { + // Collect data + std::vector::iterator first = std::find(result.begin(), result.end(), true); + std::vector::iterator second = std::find(first + 1, result.end(), true); + + const map_location village1 = villages[first - result.begin()]; + const map_location village2 = villages[second - result.begin()]; + + const bool perfect = (src_itor->first == 2 && + dst_itor->first == 2 && + units_per_village[first - result.begin()] == 2 && + units_per_village[second - result.begin()] == 2); + + // Dispatch + DBG_AI_TESTING_AI_DEFAULT << "Found a square.\nDispatched unit at " << units[src_itor->second] + << " to village " << village1 << '\n'; + moves.push_back(std::make_pair(village1, units[src_itor->second])); + + DBG_AI_TESTING_AI_DEFAULT << "Dispatched unit at " << units[dst_itor->second] + << " to village " << village2 << '\n'; + moves.push_back(std::make_pair(village2, units[dst_itor->second])); + + // Remove the units + reachmap.erase(units[src_itor->second]); + reachmap.erase(units[dst_itor->second]); + + // Evaluate and start correct function. + if(perfect) { + // We did a perfect dispatch 2 units who could visit 2 villages. + // This means we didn't change the assertion for this funtions + // so call ourselves recursively, and finish afterwards. + DBG_AI_TESTING_AI_DEFAULT << "Perfect dispatch, do complex again.\n"; + dispatch_complex(reachmap, moves, village_count - 2); + return; + } else { + // We did a not perfect dispatch but we did modify things + // so restart dispatching. + DBG_AI_TESTING_AI_DEFAULT << "NON Perfect dispatch, do dispatch again.\n"; + remove_village(reachmap, moves, village1); + remove_village(reachmap, moves, village2); + dispatch(reachmap, moves); + return; + } + } + } + + ++src_itor; + } + + // ***** ***** Do all permutations. + // Now walk through all possible permutations + // - test whether the suggestion is possible + // - does it result in max_villages + // - dispatch and ready + // - is it's result better as the last best + // - store + std::vector > best_result; + + // Bruteforcing all possible permutations can result in a slow game. + // So there needs to be a balance between the best possible result and + // not too slow. From the test (at the end of the file) a good number is + // picked. In general we shouldn't reach this point too often if we do + // there are a lot of villages which are unclaimed and a lot of units + // to claim them. + const size_t max_options = 8; + if(unit_count >= max_options && village_count >= max_options) { + + DBG_AI_TESTING_AI_DEFAULT << "Too many units " << unit_count << " and villages " + << village_count<<" found, evaluate only the first " + << max_options << " options;\n"; + + std::vector perm (max_options, 0); + for(size_t i =0; i < max_options; ++i) { + perm[i] = i; + } + while(std::next_permutation(perm.begin(), perm.end())) { + + // Get result for current permutation. + std::vector > result; + for(size_t u = 0; u < max_options; ++u) { + if(matrix[u][perm[u]]) { + result.push_back(std::make_pair(villages[perm[u]], units[u])); + + } + } + if(result.size() == max_result) { + best_result.swap(result); + break; + } + + if(result.size() > best_result.size()) { + best_result.swap(result); + } + } + // End of loop no optimal found, assign the best + std::copy(best_result.begin(), best_result.end(), std::back_inserter(moves)); + + // Clean up the reachmap for dispatched units. + for(std::vector >::const_iterator + itor = best_result.begin(); itor != best_result.end(); ++itor) { + reachmap.erase(itor->second); + } + + // Try to dispatch whatever is left + dispatch(reachmap, moves); + return; + + } else if(unit_count <= village_count) { + + DBG_AI_TESTING_AI_DEFAULT << "Unit major\n"; + + std::vector perm (unit_count, 0); + for(size_t i =0; i < unit_count; ++i) { + perm[i] = i; + } + while(std::next_permutation(perm.begin(), perm.end())) { + // Get result for current permutation. + std::vector > result; + for(size_t u = 0; u < unit_count; ++u) { + if(matrix[u][perm[u]]) { + result.push_back(std::make_pair(villages[perm[u]], units[u])); + + } + } + if(result.size() == max_result) { + std::copy(result.begin(), result.end(), std::back_inserter(moves)); + reachmap.clear(); + return; + } + + if(result.size() > best_result.size()) { + best_result.swap(result); + } + } + // End of loop no optimal found, assign the best + std::copy(best_result.begin(), best_result.end(), std::back_inserter(moves)); + + // clean up the reachmap we need to test whether the leader is still there + // and if so remove him manually to get him dispatched. + for(std::vector >::const_iterator + itor = best_result.begin(); itor != best_result.end(); ++itor) { + reachmap.erase(itor->second); + } + treachmap::iterator unit = reachmap.find(leader_loc_); + if(unit != reachmap.end()) { + unit->second.clear(); + remove_unit(reachmap, moves, unit); + } + reachmap.clear(); + + } else { + + DBG_AI_TESTING_AI_DEFAULT << "Village major\n"; + + std::vector perm (village_count, 0); + for(size_t i =0; i < village_count; ++i) { + perm[i] = i; + } + while(std::next_permutation(perm.begin(), perm.end())) { + // Get result for current permutation. + std::vector > result; + for(size_t v = 0; v < village_count; ++v) { + if(matrix[perm[v]][v]) { + result.push_back(std::make_pair(villages[v], units[perm[v]])); + + } + } + if(result.size() == max_result) { + std::copy(result.begin(), result.end(), std::back_inserter(moves)); + reachmap.clear(); + return; + } + + if(result.size() > best_result.size()) { + best_result.swap(result); + } + } + // End of loop no optimal found, assigne the best + std::copy(best_result.begin(), best_result.end(), std::back_inserter(moves)); + + // clean up the reachmap we need to test whether the leader is still there + // and if so remove him manually to get him dispatched. + for(std::vector >::const_iterator + itor = best_result.begin(); itor != best_result.end(); ++itor) { + reachmap.erase(itor->second); + } + treachmap::iterator unit = reachmap.find(leader_loc_); + if(unit != reachmap.end()) { + unit->second.clear(); + remove_unit(reachmap, moves, unit); + } + reachmap.clear(); + } +} + +void get_villages_phase::full_dispatch(treachmap& reachmap, tmoves& moves) +{ + treachmap::const_iterator itor = reachmap.begin(); + for(size_t i = 0; i < reachmap.size(); ++i, ++itor) { + DBG_AI_TESTING_AI_DEFAULT << "Dispatched unit at " << itor->first + << " to village " << itor->second[i] << '\n'; + moves.push_back(std::make_pair(itor->second[i], itor->first)); + } +} + +void get_villages_phase::dump_reachmap(treachmap& reachmap) +{ + if(!debug_) { + return; + } + + for(treachmap::const_iterator itor = + reachmap.begin(); itor != reachmap.end(); ++itor) { + + std::cerr << "Reachlist for unit at " << itor->first; + + if(itor->second.empty()) { + std::cerr << "\tNone"; + } + + for(std::vector::const_iterator + v_itor = itor->second.begin(); + v_itor != itor->second.end(); ++v_itor) { + + std::cerr << '\t' << *v_itor; + } + std::cerr << '\n'; + + } } //============================================================== diff --git a/src/ai/testing/ca.hpp b/src/ai/testing/ca.hpp index eca0fc230bd..ee9dccdd1e1 100644 --- a/src/ai/testing/ca.hpp +++ b/src/ai/testing/ca.hpp @@ -114,6 +114,83 @@ public: virtual double evaluate(); virtual bool execute(); +private: + /** Location of the keep the closest to our leader. */ + map_location keep_loc_; + + /** Locaton of our leader. */ + map_location leader_loc_; + + /** The best possible location for our leader if it can't reach a village. */ + map_location best_leader_loc_; + + /** debug log level for AI enabled? */ + bool debug_; + + typedef std::map > treachmap; + + typedef std::vector > tmoves; + + + // The list of moves we want to make + tmoves moves_; + + + /** Dispatches all units to their best location. */ + void dispatch(treachmap& reachmap, tmoves& moves); + + + /** + * Dispatches all units who can reach one village. + * Returns true if it modified reachmap isn't empty. + */ + bool dispatch_unit_simple(treachmap& reachmap, tmoves& moves); + + + /* + * Dispatches units to villages which can only be reached by one unit. + * Returns true if modified reachmap and reachmap isn't empty. + */ + bool dispatch_village_simple( + treachmap& reachmap, tmoves& moves, size_t& village_count); + + + /** Removes a village for all units, returns true if anything is deleted. */ + bool remove_village( + treachmap& reachmap, tmoves& moves, const map_location& village); + + + /** Removes a unit which can't reach any village anymore. */ + treachmap::iterator remove_unit( + treachmap& reachmap, tmoves& moves, treachmap::iterator unit); + + + /** Dispatches the units to a village after the simple dispatching failed. */ + void dispatch_complex( + treachmap& reachmap, tmoves& moves, const size_t village_count); + + + /** Dispatches all units to a village, every unit can reach every village. */ + void full_dispatch(treachmap& reachmap, tmoves& moves); + + + /** Shows which villages every unit can reach (debug function). */ + void dump_reachmap(treachmap& reachmap); + + + bool get_villages(const moves_map &possible_moves, + const move_map &dstsrc, const move_map &enemy_dstsrc, + unit_map::const_iterator &leader); + + + void find_villages( + treachmap& reachmap, + tmoves& moves, + const std::multimap& dstsrc, + const std::map& possible_moves, + const std::multimap& enemy_dstsrc); };