浏览代码

2184 - fix battlefield corners unreachable for 2 hex units

Andrii Danylchenko 4 年之前
父节点
当前提交
ea073c81d3
共有 6 个文件被更改,包括 103 次插入102 次删除
  1. 25 44
      AI/BattleAI/BattleAI.cpp
  2. 4 4
      AI/BattleAI/BattleAI.h
  3. 25 52
      AI/StupidAI/StupidAI.cpp
  4. 5 2
      AI/StupidAI/StupidAI.h
  5. 39 0
      lib/battle/ReachabilityInfo.cpp
  6. 5 0
      lib/battle/ReachabilityInfo.h

+ 25 - 44
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);

+ 4 - 4
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<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;
 	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;
 };

+ 25 - 52
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<EnemyInfo> 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()) //we are blocked or dest is blocked
+	{
+		return BattleAction::makeDefend(stack);
+	}
 
-	if(!avHexes.size() || !destNeighbours.size()) //we are blocked or dest is blocked
+	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(*nearestNeighbourToHex, hex);
-		};
+			return BattleHex::getDistance(bestNeighbor, 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);
 

+ 5 - 2
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;
 };
 

+ 39 - 0
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<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;
+}

+ 5 - 0
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;
 };