mirror of
https://github.com/vcmi/vcmi.git
synced 2025-08-13 19:54:17 +02:00
Nullkiller: small optimization of AIPathfinder for big maps
This commit is contained in:
committed by
Andrii Danylchenko
parent
07b6b0605c
commit
5bfe71c8f3
@@ -351,4 +351,11 @@ bool isWeeklyRevisitable(const CGObjectInstance * obj)
|
||||
return (dynamic_cast<const CGKeys *>(obj))->wasMyColorVisited(ai->playerID); //FIXME: they could be revisited sooner than in a week
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t timeElapsed(boost::chrono::time_point<boost::chrono::steady_clock> start)
|
||||
{
|
||||
auto end = boost::chrono::high_resolution_clock::now();
|
||||
|
||||
return boost::chrono::duration_cast<boost::chrono::milliseconds>(end - start).count();
|
||||
}
|
@@ -182,6 +182,8 @@ bool compareHeroStrength(HeroPtr h1, HeroPtr h2);
|
||||
bool compareArmyStrength(const CArmedInstance * a1, const CArmedInstance * a2);
|
||||
bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2);
|
||||
|
||||
uint64_t timeElapsed(boost::chrono::time_point<boost::chrono::steady_clock> start);
|
||||
|
||||
class CDistanceSorter
|
||||
{
|
||||
const CGHeroInstance * hero;
|
||||
|
@@ -16,14 +16,17 @@ void DangerHitMapAnalyzer::updateHitMap()
|
||||
if(upToDate)
|
||||
return;
|
||||
|
||||
logAi->trace("Update danger hitmap");
|
||||
|
||||
upToDate = true;
|
||||
auto start = boost::chrono::high_resolution_clock::now();
|
||||
|
||||
auto cb = ai->cb.get();
|
||||
auto mapSize = ai->cb->getMapSize();
|
||||
hitMap.resize(boost::extents[mapSize.x][mapSize.y][mapSize.z]);
|
||||
enemyHeroAccessibleObjects.clear();
|
||||
|
||||
std::map<PlayerColor, std::vector<const CGHeroInstance *>> heroes;
|
||||
std::map<PlayerColor, std::map<const CGHeroInstance *, HeroRole>> heroes;
|
||||
|
||||
for(const CGObjectInstance * obj : ai->memory->visitableObjs)
|
||||
{
|
||||
@@ -31,7 +34,7 @@ void DangerHitMapAnalyzer::updateHitMap()
|
||||
{
|
||||
auto hero = dynamic_cast<const CGHeroInstance *>(obj);
|
||||
|
||||
heroes[hero->tempOwner].push_back(hero);
|
||||
heroes[hero->tempOwner][hero] = HeroRole::MAIN;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +86,8 @@ void DangerHitMapAnalyzer::updateHitMap()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
logAi->trace("Danger hit map updated in %ld", timeElapsed(start));
|
||||
}
|
||||
|
||||
uint64_t DangerHitMapAnalyzer::enemyCanKillOurHeroesAlongThePath(const AIPath & path) const
|
||||
|
@@ -172,6 +172,8 @@ bool ObjectClusterizer::shouldVisitObject(const CGObjectInstance * obj) const
|
||||
|
||||
void ObjectClusterizer::clusterize()
|
||||
{
|
||||
auto start = boost::chrono::high_resolution_clock::now();
|
||||
|
||||
nearObjects.reset();
|
||||
farObjects.reset();
|
||||
blockedObjects.clear();
|
||||
@@ -276,4 +278,6 @@ void ObjectClusterizer::clusterize()
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
logAi->trace("Clusterization complete in %ld", timeElapsed(start));
|
||||
}
|
@@ -62,6 +62,7 @@ Goals::TTask Nullkiller::choseBestTask(Goals::TSubgoal behavior) const
|
||||
Goals::TGoalVec goals[MAX_DEPTH + 1];
|
||||
Goals::TTaskVec tasks;
|
||||
std::map<Goals::TSubgoal, Goals::TSubgoal> decompositionMap;
|
||||
auto start = boost::chrono::high_resolution_clock::now();
|
||||
|
||||
goals[0] = {behavior};
|
||||
|
||||
@@ -127,14 +128,19 @@ Goals::TTask Nullkiller::choseBestTask(Goals::TSubgoal behavior) const
|
||||
|
||||
if(tasks.empty())
|
||||
{
|
||||
logAi->debug("Behavior %s found no tasks", behavior->toString());
|
||||
logAi->debug("Behavior %s found no tasks. Time taken %ld", behavior->toString(), timeElapsed(start));
|
||||
|
||||
return Goals::taskptr(Goals::Invalid());
|
||||
}
|
||||
|
||||
auto task = choseBestTask(tasks);
|
||||
|
||||
logAi->debug("Behavior %s returns %s, priority %f", behavior->toString(), task->toString(), task->priority);
|
||||
logAi->debug(
|
||||
"Behavior %s returns %s, priority %f. Time taken %ld",
|
||||
behavior->toString(),
|
||||
task->toString(),
|
||||
task->priority,
|
||||
timeElapsed(start));
|
||||
|
||||
return task;
|
||||
}
|
||||
@@ -148,26 +154,34 @@ void Nullkiller::resetAiState()
|
||||
|
||||
void Nullkiller::updateAiState()
|
||||
{
|
||||
auto start = boost::chrono::high_resolution_clock::now();
|
||||
|
||||
activeHero = nullptr;
|
||||
|
||||
memory->removeInvisibleObjects(cb.get());
|
||||
dangerHitMap->updateHitMap();
|
||||
|
||||
auto activeHeroes = cb->getHeroesInfo();
|
||||
heroManager->update();
|
||||
logAi->trace("Updating paths");
|
||||
|
||||
vstd::erase_if(activeHeroes, [this](const CGHeroInstance * hero) -> bool
|
||||
std::map<const CGHeroInstance *, HeroRole> activeHeroes;
|
||||
|
||||
for(auto hero : cb->getHeroesInfo())
|
||||
{
|
||||
auto lockedReason = getHeroLockedReason(hero);
|
||||
if(getHeroLockedReason(hero) == HeroLockedReason::DEFENCE)
|
||||
continue;
|
||||
|
||||
return lockedReason == HeroLockedReason::DEFENCE;
|
||||
});
|
||||
activeHeroes[hero] = heroManager->getHeroRole(hero);
|
||||
}
|
||||
|
||||
pathfinder->updatePaths(activeHeroes, true);
|
||||
heroManager->update();
|
||||
|
||||
armyManager->update();
|
||||
|
||||
objectClusterizer->clusterize();
|
||||
buildAnalyzer->update();
|
||||
|
||||
logAi->debug("AI state updated in %ld", timeElapsed(start));
|
||||
}
|
||||
|
||||
bool Nullkiller::isHeroLocked(const CGHeroInstance * hero) const
|
||||
|
@@ -640,13 +640,6 @@ EvaluationContext PriorityEvaluator::buildEvaluationContext(Goals::TSubgoal goal
|
||||
return context;
|
||||
}
|
||||
|
||||
/// distance
|
||||
/// nearest hero?
|
||||
/// gold income
|
||||
/// army income
|
||||
/// hero strength - hero skills
|
||||
/// danger
|
||||
/// importance
|
||||
float PriorityEvaluator::evaluate(Goals::TSubgoal task)
|
||||
{
|
||||
auto evaluationContext = buildEvaluationContext(task);
|
||||
@@ -677,7 +670,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
|
||||
fearVariable->setValue(evaluationContext.enemyHeroDangerRatio);
|
||||
|
||||
engine->process();
|
||||
//engine.process(VISIT_TILE); //TODO: Process only Visit_Tile
|
||||
|
||||
result = value->getValue();
|
||||
}
|
||||
catch(fl::Exception & fe)
|
||||
|
@@ -614,22 +614,45 @@ const std::set<const CGHeroInstance *> AINodeStorage::getAllHeroes() const
|
||||
return heroes;
|
||||
}
|
||||
|
||||
void AINodeStorage::setHeroes(std::vector<const CGHeroInstance *> heroes)
|
||||
bool AINodeStorage::isDistanceLimitReached(const PathNodeInfo & source, CDestinationNodeInfo & destination) const
|
||||
{
|
||||
if(heroChainPass == EHeroChainPass::CHAIN && destination.node->turns > heroChainTurn)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
auto aiNode = getAINode(destination.node);
|
||||
|
||||
if(heroChainPass == EHeroChainPass::FINAL)
|
||||
{
|
||||
if(aiNode->actor->heroRole == HeroRole::SCOUT && destination.node->turns > 3)
|
||||
return true;
|
||||
}
|
||||
else if(heroChainPass == EHeroChainPass::INITIAL)
|
||||
{
|
||||
if(aiNode->actor->heroRole == HeroRole::SCOUT && destination.node->turns > 5)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void AINodeStorage::setHeroes(std::map<const CGHeroInstance *, HeroRole> heroes)
|
||||
{
|
||||
playerID = ai->playerID;
|
||||
|
||||
for(auto & hero : heroes)
|
||||
{
|
||||
uint64_t mask = 1 << actors.size();
|
||||
auto actor = std::make_shared<HeroActor>(hero, mask, ai);
|
||||
auto actor = std::make_shared<HeroActor>(hero.first, hero.second, mask, ai);
|
||||
|
||||
if(hero->tempOwner != ai->playerID)
|
||||
if(actor->hero->tempOwner != ai->playerID)
|
||||
{
|
||||
bool onLand = !actor->hero->boat;
|
||||
actor->initialMovement = actor->hero->maxMovePoints(onLand);
|
||||
}
|
||||
|
||||
playerID = hero->tempOwner;
|
||||
playerID = actor->hero->tempOwner;
|
||||
|
||||
actors.push_back(actor);
|
||||
}
|
||||
@@ -926,7 +949,7 @@ bool AINodeStorage::hasBetterChain(
|
||||
}
|
||||
}
|
||||
|
||||
if(candidateActor->chainMask != node.actor->chainMask)
|
||||
if(candidateActor->chainMask != node.actor->chainMask && heroChainPass == EHeroChainPass::CHAIN)
|
||||
continue;
|
||||
|
||||
auto nodeActor = node.actor;
|
||||
@@ -949,29 +972,32 @@ bool AINodeStorage::hasBetterChain(
|
||||
return true;
|
||||
}
|
||||
|
||||
/*if(nodeArmyValue == candidateArmyValue
|
||||
&& nodeActor->heroFightingStrength >= candidateActor->heroFightingStrength
|
||||
&& node.cost <= candidateNode->cost)
|
||||
if(heroChainPass == EHeroChainPass::FINAL)
|
||||
{
|
||||
if(nodeActor->heroFightingStrength == candidateActor->heroFightingStrength
|
||||
&& node.cost == candidateNode->cost
|
||||
&& &node < candidateNode)
|
||||
if(nodeArmyValue == candidateArmyValue
|
||||
&& nodeActor->heroFightingStrength >= candidateActor->heroFightingStrength
|
||||
&& node.cost <= candidateNode->cost)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if(nodeActor->heroFightingStrength == candidateActor->heroFightingStrength
|
||||
&& node.cost == candidateNode->cost
|
||||
&& &node < candidateNode)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace(
|
||||
"Block ineficient move because of stronger hero %s->%s, hero: %s[%X], army %lld, mp diff: %i",
|
||||
source->coord.toString(),
|
||||
candidateNode->coord.toString(),
|
||||
candidateNode->actor->hero->name,
|
||||
candidateNode->actor->chainMask,
|
||||
candidateNode->actor->armyValue,
|
||||
node.moveRemains - candidateNode->moveRemains);
|
||||
logAi->trace(
|
||||
"Block ineficient move because of stronger hero %s->%s, hero: %s[%X], army %lld, mp diff: %i",
|
||||
source->coord.toString(),
|
||||
candidateNode->coord.toString(),
|
||||
candidateNode->actor->hero->name,
|
||||
candidateNode->actor->chainMask,
|
||||
candidateNode->actor->armyValue,
|
||||
node.moveRemains - candidateNode->moveRemains);
|
||||
#endif
|
||||
return true;
|
||||
}*/
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -1170,7 +1196,7 @@ std::string AIPath::toString() const
|
||||
{
|
||||
std::stringstream str;
|
||||
|
||||
str << targetHero->name << "[" << std::hex << chainMask << std::dec << "]" << ": ";
|
||||
str << targetHero->name << "[" << std::hex << chainMask << std::dec << "]" << ", turn " << (int)(turn()) << ": ";
|
||||
|
||||
for(auto node : nodes)
|
||||
str << node.targetHero->name << "[" << std::hex << node.chainMask << std::dec << "]" << "->" << node.coord.toString() << "; ";
|
||||
|
@@ -11,7 +11,7 @@
|
||||
#pragma once
|
||||
|
||||
#define PATHFINDER_TRACE_LEVEL 0
|
||||
#define AI_TRACE_LEVEL 1
|
||||
#define AI_TRACE_LEVEL 0
|
||||
|
||||
#include "../../../lib/CPathfinder.h"
|
||||
#include "../../../lib/mapObjects/CGHeroInstance.h"
|
||||
@@ -162,10 +162,7 @@ public:
|
||||
return hasBetterChain(source, destination);
|
||||
}
|
||||
|
||||
bool isDistanceLimitReached(const PathNodeInfo & source, CDestinationNodeInfo & destination) const
|
||||
{
|
||||
return heroChainPass == EHeroChainPass::CHAIN && destination.node->turns > heroChainTurn;
|
||||
}
|
||||
bool isDistanceLimitReached(const PathNodeInfo & source, CDestinationNodeInfo & destination) const;
|
||||
|
||||
template<class NodeRange>
|
||||
bool hasBetterChain(
|
||||
@@ -176,7 +173,7 @@ public:
|
||||
boost::optional<AIPathNode *> getOrCreateNode(const int3 & coord, const EPathfindingLayer layer, const ChainActor * actor);
|
||||
std::vector<AIPath> getChainInfo(const int3 & pos, bool isOnLand) const;
|
||||
bool isTileAccessible(const HeroPtr & hero, const int3 & pos, const EPathfindingLayer layer) const;
|
||||
void setHeroes(std::vector<const CGHeroInstance *> heroes);
|
||||
void setHeroes(std::map<const CGHeroInstance *, HeroRole> heroes);
|
||||
void setTownsAndDwellings(
|
||||
const std::vector<const CGTownInstance *> & towns,
|
||||
const std::set<const CGObjectInstance *> & visitableObjs);
|
||||
|
@@ -42,13 +42,14 @@ std::vector<AIPath> AIPathfinder::getPathInfo(const int3 & tile) const
|
||||
return storage->getChainInfo(tile, !tileInfo->isWater());
|
||||
}
|
||||
|
||||
void AIPathfinder::updatePaths(std::vector<const CGHeroInstance *> heroes, bool useHeroChain)
|
||||
void AIPathfinder::updatePaths(std::map<const CGHeroInstance *, HeroRole> heroes, bool useHeroChain)
|
||||
{
|
||||
if(!storage)
|
||||
{
|
||||
storage.reset(new AINodeStorage(ai, cb->getMapSize()));
|
||||
}
|
||||
|
||||
auto start = boost::chrono::high_resolution_clock::now();
|
||||
logAi->debug("Recalculate all paths");
|
||||
int pass = 0;
|
||||
|
||||
@@ -89,4 +90,6 @@ void AIPathfinder::updatePaths(std::vector<const CGHeroInstance *> heroes, bool
|
||||
cb->calculatePaths(config);
|
||||
}
|
||||
} while(storage->increaseHeroChainTurnLimit());
|
||||
|
||||
logAi->trace("Recalculated paths in %ld", timeElapsed(start));
|
||||
}
|
||||
|
@@ -26,6 +26,6 @@ public:
|
||||
AIPathfinder(CPlayerSpecificInfoCallback * cb, Nullkiller * ai);
|
||||
std::vector<AIPath> getPathInfo(const int3 & tile) const;
|
||||
bool isTileAccessible(const HeroPtr & hero, const int3 & tile) const;
|
||||
void updatePaths(std::vector<const CGHeroInstance *> heroes, bool useHeroChain = false);
|
||||
void updatePaths(std::map<const CGHeroInstance *, HeroRole> heroes, bool useHeroChain = false);
|
||||
void init();
|
||||
};
|
||||
|
@@ -22,8 +22,8 @@ bool HeroExchangeArmy::needsLastStack() const
|
||||
return true;
|
||||
}
|
||||
|
||||
ChainActor::ChainActor(const CGHeroInstance * hero, uint64_t chainMask)
|
||||
:hero(hero), isMovable(true), chainMask(chainMask), creatureSet(hero),
|
||||
ChainActor::ChainActor(const CGHeroInstance * hero, HeroRole heroRole, uint64_t chainMask)
|
||||
:hero(hero), heroRole(heroRole), isMovable(true), chainMask(chainMask), creatureSet(hero),
|
||||
baseActor(this), carrierParent(nullptr), otherParent(nullptr), actorExchangeCount(1), armyCost()
|
||||
{
|
||||
initialPosition = hero->visitablePos();
|
||||
@@ -35,7 +35,7 @@ ChainActor::ChainActor(const CGHeroInstance * hero, uint64_t chainMask)
|
||||
}
|
||||
|
||||
ChainActor::ChainActor(const ChainActor * carrier, const ChainActor * other, const CCreatureSet * heroArmy)
|
||||
:hero(carrier->hero), isMovable(true), creatureSet(heroArmy), chainMask(carrier->chainMask | other->chainMask),
|
||||
:hero(carrier->hero), heroRole(carrier->heroRole), isMovable(true), creatureSet(heroArmy), chainMask(carrier->chainMask | other->chainMask),
|
||||
baseActor(this), carrierParent(carrier), otherParent(other), heroFightingStrength(carrier->heroFightingStrength),
|
||||
actorExchangeCount(carrier->actorExchangeCount + other->actorExchangeCount), armyCost(carrier->armyCost + other->armyCost)
|
||||
{
|
||||
@@ -43,7 +43,7 @@ ChainActor::ChainActor(const ChainActor * carrier, const ChainActor * other, con
|
||||
}
|
||||
|
||||
ChainActor::ChainActor(const CGObjectInstance * obj, const CCreatureSet * creatureSet, uint64_t chainMask, int initialTurn)
|
||||
:hero(nullptr), isMovable(false), creatureSet(creatureSet), chainMask(chainMask),
|
||||
:hero(nullptr), heroRole(HeroRole::MAIN), isMovable(false), creatureSet(creatureSet), chainMask(chainMask),
|
||||
baseActor(this), carrierParent(nullptr), otherParent(nullptr), initialTurn(initialTurn), initialMovement(0),
|
||||
heroFightingStrength(0), actorExchangeCount(1), armyCost()
|
||||
{
|
||||
@@ -72,8 +72,8 @@ std::string ObjectActor::toString() const
|
||||
return object->getObjectName() + " at " + object->visitablePos().toString();
|
||||
}
|
||||
|
||||
HeroActor::HeroActor(const CGHeroInstance * hero, uint64_t chainMask, const Nullkiller * ai)
|
||||
:ChainActor(hero, chainMask)
|
||||
HeroActor::HeroActor(const CGHeroInstance * hero, HeroRole heroRole, uint64_t chainMask, const Nullkiller * ai)
|
||||
:ChainActor(hero, heroRole, chainMask)
|
||||
{
|
||||
exchangeMap = new HeroExchangeMap(this, ai);
|
||||
setupSpecialActors();
|
||||
@@ -94,6 +94,7 @@ void ChainActor::setBaseActor(HeroActor * base)
|
||||
{
|
||||
baseActor = base;
|
||||
hero = base->hero;
|
||||
heroRole = base->heroRole;
|
||||
layer = base->layer;
|
||||
initialMovement = base->initialMovement;
|
||||
initialTurn = base->initialTurn;
|
||||
|
@@ -27,7 +27,7 @@ public:
|
||||
class ChainActor
|
||||
{
|
||||
protected:
|
||||
ChainActor(const CGHeroInstance * hero, uint64_t chainMask);
|
||||
ChainActor(const CGHeroInstance * hero, HeroRole heroRole, uint64_t chainMask);
|
||||
ChainActor(const ChainActor * carrier, const ChainActor * other, const CCreatureSet * heroArmy);
|
||||
ChainActor(const CGObjectInstance * obj, const CCreatureSet * army, uint64_t chainMask, int initialTurn);
|
||||
|
||||
@@ -38,6 +38,7 @@ public:
|
||||
bool allowBattle;
|
||||
bool allowSpellCast;
|
||||
const CGHeroInstance * hero;
|
||||
HeroRole heroRole;
|
||||
const CCreatureSet * creatureSet;
|
||||
const ChainActor * battleActor;
|
||||
const ChainActor * castActor;
|
||||
@@ -105,7 +106,7 @@ public:
|
||||
std::shared_ptr<SpecialAction> exchangeAction;
|
||||
// chain flags, can be combined meaning hero exchange and so on
|
||||
|
||||
HeroActor(const CGHeroInstance * hero, uint64_t chainMask, const Nullkiller * ai);
|
||||
HeroActor(const CGHeroInstance * hero, HeroRole heroRole, uint64_t chainMask, const Nullkiller * ai);
|
||||
HeroActor(const ChainActor * carrier, const ChainActor * other, const CCreatureSet * army, const Nullkiller * ai);
|
||||
|
||||
virtual bool canExchange(const ChainActor * other) const override;
|
||||
|
@@ -38,7 +38,11 @@ namespace AIPathfinding
|
||||
|
||||
auto blocker = getBlockingReason(source, destination, pathfinderConfig, pathfinderHelper);
|
||||
if(blocker == BlockingReason::NONE)
|
||||
{
|
||||
destination.blocked = nodeStorage->isDistanceLimitReached(source, destination);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto destGuardians = cb->getGuardingCreatures(destination.coord);
|
||||
bool allowBypass = false;
|
||||
|
Reference in New Issue
Block a user