diff --git a/AI/GeniusAI/CGeniusAI.cpp b/AI/GeniusAI/CGeniusAI.cpp index cd158c7a4..52fbf2057 100644 --- a/AI/GeniusAI/CGeniusAI.cpp +++ b/AI/GeniusAI/CGeniusAI.cpp @@ -107,7 +107,7 @@ void CGeniusAI::battleStackAttacked(BattleStackAttacked * bsa) void CGeniusAI::battleStart(CCreatureSet *army1, CCreatureSet *army2, int3 tile, CGHeroInstance *hero1, CGHeroInstance *hero2, bool side) { assert(!m_battleLogic); - m_battleLogic = new CBattleLogic(m_cb); + m_battleLogic = new CBattleLogic(m_cb, army1, army2, tile, hero1, hero2, side); assert(m_battleLogic); MsgBox("** CGeniusAI::battleStart **"); @@ -234,7 +234,7 @@ CBattleHelper::CBattleHelper(): f.open("AI\\CBattleHelper.txt", std::ios::in); if (f) { - char c_line[512]; + //char c_line[512]; std::string line; while (std::getline(f, line, '\n')) { @@ -317,16 +317,19 @@ int CBattleHelper::GetBattleFieldPosition(int x, int y) int CBattleHelper::DecodeXPosition(int battleFieldPosition) { - return battleFieldPosition - (DecodeYPosition(battleFieldPosition) - 1) * 17; + int pos = battleFieldPosition - (DecodeYPosition(battleFieldPosition) - 1) * 17; + assert(pos > 0 && pos < 16); + return pos; } int CBattleHelper::DecodeYPosition(int battleFieldPosition) { - double y = battleFieldPosition / 17; + double y = (double)battleFieldPosition / 17.0; if (y - (int)y > 0.0) { return (int)y + 1; } + assert((int)y > 0 && (int)y <= 11); return (int)y; } @@ -358,10 +361,16 @@ int CBattleHelper::GetDistanceWithObstacles(int pointA, int pointB) * Implementation of CBattleLogic class. */ -CBattleLogic::CBattleLogic(ICallback *cb) : +CBattleLogic::CBattleLogic(ICallback *cb, CCreatureSet *army1, CCreatureSet *army2, int3 tile, CGHeroInstance *hero1, CGHeroInstance *hero2, bool side) : m_cb(cb), m_iCurrentTurn(-2), - m_bIsAttacker(false) + m_bIsAttacker(false), + m_army1(army1), + m_army2(army2), + m_tile(tile), + m_hero1(hero1), + m_hero2(hero2), + m_side(side) { const int max_enemy_creatures = 12; m_statMaxDamage.reserve(max_enemy_creatures); @@ -417,22 +426,81 @@ void CBattleLogic::MakeStatistics(int currentCreatureId) m_statDistance.clear(); m_statDistance.clear(); + m_statCasualties.clear(); + + int totalEnemyDamage = 0; + int totalEnemyHitPoints = 0; + + int totalDamage = 0; + int totalHitPoints = 0; + for (map_stacks::const_iterator it = allStacks.begin(); it != allStacks.end(); ++it) { - if (it->second.attackerOwned != m_bIsAttacker) + const CStack *st = &it->second; + + if ((it->second.attackerOwned != 0) != m_bIsAttacker) { int id = it->first; - const CStack *st = &it->second; if (st->amount < 1) { continue; } // make stats + int hitPoints = st->amount * st->creature->hitPoints - (st->creature->hitPoints - st->firstHPleft); + m_statMaxDamage.push_back(std::pair(id, st->creature->damageMax * st->amount)); m_statMinDamage.push_back(std::pair(id, st->creature->damageMin * st->amount)); - m_statHitPoints.push_back(std::pair(id, st->creature->hitPoints * st->amount)); + m_statHitPoints.push_back(std::pair(id, hitPoints)); m_statMaxSpeed.push_back(std::pair(id, st->creature->speed)); + totalEnemyDamage += (st->creature->damageMax + st->creature->damageMin) * st->amount / 2; + totalEnemyHitPoints += hitPoints; + + // calculate casualties + SCreatureCasualties cs; + // hp * amount - damage * ( (att - def)>=0 ) + // hit poionts + assert(hitPoints >= 0 && "CGeniusAI - creature cannot have hit points less than zero"); + + CGHeroInstance *attackerHero = (m_side)? m_hero1 : m_hero2; + CGHeroInstance *defendingHero = (m_side)? m_hero2 : m_hero1; + + int attackDefenseBonus = currentStack->creature->attack + (attackerHero ? attackerHero->getPrimSkillLevel(0) : 0) - (st->creature->defence + (defendingHero ? defendingHero->getPrimSkillLevel(1) : 0)); + float damageFactor = 1.0f; + if(attackDefenseBonus < 0) //decreasing dmg + { + if(0.02f * (-attackDefenseBonus) > 0.3f) + { + damageFactor += -0.3f; + } + else + { + damageFactor += 0.02f * attackDefenseBonus; + } + } + else //increasing dmg + { + if(0.05f * attackDefenseBonus > 4.0f) + { + damageFactor += 4.0f; + } + else + { + damageFactor += 0.05f * attackDefenseBonus; + } + } + + cs.damage_max = (int)(currentStack->creature->damageMax * currentStack->amount * damageFactor); + cs.damage_min = (int)(currentStack->creature->damageMin * currentStack->amount * damageFactor); + + cs.amount_max = cs.damage_max / st->creature->hitPoints; + cs.amount_min = cs.damage_min / st->creature->hitPoints; + + cs.leftHitPoints_for_max = (hitPoints - cs.damage_max) % st->creature->hitPoints; + cs.leftHitPoint_for_min = (hitPoints - cs.damage_min) % st->creature->hitPoints; + + m_statCasualties.push_back(std::pair(id, cs)); + if (st->creature->isShooting() && st->shots > 0) { m_statDistanceFromShooters.push_back(std::pair(id, m_battleHelper.GetShortestDistance(currentStack->position, st->position))); @@ -447,6 +515,27 @@ void CBattleLogic::MakeStatistics(int currentCreatureId) m_statDistance.push_back(std::pair(id, m_battleHelper.GetDistanceWithObstacles(currentStack->position, st->position))); } } + else + { + if (st->amount < 1) + { + continue; + } + int hitPoints = st->amount * st->creature->hitPoints - (st->creature->hitPoints - st->firstHPleft); + + totalDamage += (st->creature->damageMax + st->creature->damageMin) * st->amount / 2; + totalHitPoints += hitPoints; + } + } + if ((float)totalDamage / (float)totalEnemyDamage < 0.5f && + (float)totalHitPoints / (float)totalEnemyHitPoints < 0.5f) + { + m_bEnemyDominates = true; + MsgBox("** EnemyDominates!"); + } + else + { + m_bEnemyDominates = false; } // sort max damage std::sort(m_statMaxDamage.begin(), m_statMaxDamage.end(), @@ -466,45 +555,30 @@ void CBattleLogic::MakeStatistics(int currentCreatureId) // sort hit points std::sort(m_statHitPoints.begin(), m_statHitPoints.end(), bind(&creature_stat::value_type::second, _1) > bind(&creature_stat::value_type::second, _2)); + // sort casualties + std::sort(m_statCasualties.begin(), m_statCasualties.end(), + bind(&creature_stat_casualties::value_type::second_type::damage_max, bind(&creature_stat_casualties::value_type::second, _1)) + > + bind(&creature_stat_casualties::value_type::second_type::damage_max, bind(&creature_stat_casualties::value_type::second, _2))); } BattleAction CBattleLogic::MakeDecision(int stackID) { MakeStatistics(stackID); - // first approach based on the statistics and weights - // if this solution was fine we would develop this idea + int creature_to_attack = -1; - // - std::map votes; - - for (creature_stat::iterator it = m_statMaxDamage.begin(); it != m_statMaxDamage.end(); ++it) + + if (m_bEnemyDominates) { - votes[it->first] = 0; + creature_to_attack = PerformBerserkAttack(stackID); } - - votes[m_statMaxDamage.begin()->first] += m_battleHelper.GetVoteForMaxDamage(); - votes[m_statMinDamage.begin()->first] += m_battleHelper.GetVoteForMinDamage(); - if (m_statDistanceFromShooters.size()) + else { - votes[m_statDistanceFromShooters.begin()->first] += m_battleHelper.GetVoteForDistanceFromShooters(); - } - votes[m_statDistance.begin()->first] += m_battleHelper.GetVoteForDistance(); - votes[m_statHitPoints.begin()->first] += m_battleHelper.GetVoteForHitPoints(); - votes[m_statMaxSpeed.begin()->first] += m_battleHelper.GetVoteForMaxSpeed(); - - - // get creature to attack - - int max_vote = 0; - - for (std::map::iterator it = votes.begin(); it != votes.end(); ++it) - { - if (it->second > max_vote) - { - max_vote = it->second; - creature_to_attack = it->first; - } + creature_to_attack = PerformDefaultAction(stackID); } + std::string message("Creature will be attacked - "); + message += boost::lexical_cast(creature_to_attack); + MsgBox(message.c_str()); if (creature_to_attack == -1) { @@ -518,14 +592,14 @@ BattleAction CBattleLogic::MakeDecision(int stackID) ba.side = 1; ba.actionType = action_shoot; // shoot ba.stackNumber = stackID; - ba.destinationTile = m_cb->battleGetPos(creature_to_attack); + ba.destinationTile = (ui16)m_cb->battleGetPos(creature_to_attack); return ba; } else { // go or go&attack int dest_tile = -1; //m_cb->battleGetPos(creature_to_attack) + 1; - std::vector av_tiles = GetAvailableHexesForAttacker(m_cb->battleGetStackByID(creature_to_attack)); + std::vector av_tiles = GetAvailableHexesForAttacker(m_cb->battleGetStackByID(creature_to_attack), m_cb->battleGetStackByID(stackID)); if (av_tiles.size() < 1) { // TODO: shouldn't be like that @@ -535,6 +609,7 @@ BattleAction CBattleLogic::MakeDecision(int stackID) // get the best tile - now the nearest int prev_distance = m_battleHelper.InfiniteDistance; + int currentPos = m_cb->battleGetPos(stackID); for (std::vector::iterator it = av_tiles.begin(); it != av_tiles.end(); ++it) { @@ -544,15 +619,19 @@ BattleAction CBattleLogic::MakeDecision(int stackID) prev_distance = dist; dest_tile = *it; } + if (*it == currentPos) + { + dest_tile = currentPos; + break; + } } std::vector fields = m_cb->battleGetAvailableHexes(stackID); - BattleAction ba; ba.side = 1; //ba.actionType = 6; // go and attack ba.stackNumber = stackID; - ba.destinationTile = dest_tile; + ba.destinationTile = (ui16)dest_tile; ba.additionalInfo = m_cb->battleGetPos(creature_to_attack); int nearest_dist = m_battleHelper.InfiniteDistance; @@ -564,6 +643,9 @@ BattleAction CBattleLogic::MakeDecision(int stackID) { // attack! ba.actionType = action_walk_and_attack; +#if defined _DEBUG + PrintBattleAction(ba); +#endif return ba; } int d = m_battleHelper.GetDistanceWithObstacles(dest_tile, *it); @@ -573,14 +655,22 @@ BattleAction CBattleLogic::MakeDecision(int stackID) nearest_pos = *it; } } + message = "Attacker position X="; + message += boost::lexical_cast(m_battleHelper.DecodeXPosition(nearest_pos)) + ", Y="; + message += boost::lexical_cast(m_battleHelper.DecodeYPosition(nearest_pos)); + MsgBox(message.c_str()); + ba.actionType = action_walk; - ba.destinationTile = nearest_pos; + ba.destinationTile = (ui16)nearest_pos; ba.additionalInfo = -1; +#if defined _DEBUG + PrintBattleAction(ba); +#endif return ba; } } -std::vector CBattleLogic::GetAvailableHexesForAttacker(CStack *defender) +std::vector CBattleLogic::GetAvailableHexesForAttacker(CStack *defender, CStack *attacker) { int x = m_battleHelper.DecodeXPosition(defender->position); int y = m_battleHelper.DecodeYPosition(defender->position); @@ -635,7 +725,15 @@ std::vector CBattleLogic::GetAvailableHexesForAttacker(CStack *defender) { candidates.push_back(std::pair(x + 1, y - 1)); } - // check if these field are empty + if (!leftLimit) + { + candidates.push_back(std::pair(x - 1, y)); + } + if (!rightLimit) + { + candidates.push_back(std::pair(x + 1, y)); + } + // check if these fields are empty for (std::vector >::iterator it = candidates.begin(); it != candidates.end(); ++it) { int new_pos = m_battleHelper.GetBattleFieldPosition(it->first, it->second); @@ -645,6 +743,13 @@ std::vector CBattleLogic::GetAvailableHexesForAttacker(CStack *defender) { fields.push_back(new_pos); } + else if (attacker) + { + if (attacker->ID == st->ID) + { + fields.push_back(new_pos); + } + } } return fields; } @@ -657,4 +762,152 @@ BattleAction CBattleLogic::MakeDefend(int stackID) ba.stackNumber = stackID; ba.additionalInfo = -1; return ba; +} + +int CBattleLogic::PerformBerserkAttack(int stackID) +{ + CCreature c = m_cb->battleGetCreature(stackID); + // attack to make biggest damage + int creature_to_attack = -1; + //if (m_statDistance.size() >= 2) + //{ + // creature_stat::const_iterator it = m_statDistance.begin(); + // creature_stat::const_iterator it2 = it + 1; + // if (it->second < + if (!m_statCasualties.empty()) + { + creature_to_attack = m_statCasualties.begin()->first; + creature_stat_casualties::iterator it = m_statCasualties.begin(); + for (; it != m_statCasualties.end(); ++it) + { + if (it->second.amount_min <= 0) // if nobody die after attack it won't make any sense + { + continue; + } + for (creature_stat::const_iterator it2 = m_statDistance.begin(); it2 != m_statDistance.end(); ++it2) + { + if (it2->first == it->first && it2->second - 1 <= c.speed) + { + return it->first; + } + } + } + return m_statCasualties.begin()->first; + } + return -1; +} + +int CBattleLogic::PerformDefaultAction(int stackID) +{ + // first approach based on the statistics and weights + // if this solution was fine we would develop this idea + int creature_to_attack = -1; + // + std::map votes; + + for (creature_stat::iterator it = m_statMaxDamage.begin(); it != m_statMaxDamage.end(); ++it) + { + votes[it->first] = 0; + } + + votes[m_statMaxDamage.begin()->first] += m_battleHelper.GetVoteForMaxDamage(); + votes[m_statMinDamage.begin()->first] += m_battleHelper.GetVoteForMinDamage(); + if (m_statDistanceFromShooters.size()) + { + votes[m_statDistanceFromShooters.begin()->first] += m_battleHelper.GetVoteForDistanceFromShooters(); + } + votes[m_statDistance.begin()->first] += m_battleHelper.GetVoteForDistance(); + votes[m_statHitPoints.begin()->first] += m_battleHelper.GetVoteForHitPoints(); + votes[m_statMaxSpeed.begin()->first] += m_battleHelper.GetVoteForMaxSpeed(); + + + // get creature to attack + + int max_vote = 0; + + for (std::map::iterator it = votes.begin(); it != votes.end(); ++it) + { + if (it->second > max_vote) + { + max_vote = it->second; + creature_to_attack = it->first; + } + } + return creature_to_attack; +} + +void CBattleLogic::PrintBattleAction(const BattleAction &action) // for debug purpose +{ + std::string message("Battle action \n"); + message += "\taction type - "; + switch (action.actionType) + { + case 0: + message += "Cancel BattleAction\n"; + break; + case 1: + message += "Hero cast a spell\n"; + break; + case 2: + message += "Walk\n"; + break; + case 3: + message += "Defend\n"; + break; + case 4: + message += "Retreat from the battle\n"; + break; + case 5: + message += "Surrender\n"; + break; + case 6: + message += "Walk and Attack\n"; + break; + case 7: + message += "Shoot\n"; + break; + case 8: + message += "Wait\n"; + break; + case 9: + message += "Catapult\n"; + break; + case 10: + message += "Monster casts a spell\n"; + break; + } + message += "\tDestination tile: X = "; + message += boost::lexical_cast(m_battleHelper.DecodeXPosition(action.destinationTile)); + message += ", Y = " + boost::lexical_cast(m_battleHelper.DecodeYPosition(action.destinationTile)); + message += "\nAdditional info: "; + if (action.actionType == 6 || action.actionType == 7) + { + message += "stack - " + boost::lexical_cast(m_battleHelper.DecodeXPosition(action.additionalInfo)); + message += ", " + boost::lexical_cast(m_battleHelper.DecodeYPosition(action.additionalInfo)); + message += ", creature - "; + CStack *c = m_cb->battleGetStackByPos(action.additionalInfo); + if (c && c->creature) + { + message += c->creature->nameRef; + } + else + { + message += "NULL"; + } + } + else + { + message += boost::lexical_cast(action.additionalInfo); + } + + HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + + CONSOLE_SCREEN_BUFFER_INFO csbi; + GetConsoleScreenBufferInfo(hConsole, &csbi); + + SetConsoleTextAttribute(hConsole, FOREGROUND_GREEN | FOREGROUND_INTENSITY); + + std::cout << message.c_str() << std::flush; + + SetConsoleTextAttribute(hConsole, csbi.wAttributes); } \ No newline at end of file diff --git a/AI/GeniusAI/CGeniusAI.h b/AI/GeniusAI/CGeniusAI.h index dfc77d320..5cab41fe2 100644 --- a/AI/GeniusAI/CGeniusAI.h +++ b/AI/GeniusAI/CGeniusAI.h @@ -4,12 +4,16 @@ #define VCMI_DLL #pragma warning (disable: 4100 4251 4245 4018 4081) +#include "../../global.h" #include "../../AI_Base.h" #include "../../CCallback.h" #include "../../hch/CCreatureHandler.h" +#include "../../hch/CObjectHandler.h" #pragma warning (default: 4100 4251 4245 4018 4081) +#pragma warning (disable: 4100) + namespace GeniusAI { class CBattleHelper @@ -42,6 +46,9 @@ private: int m_voteForDistance; int m_voteForDistanceFromShooters; int m_voteForHitPoints; + + CBattleHelper(const CBattleHelper &); + CBattleHelper &operator=(const CBattleHelper &); }; /** @@ -57,7 +64,7 @@ private: strategy_neutral, strategy_defensive, strategy_super_defensive, - strategy_stupid_attack /** only for debug and test purpose */ + strategy_berserk_attack /** cause max damage, usually when creatures fight against overwhelming army*/ }; enum ECreatureRoleInBattle { @@ -80,8 +87,17 @@ private: action_catapult = 9, // Catapult action_monster_casts_spell = 10 // Monster casts a spell (i.e. Faerie Dragons) }; + struct SCreatureCasualties + { + int amount_max; // amount of creatures that will be dead + int amount_min; + int damage_max; // number of hit points that creature will lost + int damage_min; + int leftHitPoints_for_max; // number of hit points that will remain (for last unity) + int leftHitPoint_for_min; // scenario + }; public: - CBattleLogic(ICallback *cb); + CBattleLogic(ICallback *cb, CCreatureSet *army1, CCreatureSet *army2, int3 tile, CGHeroInstance *hero1, CGHeroInstance *hero2, bool side); ~CBattleLogic(); void SetCurrentTurn(int turn); @@ -89,9 +105,16 @@ public: BattleAction MakeDecision(int stackID); private: CBattleHelper m_battleHelper; + //BattleInfo m_battleInfo; int m_iCurrentTurn; bool m_bIsAttacker; ICallback *m_cb; + CCreatureSet *m_army1; + CCreatureSet *m_army2; + int3 m_tile; + CGHeroInstance *m_hero1; + CGHeroInstance *m_hero2; + bool m_side; void MakeStatistics(int currentCreatureId); // before decision we have to make some calculation and simulation // statistics @@ -104,9 +127,19 @@ private: creature_stat m_statDistanceFromShooters; creature_stat m_statHitPoints; - std::vector GetAvailableHexesForAttacker(CStack *defender); + typedef std::vector > creature_stat_casualties; + creature_stat_casualties m_statCasualties; + + bool m_bEnemyDominates; + + std::vector GetAvailableHexesForAttacker(CStack *defender, CStack *attacker = NULL); BattleAction MakeDefend(int stackID); + + int PerformBerserkAttack(int stackID); // return ID of creature to attack, -2 means wait, -1 - defend + int PerformDefaultAction(int stackID); + + void CBattleLogic::PrintBattleAction(const BattleAction &action); }; class CGeniusAI : public CGlobalAI