diff --git a/AI/BattleAI/BattleAI.cpp b/AI/BattleAI/BattleAI.cpp index 0e89bba85..144c005c4 100644 --- a/AI/BattleAI/BattleAI.cpp +++ b/AI/BattleAI/BattleAI.cpp @@ -189,13 +189,17 @@ BattleAction CBattleAI::activeStack( const CStack * stack ) if(stack->waited()) { //ThreatMap threatsToUs(stack); // These lines may be usefull but they are't used in the code. - auto dists = getCbc()->battleGetDistances(stack, stack->getPosition()); + auto dists = getCbc()->getReachability(stack); if(!targets.unreachableEnemies.empty()) { - const EnemyInfo &ei= *range::min_element(targets.unreachableEnemies, std::bind(isCloser, _1, _2, std::ref(dists))); - if(distToNearestNeighbour(ei.s->getPosition(), dists) < GameConstants::BFIELD_SIZE) + auto closestEnemy = vstd::minElementByFun(targets.unreachableEnemies, [&](const battle::Unit * enemy) -> int { - return goTowards(stack, ei.s->getPosition()); + return dists.distToNearestNeighbour(stack, enemy); + }); + + if(dists.distToNearestNeighbour(stack, *closestEnemy) < GameConstants::BFIELD_SIZE) + { + return goTowards(stack, *closestEnemy); } } } @@ -216,19 +220,15 @@ BattleAction CBattleAI::activeStack( const CStack * stack ) return BattleAction::makeDefend(stack); } -BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination) +BattleAction CBattleAI::goTowards(const CStack * stack, const battle::Unit * enemy) const { - if(!destination.isValid()) - { - logAi->error("CBattleAI::goTowards: invalid destination"); - return BattleAction::makeDefend(stack); - } - auto reachability = cb->getReachability(stack); auto avHexes = cb->battleGetAvailableHexes(reachability, stack); + auto destination = enemy->getPosition(); if(vstd::contains(avHexes, destination)) return BattleAction::makeMove(stack, destination); + auto destNeighbours = destination.neighbouringTiles(); if(vstd::contains_if(destNeighbours, [&](BattleHex n) { return stack->coversPos(destination); })) { @@ -236,31 +236,31 @@ BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination) //We shouldn't even be here... return BattleAction::makeDefend(stack); } - vstd::erase_if(destNeighbours, [&](BattleHex hex){ return !reachability.accessibility.accessible(hex, stack); }); - if(!avHexes.size() || !destNeighbours.size()) //we are blocked or dest is blocked + + if(!avHexes.size()) //we are blocked or dest is blocked { return BattleAction::makeDefend(stack); } + + BattleHex bestNeighbor = destination; + if(reachability.distToNearestNeighbour(stack, enemy, &bestNeighbor) > GameConstants::BFIELD_SIZE) + { + return BattleAction::makeDefend(stack); + } + if(stack->hasBonusOfType(Bonus::FLYING)) { // Flying stack doesn't go hex by hex, so we can't backtrack using predecessors. // We just check all available hexes and pick the one closest to the target. - auto distToDestNeighbour = [&](BattleHex hex) -> int + auto nearestAvailableHex = vstd::minElementByFun(avHexes, [&](BattleHex hex) -> int { - auto nearestNeighbourToHex = vstd::minElementByFun(destNeighbours, [&](BattleHex a) - {return BattleHex::getDistance(a, hex);}); - return BattleHex::getDistance(*nearestNeighbourToHex, hex); - }; - auto nearestAvailableHex = vstd::minElementByFun(avHexes, distToDestNeighbour); + return BattleHex::getDistance(bestNeighbor, hex); + }); + return BattleAction::makeMove(stack, *nearestAvailableHex); } else { - BattleHex bestNeighbor = destination; - if(distToNearestNeighbour(destination, reachability.distances, &bestNeighbor) > GameConstants::BFIELD_SIZE) - { - return BattleAction::makeDefend(stack); - } BattleHex currentDest = bestNeighbor; while(1) { @@ -272,6 +272,7 @@ BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination) if(vstd::contains(avHexes, currentDest)) return BattleAction::makeMove(stack, currentDest); + currentDest = reachability.predecessors[currentDest]; } } @@ -624,32 +625,12 @@ void CBattleAI::evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcas ps.value = totalGain; }; -int CBattleAI::distToNearestNeighbour(BattleHex hex, const ReachabilityInfo::TDistances &dists, BattleHex *chosenHex) -{ - int ret = 1000000; - for(BattleHex n : hex.neighbouringTiles()) - { - if(dists[n] >= 0 && dists[n] < ret) - { - ret = dists[n]; - if(chosenHex) - *chosenHex = n; - } - } - return ret; -} - void CBattleAI::battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool Side) { LOG_TRACE(logAi); side = Side; } -bool CBattleAI::isCloser(const EnemyInfo &ei1, const EnemyInfo &ei2, const ReachabilityInfo::TDistances &dists) -{ - return distToNearestNeighbour(ei1.s->getPosition(), dists) < distToNearestNeighbour(ei2.s->getPosition(), dists); -} - void CBattleAI::print(const std::string &text) const { logAi->trace("%s Battle AI[%p]: %s", playerID.getStr(), this, text); diff --git a/AI/BattleAI/BattleAI.h b/AI/BattleAI/BattleAI.h index 149532340..385801f83 100644 --- a/AI/BattleAI/BattleAI.h +++ b/AI/BattleAI/BattleAI.h @@ -9,6 +9,7 @@ */ #pragma once #include "../../lib/AI_Base.h" +#include "../../lib/battle/ReachabilityInfo.h" #include "PossibleSpellcast.h" #include "PotentialTargets.h" @@ -64,13 +65,9 @@ public: void evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcast & ps); //for offensive damaging spells only BattleAction activeStack(const CStack * stack) override; //called when it's turn of that stack - BattleAction goTowards(const CStack * stack, BattleHex hex ); boost::optional considerFleeingOrSurrendering(); - static int distToNearestNeighbour(BattleHex hex, const ReachabilityInfo::TDistances& dists, BattleHex *chosenHex = nullptr); - static bool isCloser(const EnemyInfo & ei1, const EnemyInfo & ei2, const ReachabilityInfo::TDistances & dists); - void print(const std::string &text) const; BattleAction useCatapult(const CStack *stack); void battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool Side) override; @@ -88,4 +85,7 @@ public: //void battleTriggerEffect(const BattleTriggerEffect & bte) override; //void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side) override; //called by engine when battle starts; side=0 - left, side=1 - right //void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack + +private: + BattleAction goTowards(const CStack * stack, const battle::Unit * enemy) const; }; diff --git a/AI/StupidAI/StupidAI.cpp b/AI/StupidAI/StupidAI.cpp index 0a7b66c8e..0efcdb2fa 100644 --- a/AI/StupidAI/StupidAI.cpp +++ b/AI/StupidAI/StupidAI.cpp @@ -69,31 +69,6 @@ bool isMoreProfitable(const EnemyInfo &ei1, const EnemyInfo& ei2) return (ei1.adi-ei1.adr) < (ei2.adi - ei2.adr); } -namespace { - -int distToNearestNeighbour(BattleHex hex, const ReachabilityInfo::TDistances& dists, BattleHex *chosenHex = nullptr) -{ - int ret = 1000000; - for(auto & n: hex.neighbouringTiles()) - { - if(dists[n] >= 0 && dists[n] < ret) - { - ret = dists[n]; - if(chosenHex) - *chosenHex = n; - } - } - - return ret; -} - -bool isCloser(const EnemyInfo & ei1, const EnemyInfo & ei2, const ReachabilityInfo::TDistances & dists) -{ - return distToNearestNeighbour(ei1.s->getPosition(), dists) < distToNearestNeighbour(ei2.s->getPosition(), dists); -} - -} - static bool willSecondHexBlockMoreEnemyShooters(const BattleHex &h1, const BattleHex &h2) { int shooters[2] = {0}; //count of shooters on hexes @@ -111,7 +86,7 @@ BattleAction CStupidAI::activeStack( const CStack * stack ) { //boost::this_thread::sleep(boost::posix_time::seconds(2)); print("activeStack called for " + stack->nodeName()); - auto dists = cb->battleGetDistances(stack, stack->getPosition()); + ReachabilityInfo dists = cb->getReachability(stack); std::vector enemiesShootable, enemiesReachable, enemiesUnreachable; if(stack->type->idNumber == CreatureID::CATAPULT) @@ -179,12 +154,14 @@ BattleAction CStupidAI::activeStack( const CStack * stack ) } else if(enemiesUnreachable.size()) //due to #955 - a buggy battle may occur when there are no enemies { - assert(enemiesUnreachable.size()); - const EnemyInfo &ei= *std::min_element(enemiesUnreachable.begin(), enemiesUnreachable.end(), std::bind(isCloser, _1, _2, std::ref(dists))); - assert(ei.s); - if(distToNearestNeighbour(ei.s->getPosition(), dists) < GameConstants::BFIELD_SIZE) + auto closestEnemy = vstd::minElementByFun(enemiesUnreachable, [&](const EnemyInfo & ei) -> int { - return goTowards(stack, ei.s->getPosition()); + return dists.distToNearestNeighbour(stack, ei.s); + }); + + if(dists.distToNearestNeighbour(stack, closestEnemy->s) < GameConstants::BFIELD_SIZE) + { + return goTowards(stack, closestEnemy->s); } } @@ -252,11 +229,12 @@ void CStupidAI::print(const std::string &text) const logAi->trace("CStupidAI [%p]: %s", this, text); } -BattleAction CStupidAI::goTowards(const CStack * stack, BattleHex destination) +BattleAction CStupidAI::goTowards(const CStack * stack, const CStack * enemy) const { assert(destination.isValid()); auto reachability = cb->getReachability(stack); auto avHexes = cb->battleGetAvailableHexes(reachability, stack); + auto destination = enemy->getPosition(); if(vstd::contains(avHexes, destination)) return BattleAction::makeMove(stack, destination); @@ -269,11 +247,14 @@ BattleAction CStupidAI::goTowards(const CStack * stack, BattleHex destination) return BattleAction::makeDefend(stack); } - vstd::erase_if(destNeighbours, [&](BattleHex hex){ return !reachability.accessibility.accessible(hex, stack); }); - - if(!avHexes.size() || !destNeighbours.size()) //we are blocked or dest is blocked + if(!avHexes.size()) //we are blocked or dest is blocked + { + return BattleAction::makeDefend(stack); + } + + BattleHex bestNeighbor = destination; + if(reachability.distToNearestNeighbour(stack, enemy, &bestNeighbor) > GameConstants::BFIELD_SIZE) { - print("goTowards: Stack cannot move! That's " + stack->nodeName()); return BattleAction::makeDefend(stack); } @@ -281,32 +262,24 @@ BattleAction CStupidAI::goTowards(const CStack * stack, BattleHex destination) { // Flying stack doesn't go hex by hex, so we can't backtrack using predecessors. // We just check all available hexes and pick the one closest to the target. - auto distToDestNeighbour = [&](BattleHex hex) -> int + auto nearestAvailableHex = vstd::minElementByFun(avHexes, [&](BattleHex hex) -> int { - auto nearestNeighbourToHex = vstd::minElementByFun(destNeighbours, [&](BattleHex a) - { - return BattleHex::getDistance(a, hex); - }); + return BattleHex::getDistance(bestNeighbor, hex); + }); - return BattleHex::getDistance(*nearestNeighbourToHex, hex); - }; - - auto nearestAvailableHex = vstd::minElementByFun(avHexes, distToDestNeighbour); return BattleAction::makeMove(stack, *nearestAvailableHex); } else { - BattleHex bestNeighbor = destination; - if(distToNearestNeighbour(destination, reachability.distances, &bestNeighbor) > GameConstants::BFIELD_SIZE) - { - print("goTowards: Cannot reach"); - return BattleAction::makeDefend(stack); - } - BattleHex currentDest = bestNeighbor; while(1) { - assert(currentDest.isValid()); + if(!currentDest.isValid()) + { + logAi->error("CBattleAI::goTowards: internal error"); + return BattleAction::makeDefend(stack); + } + if(vstd::contains(avHexes, currentDest)) return BattleAction::makeMove(stack, currentDest); diff --git a/AI/StupidAI/StupidAI.h b/AI/StupidAI/StupidAI.h index 789873073..07d3e61c9 100644 --- a/AI/StupidAI/StupidAI.h +++ b/AI/StupidAI/StupidAI.h @@ -10,6 +10,9 @@ #pragma once #include "../../lib/battle/BattleHex.h" +#include "../../lib/battle/ReachabilityInfo.h" + +class EnemyInfo; class CStupidAI : public CBattleGameInterface { @@ -39,10 +42,10 @@ public: void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side) override; //called by engine when battle starts; side=0 - left, side=1 - right void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack - BattleAction goTowards(const CStack * stack, BattleHex hex ); - virtual void saveGame(BinarySerializer & h, const int version) override; virtual void loadGame(BinaryDeserializer & h, const int version) override; +private: + BattleAction goTowards(const CStack * stack, const CStack * enemy) const; }; diff --git a/lib/battle/ReachabilityInfo.cpp b/lib/battle/ReachabilityInfo.cpp index 3589415c8..a293f718b 100644 --- a/lib/battle/ReachabilityInfo.cpp +++ b/lib/battle/ReachabilityInfo.cpp @@ -40,3 +40,42 @@ bool ReachabilityInfo::isReachable(BattleHex hex) const { return distances[hex] < INFINITE_DIST; } + +int ReachabilityInfo::distToNearestNeighbour( + const battle::Unit * attacker, + const battle::Unit * defender, + BattleHex * chosenHex) const +{ + int ret = 1000000; + auto defenderHexes = battle::Unit::getHexes( + defender->getPosition(), + defender->doubleWide(), + defender->unitSide()); + + std::vector targetableHexes; + + for(auto defenderHex : defenderHexes) + { + vstd::concatenate(targetableHexes, battle::Unit::getHexes( + defenderHex, + attacker->doubleWide(), + defender->unitSide())); + } + + vstd::removeDuplicates(targetableHexes); + + for(auto targetableHex : targetableHexes) + { + for(auto & n : targetableHex.neighbouringTiles()) + { + if(distances[n] >= 0 && distances[n] < ret) + { + ret = distances[n]; + if(chosenHex) + *chosenHex = n; + } + } + } + + return ret; +} diff --git a/lib/battle/ReachabilityInfo.h b/lib/battle/ReachabilityInfo.h index f4be1073f..273b062cb 100644 --- a/lib/battle/ReachabilityInfo.h +++ b/lib/battle/ReachabilityInfo.h @@ -43,6 +43,11 @@ struct DLL_LINKAGE ReachabilityInfo ReachabilityInfo(); bool isReachable(BattleHex hex) const; + + int distToNearestNeighbour( + const battle::Unit * attacker, + const battle::Unit * defender, + BattleHex * chosenHex = nullptr) const; };