123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557 |
- /*
- * 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 "../AIGateway.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 "../Behaviors/StayAtTownBehavior.h"
- #include "../Behaviors/ExplorationBehavior.h"
- #include "../Goals/Invalid.h"
- #include "../Goals/Composition.h"
- #include "../../../lib/CPlayerState.h"
- #include "../../lib/StartInfo.h"
- namespace NKAI
- {
- using namespace Goals;
- // while we play vcmieagles graph can be shared
- std::unique_ptr<ObjectGraph> Nullkiller::baseGraph;
- Nullkiller::Nullkiller()
- :activeHero(nullptr), scanDepth(ScanDepth::MAIN_FULL), useHeroChain(true)
- {
- memory = std::make_unique<AIMemory>();
- settings = std::make_unique<Settings>();
- useObjectGraph = settings->isObjectGraphAllowed();
- openMap = settings->isOpenMap() || useObjectGraph;
- }
- bool canUseOpenMap(std::shared_ptr<CCallback> cb, PlayerColor playerID)
- {
- if(!cb->getStartInfo()->extraOptionsInfo.cheatsAllowed)
- {
- return false;
- }
- const TeamState * team = cb->getPlayerTeam(playerID);
- auto hasHumanInTeam = vstd::contains_if(team->players, [cb](PlayerColor teamMateID) -> bool
- {
- return cb->getPlayerState(teamMateID)->isHuman();
- });
- if(hasHumanInTeam)
- {
- return false;
- }
- return cb->getStartInfo()->difficulty >= 3;
- }
- void Nullkiller::init(std::shared_ptr<CCallback> cb, AIGateway * gateway)
- {
- this->cb = cb;
- this->gateway = gateway;
-
- playerID = gateway->playerID;
- if(openMap && !canUseOpenMap(cb, playerID))
- {
- useObjectGraph = false;
- openMap = false;
- }
- baseGraph.reset();
- priorityEvaluator.reset(new PriorityEvaluator(this));
- priorityEvaluators.reset(
- new SharedPool<PriorityEvaluator>(
- [&]()->std::unique_ptr<PriorityEvaluator>
- {
- return std::make_unique<PriorityEvaluator>(this);
- }));
- dangerHitMap.reset(new DangerHitMapAnalyzer(this));
- buildAnalyzer.reset(new BuildAnalyzer(this));
- 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));
- decomposer.reset(new DeepDecomposer(this));
- armyFormation.reset(new ArmyFormation(cb, this));
- }
- TaskPlanItem::TaskPlanItem(TSubgoal task)
- :task(task), affectedObjects(task->asTask()->getAffectedObjects())
- {
- }
- Goals::TTaskVec TaskPlan::getTasks() const
- {
- Goals::TTaskVec result;
- for(auto & item : tasks)
- {
- result.push_back(taskptr(*item.task));
- }
- vstd::removeDuplicates(result);
- return result;
- }
- void TaskPlan::merge(TSubgoal task)
- {
- TGoalVec blockers;
- for(auto & item : tasks)
- {
- for(auto objid : item.affectedObjects)
- {
- if(task == item.task || task->asTask()->isObjectAffected(objid))
- {
- if(item.task->asTask()->priority >= task->asTask()->priority)
- return;
- blockers.push_back(item.task);
- break;
- }
- }
- }
- vstd::erase_if(tasks, [&](const TaskPlanItem & task)
- {
- return vstd::contains(blockers, task.task);
- });
- tasks.emplace_back(task);
- }
- Goals::TTask Nullkiller::choseBestTask(Goals::TGoalVec & tasks) const
- {
- if(tasks.empty())
- {
- return taskptr(Invalid());
- }
- for(TSubgoal & task : tasks)
- {
- if(task->asTask()->priority <= 0)
- task->asTask()->priority = priorityEvaluator->evaluate(task);
- }
- auto bestTask = *vstd::maxElementByFun(tasks, [](Goals::TSubgoal task) -> float
- {
- return task->asTask()->priority;
- });
- return taskptr(*bestTask);
- }
- Goals::TTaskVec Nullkiller::buildPlan(TGoalVec & tasks) const
- {
- TaskPlan taskPlan;
- tbb::parallel_for(tbb::blocked_range<size_t>(0, tasks.size()), [this, &tasks](const tbb::blocked_range<size_t> & r)
- {
- auto evaluator = this->priorityEvaluators->acquire();
- for(size_t i = r.begin(); i != r.end(); i++)
- {
- auto task = tasks[i];
- if(task->asTask()->priority <= 0)
- task->asTask()->priority = evaluator->evaluate(task);
- }
- });
- std::sort(tasks.begin(), tasks.end(), [](TSubgoal g1, TSubgoal g2) -> bool
- {
- return g2->asTask()->priority < g1->asTask()->priority;
- });
- for(TSubgoal & task : tasks)
- {
- taskPlan.merge(task);
- }
- return taskPlan.getTasks();
- }
- void Nullkiller::decompose(Goals::TGoalVec & result, Goals::TSubgoal behavior, int decompositionMaxDepth) const
- {
- boost::this_thread::interruption_point();
- logAi->debug("Checking behavior %s", behavior->toString());
- auto start = std::chrono::high_resolution_clock::now();
-
- decomposer->decompose(result, behavior, decompositionMaxDepth);
- boost::this_thread::interruption_point();
- logAi->debug(
- "Behavior %s. Time taken %ld",
- behavior->toString(),
- timeElapsed(start));
- }
- void Nullkiller::resetAiState()
- {
- std::unique_lock lockGuard(aiStateMutex);
- lockedResources = TResources();
- scanDepth = ScanDepth::MAIN_FULL;
- lockedHeroes.clear();
- dangerHitMap->reset();
- useHeroChain = true;
- objectClusterizer->reset();
- if(!baseGraph && isObjectGraphAllowed())
- {
- baseGraph = std::make_unique<ObjectGraph>();
- baseGraph->updateGraph(this);
- }
- }
- void Nullkiller::updateAiState(int pass, bool fast)
- {
- boost::this_thread::interruption_point();
- std::unique_lock lockGuard(aiStateMutex);
- auto start = std::chrono::high_resolution_clock::now();
- activeHero = nullptr;
- setTargetObject(-1);
- decomposer->reset();
- buildAnalyzer->update();
- if(!fast)
- {
- memory->removeInvisibleObjects(cb.get());
- dangerHitMap->updateHitMap();
- dangerHitMap->calculateTileOwners();
- boost::this_thread::interruption_point();
- 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 = useHeroChain;
- cfg.allowBypassObjects = true;
- if(scanDepth == ScanDepth::SMALL || isObjectGraphAllowed())
- {
- cfg.mainTurnDistanceLimit = settings->getMainHeroTurnDistanceLimit();
- }
- if(scanDepth != ScanDepth::ALL_FULL || isObjectGraphAllowed())
- {
- cfg.scoutTurnDistanceLimit =settings->getScoutHeroTurnDistanceLimit();
- }
- boost::this_thread::interruption_point();
- pathfinder->updatePaths(activeHeroes, cfg);
- if(isObjectGraphAllowed())
- {
- pathfinder->updateGraphs(
- activeHeroes,
- scanDepth == ScanDepth::SMALL ? 255 : 10,
- scanDepth == ScanDepth::ALL_FULL ? 255 : 3);
- }
- boost::this_thread::interruption_point();
- objectClusterizer->clusterize();
- }
- armyManager->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 NKAI_TRACE_LEVEL >= 1
- logAi->trace("Hero %s is locked by STARTUP. Discarding %s", path.targetHero->getObjectName(), path.toString());
- #endif
- return true;
- }
- for(auto & node : path.nodes)
- {
- auto lockReason = getHeroLockedReason(node.targetHero);
- if(lockReason != HeroLockedReason::NOT_LOCKED)
- {
- #if NKAI_TRACE_LEVEL >= 1
- logAi->trace("Hero %s is locked by STARTUP. Discarding %s", path.targetHero->getObjectName(), 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()
- {
- boost::lock_guard<boost::mutex> sharedStorageLock(AISharedStorage::locker);
- const int MAX_DEPTH = 10;
- const float FAST_TASK_MINIMAL_PRIORITY = 0.7f;
- resetAiState();
- Goals::TGoalVec bestTasks;
- for(int i = 1; i <= settings->getMaxPass() && cb->getPlayerStatus(playerID) == EPlayerStatus::INGAME; i++)
- {
- auto start = std::chrono::high_resolution_clock::now();
- updateAiState(i);
- Goals::TTask bestTask = taskptr(Goals::Invalid());
- for(;i <= settings->getMaxPass(); i++)
- {
- bestTasks.clear();
- decompose(bestTasks, sptr(BuyArmyBehavior()), 1);
- decompose(bestTasks, sptr(BuildingBehavior()), 1);
- bestTask = choseBestTask(bestTasks);
- if(bestTask->priority >= FAST_TASK_MINIMAL_PRIORITY)
- {
- if(!executeTask(bestTask))
- return;
- updateAiState(i, true);
- }
- else
- {
- break;
- }
- }
- decompose(bestTasks, sptr(RecruitHeroBehavior()), 1);
- decompose(bestTasks, sptr(CaptureObjectsBehavior()), 1);
- decompose(bestTasks, sptr(ClusterBehavior()), MAX_DEPTH);
- decompose(bestTasks, sptr(DefenceBehavior()), MAX_DEPTH);
- decompose(bestTasks, sptr(GatherArmyBehavior()), MAX_DEPTH);
- decompose(bestTasks, sptr(StayAtTownBehavior()), MAX_DEPTH);
- if(!isOpenMap())
- decompose(bestTasks, sptr(ExplorationBehavior()), MAX_DEPTH);
- if(cb->getDate(Date::DAY) == 1 || heroManager->getHeroRoles().empty())
- {
- decompose(bestTasks, sptr(StartupBehavior()), 1);
- }
- auto selectedTasks = buildPlan(bestTasks);
- logAi->debug("Decision madel in %ld", timeElapsed(start));
- if(selectedTasks.empty())
- {
- selectedTasks.push_back(taskptr(Goals::Invalid()));
- }
- bool hasAnySuccess = false;
- for(auto bestTask : selectedTasks)
- {
- if(cb->getPlayerStatus(playerID) != EPlayerStatus::INGAME)
- return;
- if(!areAffectedObjectsPresent(bestTask))
- {
- logAi->debug("Affected object not found. Canceling task.");
- continue;
- }
- std::string taskDescription = bestTask->toString();
- HeroRole heroRole = getTaskRole(bestTask);
- if(heroRole != HeroRole::MAIN || bestTask->getHeroExchangeCount() <= 1)
- useHeroChain = false;
- // TODO: better to check turn distance here instead of priority
- if((heroRole != HeroRole::MAIN || bestTask->priority < SMALL_SCAN_MIN_PRIORITY)
- && scanDepth == ScanDepth::MAIN_FULL)
- {
- useHeroChain = false;
- scanDepth = ScanDepth::SMALL;
- logAi->trace(
- "Goal %s has low priority %f so decreasing scan depth to gain performance.",
- taskDescription,
- bestTask->priority);
- }
- if(bestTask->priority < MIN_PRIORITY)
- {
- auto heroes = cb->getHeroesInfo();
- auto hasMp = vstd::contains_if(heroes, [](const CGHeroInstance * h) -> bool
- {
- return h->movementPointsRemaining() > 100;
- });
- if(hasMp && scanDepth != ScanDepth::ALL_FULL)
- {
- logAi->trace(
- "Goal %s has too low priority %f so increasing scan depth to full.",
- taskDescription,
- bestTask->priority);
- scanDepth = ScanDepth::ALL_FULL;
- useHeroChain = false;
- hasAnySuccess = true;
- break;
- }
- logAi->trace("Goal %s has too low priority. It is not worth doing it.", taskDescription);
- continue;
- }
- if(!executeTask(bestTask))
- {
- if(hasAnySuccess)
- break;
- else
- return;
- }
- hasAnySuccess = true;
- }
- if(!hasAnySuccess)
- {
- logAi->trace("Nothing was done this turn. Ending turn.");
- return;
- }
- if(i == settings->getMaxPass())
- {
- logAi->warn("Maxpass exceeded. Terminating AI turn.");
- }
- }
- }
- bool Nullkiller::areAffectedObjectsPresent(Goals::TTask task) const
- {
- auto affectedObjs = task->getAffectedObjects();
- for(auto oid : affectedObjs)
- {
- if(!cb->getObj(oid, false))
- return false;
- }
- return true;
- }
- HeroRole Nullkiller::getTaskRole(Goals::TTask task) const
- {
- HeroPtr hero = task->getHero();
- HeroRole heroRole = HeroRole::MAIN;
- if(hero.validAndSet())
- heroRole = heroManager->getHeroRole(hero);
- return heroRole;
- }
- bool Nullkiller::executeTask(Goals::TTask task)
- {
- auto start = std::chrono::high_resolution_clock::now();
- std::string taskDescr = task->toString();
- boost::this_thread::interruption_point();
- logAi->debug("Trying to realize %s (value %2.3f)", taskDescr, task->priority);
- try
- {
- task->accept(gateway);
- logAi->trace("Task %s completed in %lld", taskDescr, timeElapsed(start));
- }
- catch(goalFulfilledException &)
- {
- logAi->trace("Task %s completed in %lld", taskDescr, timeElapsed(start));
- }
- catch(cannotFulfillGoalException & e)
- {
- logAi->error("Failed to realize subgoal of type %s, I will stop.", taskDescr);
- logAi->error("The error message was: %s", e.what());
- return false;
- }
- return true;
- }
- TResources Nullkiller::getFreeResources() const
- {
- auto freeRes = cb->getResourceAmount() - lockedResources;
- freeRes.positive();
- return freeRes;
- }
- void Nullkiller::lockResources(const TResources & res)
- {
- lockedResources += res;
- }
- }
|