From c96be75f410734d28b179a426b342679918e8be8 Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Mon, 14 Jan 2019 13:20:46 +0200 Subject: [PATCH] AI: optimizae explore logically, reduce amount of scanned tiles during full map scan for new exploration point --- AI/VCAI/AIhelper.cpp | 5 - AI/VCAI/AIhelper.h | 7 +- AI/VCAI/Goals/Explore.cpp | 411 +++++++++++---------- AI/VCAI/Goals/Explore.h | 14 +- AI/VCAI/Pathfinding/AINodeStorage.cpp | 32 +- AI/VCAI/Pathfinding/AINodeStorage.h | 2 +- AI/VCAI/Pathfinding/AIPathfinder.cpp | 4 +- AI/VCAI/Pathfinding/AIPathfinder.h | 4 +- AI/VCAI/Pathfinding/PathfindingManager.cpp | 5 - AI/VCAI/Pathfinding/PathfindingManager.h | 8 +- 10 files changed, 252 insertions(+), 240 deletions(-) diff --git a/AI/VCAI/AIhelper.cpp b/AI/VCAI/AIhelper.cpp index 04ad94a02..87e5ae5f1 100644 --- a/AI/VCAI/AIhelper.cpp +++ b/AI/VCAI/AIhelper.cpp @@ -148,11 +148,6 @@ std::vector AIhelper::getPathsToTile(HeroPtr hero, int3 tile) return pathfindingManager->getPathsToTile(hero, tile); } -bool AIhelper::isTileAccessible(HeroPtr hero, int3 tile) -{ - return pathfindingManager->isTileAccessible(hero, tile); -} - void AIhelper::resetPaths() { pathfindingManager->resetPaths(); diff --git a/AI/VCAI/AIhelper.h b/AI/VCAI/AIhelper.h index f2d16a48e..cf893c58f 100644 --- a/AI/VCAI/AIhelper.h +++ b/AI/VCAI/AIhelper.h @@ -60,9 +60,14 @@ public: Goals::TGoalVec howToVisitTile(int3 tile) override; Goals::TGoalVec howToVisitObj(ObjectIdRef obj) override; std::vector getPathsToTile(HeroPtr hero, int3 tile) override; - bool isTileAccessible(HeroPtr hero, int3 tile) override; void resetPaths() override; + STRONG_INLINE + bool isTileAccessible(const HeroPtr & hero, const int3 & tile) + { + return pathfindingManager->isTileAccessible(hero, tile); + } + private: bool notifyGoalCompleted(Goals::TSubgoal goal) override; diff --git a/AI/VCAI/Goals/Explore.cpp b/AI/VCAI/Goals/Explore.cpp index 5ef83d121..b25beb9be 100644 --- a/AI/VCAI/Goals/Explore.cpp +++ b/AI/VCAI/Goals/Explore.cpp @@ -26,6 +26,208 @@ extern FuzzyHelper * fh; using namespace Goals; +namespace Goals +{ + struct ExplorationHelper + { + HeroPtr hero; + int sightRadius; + float bestValue; + TSubgoal bestGoal; + VCAI * aip; + CCallback * cbp; + const TeamState * ts; + int3 ourPos; + bool allowDeadEndCancellation; + bool allowGatherArmy; + + ExplorationHelper(HeroPtr h, bool gatherArmy) + { + cbp = cb.get(); + aip = ai.get(); + hero = h; + ts = cbp->getPlayerTeam(ai->playerID); + sightRadius = hero->getSightRadius(); + bestGoal = sptr(Goals::Invalid()); + bestValue = 0; + ourPos = h->convertPosition(h->pos, false); + allowDeadEndCancellation = true; + allowGatherArmy = gatherArmy; + } + + void scanSector(int scanRadius) + { + for(int x = ourPos.x - scanRadius; x <= ourPos.x + scanRadius; x++) + { + for(int y = ourPos.y - scanRadius; y <= ourPos.y + scanRadius; y++) + { + int3 tile = int3(x, y, ourPos.z); + + if(cbp->isInTheMap(tile) && ts->fogOfWarMap[tile.x][tile.y][tile.z]) + { + scanTile(tile); + } + } + } + } + + void scanMap() + { + int3 mapSize = cbp->getMapSize(); + int perimeter = 2 * sightRadius * (mapSize.x + mapSize.y); + + std::vector from; + std::vector to; + + from.reserve(perimeter); + to.reserve(perimeter); + + foreach_tile_pos([&](const int3 & pos) + { + if(ts->fogOfWarMap[pos.x][pos.y][pos.z]) + { + bool hasInvisibleNeighbor = false; + + foreach_neighbour(cbp, pos, [&](CCallback * cbp, int3 neighbour) + { + if(!ts->fogOfWarMap[neighbour.x][neighbour.y][neighbour.z]) + { + hasInvisibleNeighbor = true; + } + }); + + if(hasInvisibleNeighbor) + from.push_back(pos); + } + }); + + logAi->debug("Exploration scan visible area perimeter for hero %s", hero.name); + + for(const int3 & tile : from) + { + scanTile(tile); + } + + if(!bestGoal->invalid()) + { + return; + } + + allowDeadEndCancellation = false; + + for(int i = 0; i < sightRadius; i++) + { + getVisibleNeighbours(from, to); + vstd::concatenate(from, to); + vstd::removeDuplicates(from); + } + + logAi->debug("Exploration scan all possible tiles for hero %s", hero.name); + + for(const int3 & tile : from) + { + scanTile(tile); + } + } + + void scanTile(const int3 & tile) + { + if(tile == ourPos + || !aip->ah->isTileAccessible(hero, tile)) //shouldn't happen, but it does + return; + + int tilesDiscovered = howManyTilesWillBeDiscovered(tile); + if(!tilesDiscovered) + return; + + auto waysToVisit = aip->ah->howToVisitTile(hero, tile, allowGatherArmy); + for(auto goal : waysToVisit) + { + if(goal->evaluationContext.movementCost <= 0.0) // should not happen + continue; + + float ourValue = (float)tilesDiscovered * tilesDiscovered / goal->evaluationContext.movementCost; + + if(ourValue > bestValue) //avoid costly checks of tiles that don't reveal much + { + auto obj = cb->getTopObj(tile); + + // picking up resources does not yield any exploration at all. + // if it blocks the way to some explorable tile AIPathfinder will take care of it + if(obj && obj->blockVisit) + { + continue; + } + + if(isSafeToVisit(hero, tile)) + { + bestGoal = goal; + bestValue = ourValue; + } + } + } + } + + void getVisibleNeighbours(const std::vector & tiles, std::vector & out) const + { + for(const int3 & tile : tiles) + { + foreach_neighbour(cbp, tile, [&](CCallback * cbp, int3 neighbour) + { + if(ts->fogOfWarMap[neighbour.x][neighbour.y][neighbour.z]) + { + out.push_back(neighbour); + } + }); + } + } + + int howManyTilesWillBeDiscovered( + const int3 & pos) const + { + int ret = 0; + for(int x = pos.x - sightRadius; x <= pos.x + sightRadius; x++) + { + for(int y = pos.y - sightRadius; y <= pos.y + sightRadius; y++) + { + int3 npos = int3(x, y, pos.z); + if(cbp->isInTheMap(npos) + && pos.dist2d(npos) - 0.5 < sightRadius + && !ts->fogOfWarMap[npos.x][npos.y][npos.z]) + { + if(allowDeadEndCancellation + && !hasReachableNeighbor(npos)) + { + continue; + } + + ret++; + } + } + } + + return ret; + } + + bool hasReachableNeighbor(const int3 &pos) const + { + for(crint3 dir : int3::getDirs()) + { + int3 tile = pos + dir; + if(cbp->isInTheMap(tile)) + { + auto isAccessible = aip->ah->isTileAccessible(hero, tile); + + if(isAccessible) + return true; + } + } + + return false; + } + }; +} + bool Explore::operator==(const Explore & other) const { return other.hero.h == hero.h && other.allowGatherArmy == allowGatherArmy; @@ -38,18 +240,7 @@ std::string Explore::completeMessage() const TSubgoal Explore::whatToDoToAchieve() { - auto ret = fh->chooseSolution(getAllPossibleSubgoals()); - if(hero) //use best step for this hero - { - return ret; - } - else - { - if(ret->hero.get(true)) - return sptr(Explore().sethero(ret->hero.h)); //choose this hero and then continue with him - else - return ret; //other solutions, like buying hero from tavern - } + return fh->chooseSolution(getAllPossibleSubgoals()); } TGoalVec Explore::getAllPossibleSubgoals() @@ -143,6 +334,8 @@ TGoalVec Explore::getAllPossibleSubgoals() { for(auto h : heroes) { + logAi->trace("Exploration searching for a new point for hero %s", h->name); + TSubgoal goal = explorationNewPoint(h); if(goal->invalid()) @@ -180,197 +373,43 @@ bool Explore::fulfillsMe(TSubgoal goal) return false; } -bool Explore::hasReachableNeighbor(const int3 &pos, HeroPtr hero, CCallback * cbp, VCAI * vcai) const +TSubgoal Explore::explorationBestNeighbour(int3 hpos, HeroPtr h) const { - for(crint3 dir : int3::getDirs()) - { - int3 tile = pos + dir; - if(cbp->isInTheMap(tile)) - { - auto isAccessible = vcai->ah->isTileAccessible(hero, tile); - - if(isAccessible) - return true; - } - } - - return false; -} - -int Explore::howManyTilesWillBeDiscovered( - const int3 & pos, - int radious, - CCallback * cbp, - const TeamState * ts, - VCAI * aip, - HeroPtr h) const -{ - int ret = 0; - for(int x = pos.x - radious; x <= pos.x + radious; x++) - { - for(int y = pos.y - radious; y <= pos.y + radious; y++) - { - int3 npos = int3(x, y, pos.z); - if(cbp->isInTheMap(npos) - && pos.dist2d(npos) - 0.5 < radious - && !ts->fogOfWarMap[npos.x][npos.y][npos.z] - && hasReachableNeighbor(npos, h, cbp, aip)) - { - ret++; - } - } - } - - return ret; -} - -TSubgoal Explore::explorationBestNeighbour(int3 hpos, int radius, HeroPtr h) const -{ - std::map dstToRevealedTiles; - VCAI * aip = ai.get(); - CCallback * cbp = cb.get(); - const TeamState * ts = cbp->getPlayerTeam(ai->playerID); + ExplorationHelper scanResult(h, allowGatherArmy); for(crint3 dir : int3::getDirs()) { int3 tile = hpos + dir; if(cb->isInTheMap(tile)) { - if(isBlockVisitObj(tile)) - continue; - - if(isSafeToVisit(h, tile) && ai->isAccessibleForHero(tile, h)) - { - auto distance = hpos.dist2d(tile); // diagonal movement opens more tiles but spends more mp - int tilesDiscovered = howManyTilesWillBeDiscovered(tile, radius, cbp, ts, aip, h); - - if(tilesDiscovered > 0) - dstToRevealedTiles[tile] = tilesDiscovered / distance; - } + scanResult.scanTile(tile); } } - if(dstToRevealedTiles.empty()) //yes, it DID happen! - return sptr(Invalid()); - - auto paths = cb->getPathsInfo(h.get()); - - auto best = dstToRevealedTiles.begin(); - for(auto i = dstToRevealedTiles.begin(); i != dstToRevealedTiles.end(); i++) - { - const CGPathNode * pn = paths->getPathInfo(i->first); - if(best->second < i->second && pn->reachable() && pn->accessible == CGPathNode::ACCESSIBLE) - best = i; - } - - if(best->second) - return sptr(VisitTile(best->first).sethero(h)); - - return sptr(Invalid()); + return scanResult.bestGoal; } + TSubgoal Explore::explorationNewPoint(HeroPtr h) const { - int radius = h->getSightRadius(); - CCallback * cbp = cb.get(); - VCAI *aip = ai.get(); - const TeamState * ts = cbp->getPlayerTeam(aip->playerID); + ExplorationHelper scanResult(h, allowGatherArmy); - int3 mapSize = cbp->getMapSize(); - int perimiter = 2 * radius * (mapSize.x + mapSize.y); + scanResult.scanSector(10); - std::vector from; - std::vector to; - - from.reserve(perimiter); - to.reserve(perimiter); - - foreach_tile_pos([&](const int3 & pos) + if(!scanResult.bestGoal->invalid()) { - if(ts->fogOfWarMap[pos.x][pos.y][pos.z]) - { - bool hasInvisibleNeighbor = false; - - foreach_neighbour(cbp, pos, [&](CCallback * cbp, int3 neighbour) - { - if(!ts->fogOfWarMap[neighbour.x][neighbour.y][neighbour.z]) - { - hasInvisibleNeighbor = true; - } - }); - - if(hasInvisibleNeighbor) - from.push_back(pos); - } - }); - - for(int i = 0; i < radius; i++) - { - getVisibleNeighbours(from, to, cbp, ts); - vstd::concatenate(from, to); - vstd::removeDuplicates(from); + return scanResult.bestGoal; } - return explorationScanRange(h, from); + scanResult.scanMap(); + + return scanResult.bestGoal; } -TSubgoal Explore::explorationScanRange(HeroPtr h, std::vector & range) const -{ - int radius = h->getSightRadius(); - CCallback * cbp = cb.get(); - VCAI *aip = ai.get(); - const TeamState * ts = cbp->getPlayerTeam(aip->playerID); - - float bestValue = 0; //discovered tile to node distance ratio - TSubgoal bestWay = sptr(Invalid()); - int3 ourPos = h->convertPosition(h->pos, false); - - for(const int3 & tile : range) - { - if(tile == ourPos) //shouldn't happen, but it does - continue; - - int tilesDiscovered = howManyTilesWillBeDiscovered(tile, radius, cbp, ts, aip, h); - if(!tilesDiscovered) - continue; - - auto waysToVisit = aip->ah->howToVisitTile(h, tile, allowGatherArmy); - for(auto goal : waysToVisit) - { - if(goal->evaluationContext.movementCost <= 0.0) // should not happen - continue; - - float ourValue = (float)tilesDiscovered * tilesDiscovered / goal->evaluationContext.movementCost; - - if(ourValue > bestValue) //avoid costly checks of tiles that don't reveal much - { - auto obj = cb->getTopObj(tile); - if(obj && obj->blockVisit && !isObjectRemovable(obj)) //we can't stand on that object - { - continue; - } - - if(isSafeToVisit(h, tile)) - { - bestWay = goal; - bestValue = ourValue; - } - } - } - } - - if(!bestWay->invalid()) - { - return bestWay; - } - - return bestWay; -} TSubgoal Explore::exploreNearestNeighbour(HeroPtr h) const { TimeCheck tc("where to explore"); - int radius = h->getSightRadius(); int3 hpos = h->visitablePos(); //look for nearby objs -> visit them if they're close enough @@ -406,19 +445,5 @@ TSubgoal Explore::exploreNearestNeighbour(HeroPtr h) const } //check if nearby tiles allow us to reveal anything - this is quick - return explorationBestNeighbour(hpos, radius, h); -} - -void Explore::getVisibleNeighbours(const std::vector & tiles, std::vector & out, CCallback * cbp, const TeamState * ts) const -{ - for(const int3 & tile : tiles) - { - foreach_neighbour(cbp, tile, [&](CCallback * cbp, int3 neighbour) - { - if(ts->fogOfWarMap[neighbour.x][neighbour.y][neighbour.z]) - { - out.push_back(neighbour); - } - }); - } + return explorationBestNeighbour(hpos, h); } diff --git a/AI/VCAI/Goals/Explore.h b/AI/VCAI/Goals/Explore.h index 813c48468..54e55dcae 100644 --- a/AI/VCAI/Goals/Explore.h +++ b/AI/VCAI/Goals/Explore.h @@ -17,6 +17,8 @@ class FuzzyHelper; namespace Goals { + struct ExplorationHelper; + class DLL_EXPORT Explore : public CGoal { private: @@ -49,8 +51,8 @@ namespace Goals private: TSubgoal exploreNearestNeighbour(HeroPtr h) const; TSubgoal explorationNewPoint(HeroPtr h) const; - TSubgoal explorationBestNeighbour(int3 hpos, int radius, HeroPtr h) const; - TSubgoal explorationScanRange(HeroPtr h, std::vector & range) const; + TSubgoal explorationBestNeighbour(int3 hpos, HeroPtr h) const; + void explorationScanTile(const int3 & tile, ExplorationHelper & scanResult) const; bool hasReachableNeighbor(const int3 &pos, HeroPtr hero, CCallback * cbp, VCAI * vcai) const; void getVisibleNeighbours( @@ -59,12 +61,6 @@ namespace Goals CCallback * cbp, const TeamState * ts) const; - int howManyTilesWillBeDiscovered( - const int3 & pos, - int radious, - CCallback * cbp, - const TeamState * ts, - VCAI * aip, - HeroPtr h) const; + int howManyTilesWillBeDiscovered(const int3 & pos, ExplorationHelper & scanResult) const; }; } diff --git a/AI/VCAI/Pathfinding/AINodeStorage.cpp b/AI/VCAI/Pathfinding/AINodeStorage.cpp index 0161cb254..cbdc38091 100644 --- a/AI/VCAI/Pathfinding/AINodeStorage.cpp +++ b/AI/VCAI/Pathfinding/AINodeStorage.cpp @@ -220,18 +220,18 @@ std::vector AINodeStorage::calculateTeleportations( if(source.isNodeObjectVisitable()) { - auto accessibleExits = pathfinderHelper->getTeleportExits(source); - auto srcNode = getAINode(source.node); + auto accessibleExits = pathfinderHelper->getTeleportExits(source); + auto srcNode = getAINode(source.node); - for(auto & neighbour : accessibleExits) - { - auto node = getOrCreateNode(neighbour, source.node->layer, srcNode->chainMask); + for(auto & neighbour : accessibleExits) + { + auto node = getOrCreateNode(neighbour, source.node->layer, srcNode->chainMask); - if(!node) - continue; + if(!node) + continue; - neighbours.push_back(node.get()); - } + neighbours.push_back(node.get()); + } } if(hero->getPosition(false) == source.coord) @@ -342,19 +342,11 @@ bool AINodeStorage::hasBetterChain(const PathNodeInfo & source, CDestinationNode return false; } -bool AINodeStorage::isTileAccessible(int3 pos, const EPathfindingLayer layer) const +bool AINodeStorage::isTileAccessible(const int3 & pos, const EPathfindingLayer layer) const { - auto chains = nodes[pos.x][pos.y][pos.z][layer]; + const AIPathNode & node = nodes[pos.x][pos.y][pos.z][layer][0]; - for(const AIPathNode & node : chains) - { - if(node.action != CGPathNode::ENodeAction::UNKNOWN) - { - return true; - } - } - - return false; + return node.action != CGPathNode::ENodeAction::UNKNOWN; } std::vector AINodeStorage::getChainInfo(int3 pos, bool isOnLand) const diff --git a/AI/VCAI/Pathfinding/AINodeStorage.h b/AI/VCAI/Pathfinding/AINodeStorage.h index 0dc5332b8..4eda383ef 100644 --- a/AI/VCAI/Pathfinding/AINodeStorage.h +++ b/AI/VCAI/Pathfinding/AINodeStorage.h @@ -114,7 +114,7 @@ public: bool hasBetterChain(const PathNodeInfo & source, CDestinationNodeInfo & destination) const; boost::optional getOrCreateNode(const int3 & coord, const EPathfindingLayer layer, int chainNumber); std::vector getChainInfo(int3 pos, bool isOnLand) const; - bool isTileAccessible(int3 pos, const EPathfindingLayer layer) const; + bool isTileAccessible(const int3 & pos, const EPathfindingLayer layer) const; void setHero(HeroPtr heroPtr); diff --git a/AI/VCAI/Pathfinding/AIPathfinder.cpp b/AI/VCAI/Pathfinding/AIPathfinder.cpp index c666fa5d8..20038ac7a 100644 --- a/AI/VCAI/Pathfinding/AIPathfinder.cpp +++ b/AI/VCAI/Pathfinding/AIPathfinder.cpp @@ -35,7 +35,7 @@ void AIPathfinder::init() storageMap.clear(); } -bool AIPathfinder::isTileAccessible(HeroPtr hero, int3 tile) +bool AIPathfinder::isTileAccessible(const HeroPtr & hero, const int3 & tile) { boost::unique_lock storageLock(storageMutex); @@ -61,7 +61,7 @@ std::vector AIPathfinder::getPathInfo(HeroPtr hero, int3 tile) return nodeStorage->getChainInfo(tile, !tileInfo->isWater()); } -std::shared_ptr AIPathfinder::getOrCreateStorage(HeroPtr hero) +std::shared_ptr AIPathfinder::getOrCreateStorage(const HeroPtr & hero) { std::shared_ptr nodeStorage; diff --git a/AI/VCAI/Pathfinding/AIPathfinder.h b/AI/VCAI/Pathfinding/AIPathfinder.h index f537114c6..0ae9f9f76 100644 --- a/AI/VCAI/Pathfinding/AIPathfinder.h +++ b/AI/VCAI/Pathfinding/AIPathfinder.h @@ -23,11 +23,11 @@ private: CPlayerSpecificInfoCallback * cb; VCAI * ai; - std::shared_ptr getOrCreateStorage(HeroPtr hero); + std::shared_ptr getOrCreateStorage(const HeroPtr & hero); public: AIPathfinder(CPlayerSpecificInfoCallback * cb, VCAI * ai); std::vector getPathInfo(HeroPtr hero, int3 tile); - bool isTileAccessible(HeroPtr hero, int3 tile); + bool isTileAccessible(const HeroPtr & hero, const int3 & tile); void clear(); void init(); }; diff --git a/AI/VCAI/Pathfinding/PathfindingManager.cpp b/AI/VCAI/Pathfinding/PathfindingManager.cpp index f39d3f0b7..ddf5ba63c 100644 --- a/AI/VCAI/Pathfinding/PathfindingManager.cpp +++ b/AI/VCAI/Pathfinding/PathfindingManager.cpp @@ -107,11 +107,6 @@ std::vector PathfindingManager::getPathsToTile(HeroPtr hero, int3 tile) return pathfinder->getPathInfo(hero, tile); } -bool PathfindingManager::isTileAccessible(HeroPtr hero, int3 tile) -{ - return pathfinder->isTileAccessible(hero, tile); -} - Goals::TGoalVec PathfindingManager::findPath( HeroPtr hero, crint3 dest, diff --git a/AI/VCAI/Pathfinding/PathfindingManager.h b/AI/VCAI/Pathfinding/PathfindingManager.h index 7f3afbd70..4921b6fb7 100644 --- a/AI/VCAI/Pathfinding/PathfindingManager.h +++ b/AI/VCAI/Pathfinding/PathfindingManager.h @@ -26,7 +26,6 @@ public: virtual Goals::TGoalVec howToVisitTile(int3 tile) = 0; virtual Goals::TGoalVec howToVisitObj(ObjectIdRef obj) = 0; virtual std::vector getPathsToTile(HeroPtr hero, int3 tile) = 0; - virtual bool isTileAccessible(HeroPtr hero, int3 tile) = 0; }; class DLL_EXPORT PathfindingManager : public IPathfindingManager @@ -47,9 +46,14 @@ public: Goals::TGoalVec howToVisitTile(int3 tile) override; Goals::TGoalVec howToVisitObj(ObjectIdRef obj) override; std::vector getPathsToTile(HeroPtr hero, int3 tile) override; - bool isTileAccessible(HeroPtr hero, int3 tile) override; void resetPaths() override; + STRONG_INLINE + bool isTileAccessible(const HeroPtr & hero, const int3 & tile) + { + return pathfinder->isTileAccessible(hero, tile); + } + private: void init(CPlayerSpecificInfoCallback * CB) override; void setAI(VCAI * AI) override;