1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-26 08:41:13 +02:00
vcmi/AI/Nullkiller/Engine/Nullkiller.cpp
2021-07-26 21:02:50 +03:00

285 lines
6.4 KiB
C++

/*
* Nullkiller.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "Nullkiller.h"
#include "../VCAI.h"
#include "../Behaviors/CaptureObjectsBehavior.h"
#include "../Behaviors/RecruitHeroBehavior.h"
#include "../Behaviors/BuyArmyBehavior.h"
#include "../Behaviors/StartupBehavior.h"
#include "../Behaviors/DefenceBehavior.h"
#include "../Behaviors/BuildingBehavior.h"
#include "../Behaviors/GatherArmyBehavior.h"
#include "../Behaviors/ClusterBehavior.h"
#include "../Goals/Invalid.h"
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<VCAI> ai;
using namespace Goals;
#if AI_TRACE_LEVEL >= 1
#define MAXPASS 1000000
#else
#define MAXPASS 30
#endif
Nullkiller::Nullkiller()
{
memory.reset(new AIMemory());
}
void Nullkiller::init(std::shared_ptr<CCallback> cb, PlayerColor playerID)
{
this->cb = cb;
this->playerID = playerID;
priorityEvaluator.reset(new PriorityEvaluator(this));
dangerHitMap.reset(new DangerHitMapAnalyzer(this));
buildAnalyzer.reset(new BuildAnalyzer());
objectClusterizer.reset(new ObjectClusterizer(this));
dangerEvaluator.reset(new FuzzyHelper(this));
pathfinder.reset(new AIPathfinder(cb.get(), this));
armyManager.reset(new ArmyManager(cb.get(), this));
heroManager.reset(new HeroManager(cb.get(), this));
}
Goals::TTask Nullkiller::choseBestTask(Goals::TTaskVec & tasks) const
{
Goals::TTask bestTask = *vstd::maxElementByFun(tasks, [](Goals::TTask task) -> float{
return task->priority;
});
return bestTask;
}
Goals::TTask Nullkiller::choseBestTask(Goals::TSubgoal behavior) const
{
logAi->debug("Checking behavior %s", behavior->toString());
const int MAX_DEPTH = 10;
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};
int depth = 0;
while(goals[0].size())
{
TSubgoal current = goals[depth].back();
#if AI_TRACE_LEVEL >= 1
logAi->trace("Decomposing %s, level: %d", current->toString(), depth);
#endif
TGoalVec subgoals = current->decompose();
#if AI_TRACE_LEVEL >= 1
logAi->trace("Found %d goals", subgoals.size());
#endif
if(depth < MAX_DEPTH)
{
goals[depth + 1].clear();
}
for(auto subgoal : subgoals)
{
if(subgoal->isElementar())
{
auto task = taskptr(*subgoal);
#if AI_TRACE_LEVEL >= 1
logAi->trace("Found task %s", task->toString());
#endif
if(task->priority <= 0)
task->priority = priorityEvaluator->evaluate(subgoal);
tasks.push_back(task);
}
else if(depth < MAX_DEPTH)
{
#if AI_TRACE_LEVEL >= 1
logAi->trace("Found abstract goal %s", subgoal->toString());
#endif
goals[depth + 1].push_back(subgoal);
}
}
if(depth < MAX_DEPTH && goals[depth + 1].size())
{
depth++;
}
else
{
goals[depth].pop_back();
while(depth > 0 && goals[depth].empty())
{
depth--;
goals[depth].pop_back();
}
}
}
if(tasks.empty())
{
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. Time taken %ld",
behavior->toString(),
task->toString(),
task->priority,
timeElapsed(start));
return task;
}
void Nullkiller::resetAiState()
{
playerID = ai->playerID;
lockedHeroes.clear();
dangerHitMap->reset();
}
void Nullkiller::updateAiState(int pass)
{
auto start = boost::chrono::high_resolution_clock::now();
activeHero = nullptr;
memory->removeInvisibleObjects(cb.get());
dangerHitMap->updateHitMap();
heroManager->update();
logAi->trace("Updating paths");
std::map<const CGHeroInstance *, HeroRole> activeHeroes;
for(auto hero : cb->getHeroesInfo())
{
if(getHeroLockedReason(hero) == HeroLockedReason::DEFENCE)
continue;
activeHeroes[hero] = heroManager->getHeroRole(hero);
}
PathfinderSettings cfg;
cfg.useHeroChain = true;
pathfinder->updatePaths(activeHeroes, cfg);
armyManager->update();
objectClusterizer->clusterize();
buildAnalyzer->update();
logAi->debug("AI state updated in %ld", timeElapsed(start));
}
bool Nullkiller::isHeroLocked(const CGHeroInstance * hero) const
{
return getHeroLockedReason(hero) != HeroLockedReason::NOT_LOCKED;
}
bool Nullkiller::arePathHeroesLocked(const AIPath & path) const
{
if(getHeroLockedReason(path.targetHero) == HeroLockedReason::STARTUP)
{
#if AI_TRACE_LEVEL >= 1
logAi->trace("Hero %s is locked by STARTUP. Discarding %s", path.targetHero->name, path.toString());
#endif
return true;
}
for(auto & node : path.nodes)
{
auto lockReason = getHeroLockedReason(node.targetHero);
if(lockReason != HeroLockedReason::NOT_LOCKED)
{
#if AI_TRACE_LEVEL >= 1
logAi->trace("Hero %s is locked by STARTUP. Discarding %s", path.targetHero->name, path.toString());
#endif
return true;
}
}
return false;
}
HeroLockedReason Nullkiller::getHeroLockedReason(const CGHeroInstance * hero) const
{
auto found = lockedHeroes.find(hero);
return found != lockedHeroes.end() ? found->second : HeroLockedReason::NOT_LOCKED;
}
void Nullkiller::makeTurn()
{
resetAiState();
for(int i = 1; i <= MAXPASS; i++)
{
updateAiState(i);
Goals::TTaskVec bestTasks = {
choseBestTask(sptr(BuyArmyBehavior())),
choseBestTask(sptr(CaptureObjectsBehavior())),
choseBestTask(sptr(ClusterBehavior())),
choseBestTask(sptr(RecruitHeroBehavior())),
choseBestTask(sptr(DefenceBehavior())),
choseBestTask(sptr(BuildingBehavior())),
choseBestTask(sptr(GatherArmyBehavior()))
};
if(cb->getDate(Date::DAY) == 1)
{
bestTasks.push_back(choseBestTask(sptr(StartupBehavior())));
}
Goals::TTask bestTask = choseBestTask(bestTasks);
if(bestTask->priority < MIN_PRIORITY)
{
logAi->trace("Goal %s has too low priority. It is not worth doing it. Ending turn.", bestTask->toString());
return;
}
logAi->debug("Trying to realize %s (value %2.3f)", bestTask->toString(), bestTask->priority);
try
{
bestTask->accept(ai.get());
}
catch(goalFulfilledException &)
{
logAi->trace("Task %s completed", bestTask->toString());
}
catch(std::exception & e)
{
logAi->debug("Failed to realize subgoal of type %s, I will stop.", bestTask->toString());
logAi->debug("The error message was: %s", e.what());
return;
}
}
}