Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- From 00503c71cfcd69704b8a1eee64bb7d37749e9398 Mon Sep 17 00:00:00 2001
- From: PL_kolek <mateusz.kolaczek@gmail.com>
- Date: Wed, 22 May 2013 11:15:40 +0200
- Subject: [PATCH] Prototype for estimating enemy response.
- ---
- data/ai/ais/ai_test.cfg | 32 ++++
- data/core/macros/ai_candidate_actions.cfg | 10 ++
- data/multiplayer/scenarios/defensive_test.cfg | 62 ++++++++
- src/CMakeLists.txt | 2 +
- src/ai/contexts.cpp | 3 +
- src/ai/contexts.hpp | 1 +
- src/ai/default/attack.cpp | 212 +++++++++++++++++++++++++-
- src/ai/default/contexts.hpp | 11 +-
- src/ai/registry.cpp | 14 ++
- 9 files changed, 345 insertions(+), 2 deletions(-)
- create mode 100644 data/ai/ais/ai_test.cfg
- create mode 100644 data/multiplayer/scenarios/defensive_test.cfg
- diff --git a/data/ai/ais/ai_test.cfg b/data/ai/ais/ai_test.cfg
- new file mode 100644
- index 0000000..fc01f6a
- --- /dev/null
- +++ b/data/ai/ais/ai_test.cfg
- @@ -0,0 +1,32 @@
- +{core/macros}
- +#textdomain wesnoth
- +[ai]
- + id=ai_test
- + description=_"Multiplayer_AI^Test test"
- + version=10710
- + [aspect]
- + id=attacks_test
- + engine=cpp
- + name=composite_aspect
- + invalidate_on_gamestate_change=yes
- + [default]
- + engine=cpp
- + name=ai_default_rca::aspect_attacks_test
- + invalidate_on_gamestate_change=yes
- + [/default]
- + [/aspect]
- + [stage]
- + id=main_loop
- + name=ai_default_rca::candidate_action_evaluation_loop
- + {AI_CA_GOTO}
- + {AI_CA_RECRUITMENT}
- + {AI_CA_MOVE_LEADER_TO_GOALS}
- + {AI_CA_MOVE_LEADER_TO_KEEP}
- + {AI_CA_COMBAT_TEST}
- + {AI_CA_HEALING}
- + {AI_CA_VILLAGES}
- + {AI_CA_RETREAT}
- + {AI_CA_MOVE_TO_TARGETS}
- + {AI_CA_LEADER_SHARES_KEEP}
- + [/stage]
- +[/ai]
- diff --git a/data/core/macros/ai_candidate_actions.cfg b/data/core/macros/ai_candidate_actions.cfg
- index 4c625ef..e9b7a32 100644
- --- a/data/core/macros/ai_candidate_actions.cfg
- +++ b/data/core/macros/ai_candidate_actions.cfg
- @@ -99,6 +99,16 @@
- [/candidate_action]
- #enddef
- +#define AI_CA_COMBAT_TEST
- + [candidate_action]
- + id=combat_test
- + engine=cpp
- + name=ai_default_rca::combat_phase_test
- + max_score={AI_CA_COMBAT_SCORE}
- + score={AI_CA_COMBAT_SCORE}
- + [/candidate_action]
- +#enddef
- +
- #define AI_CA_HEALING
- [candidate_action]
- id=healing
- diff --git a/data/multiplayer/scenarios/defensive_test.cfg b/data/multiplayer/scenarios/defensive_test.cfg
- new file mode 100644
- index 0000000..159a9e3
- --- /dev/null
- +++ b/data/multiplayer/scenarios/defensive_test.cfg
- @@ -0,0 +1,62 @@
- +[multiplayer]
- + id=multiplayer_defensive_test
- + name= _ "Defensive AI Test Map"
- + map_data= "border_size=1
- +usage=map
- +
- +Gg, Gg, Gg, Gg, Ql, Gg, Ql, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
- +Gg, 1 Gg, Ql, Ql, Ql, Ql, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
- +Gg, Ql, Ql, Ql, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
- +Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
- +Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
- +Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
- +Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
- +Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gs^Vh, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
- +Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
- +Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
- +Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
- +Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
- +Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg
- +Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Ql
- +Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Ql, Ql, Gg
- +Gg, Gg, Gg, Gg, Gg, Gg, Gg, Ms, Gg, Gg, Gg, Gg, Ql, Ql, 2 Gg, Gg, Gg
- +Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Ql, Gg, Gg, Gg, Gg, Gg
- +"
- +
- + {DEFAULT_SCHEDULE}
- + [side]
- + side=1
- + canrecruit=yes
- + controller=human
- + id=tested
- + [unit]
- + id=Defender
- + type=Dwarvish Guardsman
- + x=9
- + y=15
- + side=1
- + [/unit]
- + [unit]
- + id=Defended
- + type=Dwarvish Lord
- + x=7
- + y=7
- + side=1
- + hitpoints=1
- + [/unit]
- + [/side]
- + [side]
- + side=2
- + canrecruit=yes
- + controller=human
- + id=tester
- + [unit]
- + id=Defender
- + type=Cavalryman
- + x=7
- + y=14
- + side=2
- + [/unit]
- + [/side]
- +
- +[/multiplayer]
- diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
- index 4db9a6f..37775d6 100644
- --- a/src/CMakeLists.txt
- +++ b/src/CMakeLists.txt
- @@ -633,7 +633,9 @@ set(wesnoth-main_SRC
- ai/registry.cpp
- ai/testing.cpp
- ai/testing/aspect_attacks.cpp
- + ai/testing/aspect_attacks_test.cpp
- ai/testing/ca.cpp
- + ai/testing/ca_combat_test.cpp
- ai/testing/ca_global_fallback.cpp
- ai/testing/ca_testing_move_to_targets.cpp
- ai/testing/ca_testing_recruitment.cpp
- diff --git a/src/ai/contexts.cpp b/src/ai/contexts.cpp
- index 13291ba..10d289c 100644
- --- a/src/ai/contexts.cpp
- +++ b/src/ai/contexts.cpp
- @@ -198,6 +198,7 @@ readonly_context_impl::readonly_context_impl(side_context &context, const config
- add_known_aspect("aggression",aggression_);
- add_known_aspect("attack_depth",attack_depth_);
- add_known_aspect("attacks",attacks_);
- + add_known_aspect("attacks_test",attacks_test_);
- add_known_aspect("avoid",avoid_);
- add_known_aspect("caution",caution_);
- add_known_aspect("grouping",grouping_);
- @@ -523,6 +524,8 @@ aspect_map& readonly_context_impl::get_aspects()
- const attacks_vector& readonly_context_impl::get_attacks() const
- {
- + if(attacks_test_)
- + return attacks_test_->get();
- if (attacks_) {
- return attacks_->get();
- }
- diff --git a/src/ai/contexts.hpp b/src/ai/contexts.hpp
- index 70f33da..23738f5 100644
- --- a/src/ai/contexts.hpp
- +++ b/src/ai/contexts.hpp
- @@ -1453,6 +1453,7 @@ private:
- aspect_type<int>::typesafe_ptr attack_depth_;
- aspect_map aspects_;
- aspect_type< attacks_vector >::typesafe_ptr attacks_;
- + aspect_type< attacks_vector >::typesafe_ptr attacks_test_;
- mutable aspect_type<terrain_filter>::typesafe_ptr avoid_;
- aspect_type<double>::typesafe_ptr caution_;
- mutable std::map<map_location,defensive_position> defensive_position_cache_;
- diff --git a/src/ai/default/attack.cpp b/src/ai/default/attack.cpp
- index 8a11ce7..7225914 100644
- --- a/src/ai/default/attack.cpp
- +++ b/src/ai/default/attack.cpp
- @@ -246,6 +246,189 @@ void attack_analysis::analyze(const gamemap& map, unit_map& units,
- }
- }
- +/** Used for next turn losses prediction */
- +void attack_analysis::analyze_offensive(const gamemap& map, unit_map& units,
- + const readonly_context& ai_obj)
- +{
- + const unit_map::const_iterator defend_it = units.find(target);
- + assert(defend_it != units.end());
- +
- + leader_threat = false;
- + uses_leader = false;
- +
- + target_value = defend_it->cost();
- + target_value += (double(defend_it->experience())/
- + double(defend_it->max_experience()))*target_value;
- + target_starting_damage = defend_it->max_hitpoints() -
- + defend_it->hitpoints();
- +
- + alternative_terrain_quality = 0.0;
- +
- + avg_damage_inflicted = 0.0;
- + avg_damage_taken = 0.0;
- + resources_used = 0.0;
- + terrain_quality = 0.0;
- + avg_losses = 0.0;
- + chance_to_kill = 0.0;
- +
- + double def_avg_experience = 0.0;
- + double first_chance_kill = 0.0;
- +
- + double prob_dead_already = 0.0;
- + assert(!movements.empty());
- + std::vector<std::pair<map_location,map_location> >::const_iterator m;
- +
- + battle_context *prev_bc = NULL;
- + const combatant *prev_def = NULL;
- +
- + for (m = movements.begin(); m != movements.end(); ++m) {
- + // We fix up units map to reflect what this would look like.
- + unit *up = units.extract(m->first);
- + up->set_location(m->second);
- + units.insert(up);
- + double m_aggression = 1;
- +
- + bool from_cache = false;
- + battle_context *bc;
- +
- + // This cache is only about 99% correct, but speeds up evaluation by about 1000 times.
- + // We recalculate when we actually attack.
- + const readonly_context::unit_stats_cache_t::key_type cache_key = std::make_pair(target, &up->type());
- + const readonly_context::unit_stats_cache_t::iterator usc = ai_obj.unit_stats_cache().find(cache_key);
- + // Just check this attack is valid for this attacking unit (may be modified)
- + if (usc != ai_obj.unit_stats_cache().end() &&
- + usc->second.first.attack_num <
- + static_cast<int>(up->attacks().size())) {
- +
- + from_cache = true;
- + bc = new battle_context(usc->second.first, usc->second.second);
- + } else {
- + bc = new battle_context(units, m->second, target, -1, -1, m_aggression, prev_def);
- + }
- + const combatant &att = bc->get_attacker_combatant(prev_def);
- + const combatant &def = bc->get_defender_combatant(prev_def);
- +
- + delete prev_bc;
- + prev_bc = bc;
- + prev_def = &bc->get_defender_combatant(prev_def);
- +
- + if ( !from_cache ) {
- + ai_obj.unit_stats_cache().insert(
- + std::make_pair(cache_key, std::make_pair(bc->get_attacker_stats(),
- + bc->get_defender_stats())));
- + }
- +
- + // Note we didn't fight at all if defender is already dead.
- + double prob_fought = (1.0 - prob_dead_already);
- +
- + /** @todo 1.9 add combatant.prob_killed */
- + double prob_killed = def.hp_dist[0] - prob_dead_already;
- + prob_dead_already = def.hp_dist[0];
- +
- + double prob_died = att.hp_dist[0];
- + double prob_survived = (1.0 - prob_died) * prob_fought;
- +
- + double cost = up->cost();
- + const bool on_village = map.is_village(m->second);
- + // Up to double the value of a unit based on experience
- + cost += (double(up->experience()) / up->max_experience())*cost;
- + resources_used += cost;
- + avg_losses += cost * prob_died;
- +
- + // add half of cost for poisoned unit so it might get chance to heal
- + avg_losses += cost * up->get_state(unit::STATE_POISONED) /2;
- +
- + if (!bc->get_defender_stats().is_poisoned) {
- + avg_damage_inflicted += game_config::poison_amount * 2 * bc->get_defender_combatant().poisoned * (1 - prob_killed);
- + }
- +
- + // Double reward to emphasize getting onto villages if they survive.
- + if (on_village) {
- + avg_damage_taken -= game_config::poison_amount*2 * prob_survived;
- + }
- +
- + terrain_quality += (double(bc->get_defender_stats().chance_to_hit)/100.0)*cost * (on_village ? 0.5 : 1.0);
- +
- + double advance_prob = 0.0;
- + // The reward for advancing a unit is to get a 'negative' loss of that unit
- + if (!up->advances_to().empty()) {
- + int xp_for_advance = up->max_experience() - up->experience();
- +
- + // See bug #6272... in some cases, unit already has got enough xp to advance,
- + // but hasn't (bug elsewhere?). Can cause divide by zero.
- + if (xp_for_advance <= 0)
- + xp_for_advance = 1;
- +
- + int fight_xp = defend_it->level();
- + int kill_xp = game_config::kill_xp(fight_xp);
- +
- + if (fight_xp >= xp_for_advance) {
- + advance_prob = prob_fought;
- + avg_losses -= up->cost() * prob_fought;
- + } else if (kill_xp >= xp_for_advance) {
- + advance_prob = prob_killed;
- + avg_losses -= up->cost() * prob_killed;
- + // The reward for getting a unit closer to advancement
- + // (if it didn't advance) is to get the proportion of
- + // remaining experience needed, and multiply it by
- + // a quarter of the unit cost.
- + // This will cause the AI to heavily favor
- + // getting xp for close-to-advance units.
- + avg_losses -= up->cost() * 0.25 *
- + fight_xp * (prob_fought - prob_killed)
- + / xp_for_advance;
- + } else {
- + avg_losses -= up->cost() * 0.25 *
- + (kill_xp * prob_killed + fight_xp * (prob_fought - prob_killed))
- + / xp_for_advance;
- + }
- +
- + // The reward for killing with a unit that plagues
- + // is to get a 'negative' loss of that unit.
- + if (bc->get_attacker_stats().plagues) {
- + avg_losses -= prob_killed * up->cost();
- + }
- + }
- +
- + // If we didn't advance, we took this damage.
- + avg_damage_taken += (up->hitpoints() - att.average_hp()) * (1.0 - advance_prob);
- +
- + /**
- + * @todo 1.9: attack_prediction.cpp should understand advancement
- + * directly. For each level of attacker def gets 1 xp or
- + * kill_experience.
- + */
- + int fight_xp = up->level();
- + int kill_xp = game_config::kill_xp(fight_xp);
- + def_avg_experience += fight_xp * (1.0 - att.hp_dist[0]) + kill_xp * att.hp_dist[0];
- + if (m == movements.begin()) {
- + first_chance_kill = def.hp_dist[0];
- + }
- + }
- +
- + if (!defend_it->advances_to().empty() &&
- + def_avg_experience >= defend_it->max_experience() - defend_it->experience()) {
- + // It's likely to advance: only if we can kill with first blow.
- + chance_to_kill = first_chance_kill;
- + // Negative average damage (it will advance).
- + avg_damage_inflicted += defend_it->hitpoints() - defend_it->max_hitpoints();
- + } else {
- + chance_to_kill = prev_def->hp_dist[0];
- + std::cerr<<"HP prev_def avg hp "<<defend_it->hitpoints()<<" "<< prev_def->average_hp()<<"\n";
- + avg_damage_inflicted += defend_it->hitpoints() - prev_def->average_hp();
- + }
- +
- + delete prev_bc;
- + terrain_quality /= resources_used;
- +
- + // Restore the units to their original positions.
- + for (m = movements.begin(); m != movements.end(); ++m) {
- + units.move(m->second, m->first);
- + }
- +}
- +
- +
- +
- bool attack_analysis::attack_close(const map_location& loc) const
- {
- std::set<map_location> &attacks = manager::get_ai_info().recent_attacks;
- @@ -269,7 +452,7 @@ double attack_analysis::rating(double aggression, const readonly_context& ai_obj
- aggression = ai_obj.get_leader_aggression();
- }
- - double value = chance_to_kill*target_value - avg_losses*(1.0-aggression);
- + double value = chance_to_kill*target_value - avg_losses*(1.0-aggression) - next_turn_losses;
- if(terrain_quality > alternative_terrain_quality) {
- // This situation looks like it might be a bad move:
- @@ -329,5 +512,32 @@ double attack_analysis::rating(double aggression, const readonly_context& ai_obj
- return value;
- }
- +/** Used for next turn losses prediction */
- +double attack_analysis::rating_offensive() const
- +{
- + double value = chance_to_kill*target_value;
- +
- + // Prefer to attack already damaged targets.
- + value += (target_starting_damage/3 + avg_damage_inflicted);
- +
- + if(vulnerability*terrain_quality > 0.0) {
- + value *= support/(vulnerability*terrain_quality);
- + }
- +
- + value /= ((resources_used/2) + (resources_used/2)*terrain_quality);
- +
- + LOG_AI << "attack on " << target << ": 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";
- +
- +
- + //std::cerr<<"Rating: "<<value<<" "<<next_turn_losses<<"\n";
- + return value;
- +}
- } //end of namespace ai
- diff --git a/src/ai/default/contexts.hpp b/src/ai/default/contexts.hpp
- index 633e499..72d762e 100644
- --- a/src/ai/default/contexts.hpp
- +++ b/src/ai/default/contexts.hpp
- @@ -66,7 +66,8 @@ public:
- support(0.0),
- leader_threat(false),
- uses_leader(false),
- - is_surrounded(false)
- + is_surrounded(false),
- + next_turn_losses(0)
- {
- }
- @@ -76,6 +77,11 @@ public:
- const move_map& enemy_dstsrc, double aggression);
- double rating(double aggression, const readonly_context& ai_obj) const;
- +
- + void analyze_offensive(const gamemap& map, unit_map& units,
- + const readonly_context& ai_obj);
- + double rating_offensive() const;
- +
- variant get_value(const std::string& key) const;
- void get_inputs(std::vector<game_logic::formula_input>* inputs) const;
- @@ -129,6 +135,9 @@ public:
- /** Is true if the units involved in this attack sequence are surrounded. */
- bool is_surrounded;
- + /** Average enemy's response stats in current situation*/
- + double next_turn_losses;
- +
- };
- diff --git a/src/ai/registry.cpp b/src/ai/registry.cpp
- index 1c6a7b8..9e5eba6 100644
- --- a/src/ai/registry.cpp
- +++ b/src/ai/registry.cpp
- @@ -27,7 +27,9 @@
- #include "formula/ai.hpp"
- #include "registry.hpp"
- #include "testing/aspect_attacks.hpp"
- +#include "testing/aspect_attacks_test.hpp"
- #include "testing/ca.hpp"
- +#include "testing/ca_combat_test.hpp"
- #include "testing/ca_testing_move_to_targets.hpp"
- #include "testing/ca_testing_recruitment.hpp"
- #include "testing/ca_global_fallback.hpp"
- @@ -95,6 +97,9 @@ static register_candidate_action_factory<testing_ai_default::recruitment_phase>
- static register_candidate_action_factory<testing_ai_default::combat_phase>
- combat_phase_factory("ai_default_rca::combat_phase");
- +static register_candidate_action_factory<testing_ai_default::combat_phase_test>
- + combat_phase_factory_test("ai_default_rca::combat_phase_test");
- +
- static register_candidate_action_factory<testing_ai_default::move_leader_to_goals_phase>
- move_leader_to_goals_phase_factory("ai_default_rca::move_leader_to_goals_phase");
- @@ -233,6 +238,9 @@ static register_aspect_factory< composite_aspect<int> >
- static register_aspect_factory< composite_aspect< attacks_vector > >
- attacks__composite_aspect_factory("attacks*composite_aspect");
- +static register_aspect_factory< composite_aspect< attacks_vector > >
- + attacks__composite_aspect_factory_test("attacks_test*composite_aspect");
- +
- static register_aspect_factory< composite_aspect< terrain_filter > >
- avoid__composite_aspect_factory("avoid*composite_aspect");
- @@ -301,6 +309,9 @@ static register_aspect_factory< standard_aspect<int> >
- static register_aspect_factory< testing_ai_default::aspect_attacks >
- attacks__testing_ai_default_aspect_attacks_factory("attacks*ai_default_rca::aspect_attacks");
- +static register_aspect_factory< testing_ai_default::aspect_attacks_test >
- + attacks__testing_ai_default_aspect_attacks_test_factory("attacks_test*ai_default_rca::aspect_attacks_test");
- +
- static register_aspect_factory< standard_aspect< terrain_filter > >
- avoid__standard_aspect_factory("avoid*standard_aspect");
- @@ -372,6 +383,9 @@ static register_aspect_factory< standard_aspect<int> >
- static register_aspect_factory< testing_ai_default::aspect_attacks >
- attacks__testing_ai_default_aspect_attacks_factory2("attacks*");
- +static register_aspect_factory< testing_ai_default::aspect_attacks_test >
- + attacks__testing_ai_default_aspect_attacks_test_factory2("attacks_test*");
- +
- static register_aspect_factory< standard_aspect< terrain_filter > >
- avoid__standard_aspect_factory2("avoid*");
- --
- 1.8.2.3
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement