1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

2184 - fix battlefield corners unreachable for 2 hex units

This commit is contained in:
Andrii Danylchenko 2020-11-23 08:40:36 +02:00
parent 58b8fe3d26
commit ea073c81d3
6 changed files with 104 additions and 103 deletions

View File

@ -189,13 +189,17 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
if(stack->waited()) if(stack->waited())
{ {
//ThreatMap threatsToUs(stack); // These lines may be usefull but they are't used in the code. //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()) if(!targets.unreachableEnemies.empty())
{ {
const EnemyInfo &ei= *range::min_element(targets.unreachableEnemies, std::bind(isCloser, _1, _2, std::ref(dists))); auto closestEnemy = vstd::minElementByFun(targets.unreachableEnemies, [&](const battle::Unit * enemy) -> int
if(distToNearestNeighbour(ei.s->getPosition(), dists) < GameConstants::BFIELD_SIZE)
{ {
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); 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 reachability = cb->getReachability(stack);
auto avHexes = cb->battleGetAvailableHexes(reachability, stack); auto avHexes = cb->battleGetAvailableHexes(reachability, stack);
auto destination = enemy->getPosition();
if(vstd::contains(avHexes, destination)) if(vstd::contains(avHexes, destination))
return BattleAction::makeMove(stack, destination); return BattleAction::makeMove(stack, destination);
auto destNeighbours = destination.neighbouringTiles(); auto destNeighbours = destination.neighbouringTiles();
if(vstd::contains_if(destNeighbours, [&](BattleHex n) { return stack->coversPos(destination); })) 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... //We shouldn't even be here...
return BattleAction::makeDefend(stack); 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); 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)) if(stack->hasBonusOfType(Bonus::FLYING))
{ {
// Flying stack doesn't go hex by hex, so we can't backtrack using predecessors. // 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. // 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(bestNeighbor, hex);
{return BattleHex::getDistance(a, hex);}); });
return BattleHex::getDistance(*nearestNeighbourToHex, hex);
};
auto nearestAvailableHex = vstd::minElementByFun(avHexes, distToDestNeighbour);
return BattleAction::makeMove(stack, *nearestAvailableHex); return BattleAction::makeMove(stack, *nearestAvailableHex);
} }
else else
{ {
BattleHex bestNeighbor = destination;
if(distToNearestNeighbour(destination, reachability.distances, &bestNeighbor) > GameConstants::BFIELD_SIZE)
{
return BattleAction::makeDefend(stack);
}
BattleHex currentDest = bestNeighbor; BattleHex currentDest = bestNeighbor;
while(1) while(1)
{ {
@ -272,6 +272,7 @@ BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination)
if(vstd::contains(avHexes, currentDest)) if(vstd::contains(avHexes, currentDest))
return BattleAction::makeMove(stack, currentDest); return BattleAction::makeMove(stack, currentDest);
currentDest = reachability.predecessors[currentDest]; currentDest = reachability.predecessors[currentDest];
} }
} }
@ -624,32 +625,12 @@ void CBattleAI::evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcas
ps.value = totalGain; 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) void CBattleAI::battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool Side)
{ {
LOG_TRACE(logAi); LOG_TRACE(logAi);
side = Side; 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 void CBattleAI::print(const std::string &text) const
{ {
logAi->trace("%s Battle AI[%p]: %s", playerID.getStr(), this, text); logAi->trace("%s Battle AI[%p]: %s", playerID.getStr(), this, text);

View File

@ -9,6 +9,7 @@
*/ */
#pragma once #pragma once
#include "../../lib/AI_Base.h" #include "../../lib/AI_Base.h"
#include "../../lib/battle/ReachabilityInfo.h"
#include "PossibleSpellcast.h" #include "PossibleSpellcast.h"
#include "PotentialTargets.h" #include "PotentialTargets.h"
@ -64,13 +65,9 @@ public:
void evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcast & ps); //for offensive damaging spells only 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 activeStack(const CStack * stack) override; //called when it's turn of that stack
BattleAction goTowards(const CStack * stack, BattleHex hex );
boost::optional<BattleAction> considerFleeingOrSurrendering(); boost::optional<BattleAction> 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; void print(const std::string &text) const;
BattleAction useCatapult(const CStack *stack); BattleAction useCatapult(const CStack *stack);
void battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool Side) override; 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 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 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 //void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack
private:
BattleAction goTowards(const CStack * stack, const battle::Unit * enemy) const;
}; };

View File

@ -69,31 +69,6 @@ bool isMoreProfitable(const EnemyInfo &ei1, const EnemyInfo& ei2)
return (ei1.adi-ei1.adr) < (ei2.adi - ei2.adr); 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) static bool willSecondHexBlockMoreEnemyShooters(const BattleHex &h1, const BattleHex &h2)
{ {
int shooters[2] = {0}; //count of shooters on hexes 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)); //boost::this_thread::sleep(boost::posix_time::seconds(2));
print("activeStack called for " + stack->nodeName()); print("activeStack called for " + stack->nodeName());
auto dists = cb->battleGetDistances(stack, stack->getPosition()); ReachabilityInfo dists = cb->getReachability(stack);
std::vector<EnemyInfo> enemiesShootable, enemiesReachable, enemiesUnreachable; std::vector<EnemyInfo> enemiesShootable, enemiesReachable, enemiesUnreachable;
if(stack->type->idNumber == CreatureID::CATAPULT) 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 else if(enemiesUnreachable.size()) //due to #955 - a buggy battle may occur when there are no enemies
{ {
assert(enemiesUnreachable.size()); auto closestEnemy = vstd::minElementByFun(enemiesUnreachable, [&](const EnemyInfo & ei) -> int
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)
{ {
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); 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()); assert(destination.isValid());
auto reachability = cb->getReachability(stack); auto reachability = cb->getReachability(stack);
auto avHexes = cb->battleGetAvailableHexes(reachability, stack); auto avHexes = cb->battleGetAvailableHexes(reachability, stack);
auto destination = enemy->getPosition();
if(vstd::contains(avHexes, destination)) if(vstd::contains(avHexes, destination))
return BattleAction::makeMove(stack, destination); return BattleAction::makeMove(stack, destination);
@ -269,11 +247,14 @@ BattleAction CStupidAI::goTowards(const CStack * stack, BattleHex destination)
return BattleAction::makeDefend(stack); return BattleAction::makeDefend(stack);
} }
vstd::erase_if(destNeighbours, [&](BattleHex hex){ return !reachability.accessibility.accessible(hex, stack); }); if(!avHexes.size()) //we are blocked or dest is blocked
{
if(!avHexes.size() || !destNeighbours.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); 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. // 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. // 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(bestNeighbor, hex);
{ });
return BattleHex::getDistance(a, hex);
});
return BattleHex::getDistance(*nearestNeighbourToHex, hex);
};
auto nearestAvailableHex = vstd::minElementByFun(avHexes, distToDestNeighbour);
return BattleAction::makeMove(stack, *nearestAvailableHex); return BattleAction::makeMove(stack, *nearestAvailableHex);
} }
else else
{ {
BattleHex bestNeighbor = destination;
if(distToNearestNeighbour(destination, reachability.distances, &bestNeighbor) > GameConstants::BFIELD_SIZE)
{
print("goTowards: Cannot reach");
return BattleAction::makeDefend(stack);
}
BattleHex currentDest = bestNeighbor; BattleHex currentDest = bestNeighbor;
while(1) while(1)
{ {
assert(currentDest.isValid()); if(!currentDest.isValid())
{
logAi->error("CBattleAI::goTowards: internal error");
return BattleAction::makeDefend(stack);
}
if(vstd::contains(avHexes, currentDest)) if(vstd::contains(avHexes, currentDest))
return BattleAction::makeMove(stack, currentDest); return BattleAction::makeMove(stack, currentDest);

View File

@ -10,6 +10,9 @@
#pragma once #pragma once
#include "../../lib/battle/BattleHex.h" #include "../../lib/battle/BattleHex.h"
#include "../../lib/battle/ReachabilityInfo.h"
class EnemyInfo;
class CStupidAI : public CBattleGameInterface 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 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 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 saveGame(BinarySerializer & h, const int version) override;
virtual void loadGame(BinaryDeserializer & h, const int version) override; virtual void loadGame(BinaryDeserializer & h, const int version) override;
private:
BattleAction goTowards(const CStack * stack, const CStack * enemy) const;
}; };

View File

@ -40,3 +40,42 @@ bool ReachabilityInfo::isReachable(BattleHex hex) const
{ {
return distances[hex] < INFINITE_DIST; 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<BattleHex> 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;
}

View File

@ -43,6 +43,11 @@ struct DLL_LINKAGE ReachabilityInfo
ReachabilityInfo(); ReachabilityInfo();
bool isReachable(BattleHex hex) const; bool isReachable(BattleHex hex) const;
int distToNearestNeighbour(
const battle::Unit * attacker,
const battle::Unit * defender,
BattleHex * chosenHex = nullptr) const;
}; };