Nullkiller.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. /*
  2. * Nullkiller.cpp, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #include "StdInc.h"
  11. #include "Nullkiller.h"
  12. #include "../AIGateway.h"
  13. #include "../Behaviors/CaptureObjectsBehavior.h"
  14. #include "../Behaviors/RecruitHeroBehavior.h"
  15. #include "../Behaviors/BuyArmyBehavior.h"
  16. #include "../Behaviors/StartupBehavior.h"
  17. #include "../Behaviors/DefenceBehavior.h"
  18. #include "../Behaviors/BuildingBehavior.h"
  19. #include "../Behaviors/GatherArmyBehavior.h"
  20. #include "../Behaviors/ClusterBehavior.h"
  21. #include "../Behaviors/StayAtTownBehavior.h"
  22. #include "../Goals/Invalid.h"
  23. #include "../Goals/Composition.h"
  24. namespace NKAI
  25. {
  26. using namespace Goals;
  27. // while we play vcmieagles graph can be shared
  28. std::unique_ptr<ObjectGraph> Nullkiller::baseGraph;
  29. Nullkiller::Nullkiller()
  30. :activeHero(nullptr), scanDepth(ScanDepth::MAIN_FULL), useHeroChain(true)
  31. {
  32. memory = std::make_unique<AIMemory>();
  33. settings = std::make_unique<Settings>();
  34. }
  35. void Nullkiller::init(std::shared_ptr<CCallback> cb, AIGateway * gateway)
  36. {
  37. this->cb = cb;
  38. this->gateway = gateway;
  39. this->playerID = gateway->playerID;
  40. baseGraph.reset();
  41. priorityEvaluator.reset(new PriorityEvaluator(this));
  42. priorityEvaluators.reset(
  43. new SharedPool<PriorityEvaluator>(
  44. [&]()->std::unique_ptr<PriorityEvaluator>
  45. {
  46. return std::make_unique<PriorityEvaluator>(this);
  47. }));
  48. dangerHitMap.reset(new DangerHitMapAnalyzer(this));
  49. buildAnalyzer.reset(new BuildAnalyzer(this));
  50. objectClusterizer.reset(new ObjectClusterizer(this));
  51. dangerEvaluator.reset(new FuzzyHelper(this));
  52. pathfinder.reset(new AIPathfinder(cb.get(), this));
  53. armyManager.reset(new ArmyManager(cb.get(), this));
  54. heroManager.reset(new HeroManager(cb.get(), this));
  55. decomposer.reset(new DeepDecomposer(this));
  56. armyFormation.reset(new ArmyFormation(cb, this));
  57. }
  58. TaskPlanItem::TaskPlanItem(TSubgoal task)
  59. :task(task), affectedObjects(task->asTask()->getAffectedObjects())
  60. {
  61. }
  62. Goals::TTaskVec TaskPlan::getTasks() const
  63. {
  64. Goals::TTaskVec result;
  65. for(auto & item : tasks)
  66. {
  67. result.push_back(taskptr(*item.task));
  68. }
  69. vstd::removeDuplicates(result);
  70. return result;
  71. }
  72. void TaskPlan::merge(TSubgoal task)
  73. {
  74. TGoalVec blockers;
  75. for(auto & item : tasks)
  76. {
  77. for(auto objid : item.affectedObjects)
  78. {
  79. if(task == item.task || task->asTask()->isObjectAffected(objid))
  80. {
  81. if(item.task->asTask()->priority >= task->asTask()->priority)
  82. return;
  83. blockers.push_back(item.task);
  84. break;
  85. }
  86. }
  87. }
  88. vstd::erase_if(tasks, [&](const TaskPlanItem & task)
  89. {
  90. return vstd::contains(blockers, task.task);
  91. });
  92. tasks.emplace_back(task);
  93. }
  94. Goals::TTask Nullkiller::choseBestTask(Goals::TGoalVec & tasks) const
  95. {
  96. if(tasks.empty())
  97. {
  98. return taskptr(Invalid());
  99. }
  100. for(TSubgoal & task : tasks)
  101. {
  102. if(task->asTask()->priority <= 0)
  103. task->asTask()->priority = priorityEvaluator->evaluate(task);
  104. }
  105. auto bestTask = *vstd::maxElementByFun(tasks, [](Goals::TSubgoal task) -> float
  106. {
  107. return task->asTask()->priority;
  108. });
  109. return taskptr(*bestTask);
  110. }
  111. Goals::TTaskVec Nullkiller::buildPlan(TGoalVec & tasks) const
  112. {
  113. TaskPlan taskPlan;
  114. tbb::parallel_for(tbb::blocked_range<size_t>(0, tasks.size()), [this, &tasks](const tbb::blocked_range<size_t> & r)
  115. {
  116. auto evaluator = this->priorityEvaluators->acquire();
  117. for(size_t i = r.begin(); i != r.end(); i++)
  118. {
  119. auto task = tasks[i];
  120. if(task->asTask()->priority <= 0)
  121. task->asTask()->priority = evaluator->evaluate(task);
  122. }
  123. });
  124. std::sort(tasks.begin(), tasks.end(), [](TSubgoal g1, TSubgoal g2) -> bool
  125. {
  126. return g2->asTask()->priority < g1->asTask()->priority;
  127. });
  128. for(TSubgoal & task : tasks)
  129. {
  130. taskPlan.merge(task);
  131. }
  132. return taskPlan.getTasks();
  133. }
  134. void Nullkiller::decompose(Goals::TGoalVec & result, Goals::TSubgoal behavior, int decompositionMaxDepth) const
  135. {
  136. boost::this_thread::interruption_point();
  137. logAi->debug("Checking behavior %s", behavior->toString());
  138. auto start = std::chrono::high_resolution_clock::now();
  139. decomposer->decompose(result, behavior, decompositionMaxDepth);
  140. boost::this_thread::interruption_point();
  141. logAi->debug(
  142. "Behavior %s. Time taken %ld",
  143. behavior->toString(),
  144. timeElapsed(start));
  145. }
  146. void Nullkiller::resetAiState()
  147. {
  148. std::unique_lock<std::mutex> lockGuard(aiStateMutex);
  149. lockedResources = TResources();
  150. scanDepth = ScanDepth::MAIN_FULL;
  151. lockedHeroes.clear();
  152. dangerHitMap->reset();
  153. useHeroChain = true;
  154. objectClusterizer->reset();
  155. if(!baseGraph && settings->isObjectGraphAllowed())
  156. {
  157. baseGraph = std::make_unique<ObjectGraph>();
  158. baseGraph->updateGraph(this);
  159. }
  160. }
  161. void Nullkiller::updateAiState(int pass, bool fast)
  162. {
  163. boost::this_thread::interruption_point();
  164. std::unique_lock<std::mutex> lockGuard(aiStateMutex);
  165. auto start = std::chrono::high_resolution_clock::now();
  166. activeHero = nullptr;
  167. setTargetObject(-1);
  168. decomposer->reset();
  169. buildAnalyzer->update();
  170. if(!fast)
  171. {
  172. memory->removeInvisibleObjects(cb.get());
  173. dangerHitMap->updateHitMap();
  174. dangerHitMap->calculateTileOwners();
  175. boost::this_thread::interruption_point();
  176. heroManager->update();
  177. logAi->trace("Updating paths");
  178. std::map<const CGHeroInstance *, HeroRole> activeHeroes;
  179. for(auto hero : cb->getHeroesInfo())
  180. {
  181. if(getHeroLockedReason(hero) == HeroLockedReason::DEFENCE)
  182. continue;
  183. activeHeroes[hero] = heroManager->getHeroRole(hero);
  184. }
  185. PathfinderSettings cfg;
  186. cfg.useHeroChain = useHeroChain;
  187. cfg.allowBypassObjects = true;
  188. if(scanDepth == ScanDepth::SMALL || settings->isObjectGraphAllowed())
  189. {
  190. cfg.mainTurnDistanceLimit = settings->getMainHeroTurnDistanceLimit();
  191. }
  192. if(scanDepth != ScanDepth::ALL_FULL || settings->isObjectGraphAllowed())
  193. {
  194. cfg.scoutTurnDistanceLimit =settings->getScoutHeroTurnDistanceLimit();
  195. }
  196. boost::this_thread::interruption_point();
  197. pathfinder->updatePaths(activeHeroes, cfg);
  198. if(settings->isObjectGraphAllowed())
  199. {
  200. pathfinder->updateGraphs(
  201. activeHeroes,
  202. scanDepth == ScanDepth::SMALL ? 255 : 10,
  203. scanDepth == ScanDepth::ALL_FULL ? 255 : 3);
  204. }
  205. boost::this_thread::interruption_point();
  206. objectClusterizer->clusterize();
  207. }
  208. armyManager->update();
  209. logAi->debug("AI state updated in %ld", timeElapsed(start));
  210. }
  211. bool Nullkiller::isHeroLocked(const CGHeroInstance * hero) const
  212. {
  213. return getHeroLockedReason(hero) != HeroLockedReason::NOT_LOCKED;
  214. }
  215. bool Nullkiller::arePathHeroesLocked(const AIPath & path) const
  216. {
  217. if(getHeroLockedReason(path.targetHero) == HeroLockedReason::STARTUP)
  218. {
  219. #if NKAI_TRACE_LEVEL >= 1
  220. logAi->trace("Hero %s is locked by STARTUP. Discarding %s", path.targetHero->getObjectName(), path.toString());
  221. #endif
  222. return true;
  223. }
  224. for(auto & node : path.nodes)
  225. {
  226. auto lockReason = getHeroLockedReason(node.targetHero);
  227. if(lockReason != HeroLockedReason::NOT_LOCKED)
  228. {
  229. #if NKAI_TRACE_LEVEL >= 1
  230. logAi->trace("Hero %s is locked by STARTUP. Discarding %s", path.targetHero->getObjectName(), path.toString());
  231. #endif
  232. return true;
  233. }
  234. }
  235. return false;
  236. }
  237. HeroLockedReason Nullkiller::getHeroLockedReason(const CGHeroInstance * hero) const
  238. {
  239. auto found = lockedHeroes.find(hero);
  240. return found != lockedHeroes.end() ? found->second : HeroLockedReason::NOT_LOCKED;
  241. }
  242. void Nullkiller::makeTurn()
  243. {
  244. boost::lock_guard<boost::mutex> sharedStorageLock(AISharedStorage::locker);
  245. const int MAX_DEPTH = 10;
  246. const float FAST_TASK_MINIMAL_PRIORITY = 0.7f;
  247. resetAiState();
  248. Goals::TGoalVec bestTasks;
  249. for(int i = 1; i <= settings->getMaxPass() && cb->getPlayerStatus(playerID) == EPlayerStatus::INGAME; i++)
  250. {
  251. auto start = std::chrono::high_resolution_clock::now();
  252. updateAiState(i);
  253. Goals::TTask bestTask = taskptr(Goals::Invalid());
  254. for(;i <= settings->getMaxPass(); i++)
  255. {
  256. bestTasks.clear();
  257. decompose(bestTasks, sptr(BuyArmyBehavior()), 1);
  258. decompose(bestTasks, sptr(BuildingBehavior()), 1);
  259. bestTask = choseBestTask(bestTasks);
  260. if(bestTask->priority >= FAST_TASK_MINIMAL_PRIORITY)
  261. {
  262. if(!executeTask(bestTask))
  263. return;
  264. updateAiState(i, true);
  265. }
  266. else
  267. {
  268. break;
  269. }
  270. }
  271. decompose(bestTasks, sptr(RecruitHeroBehavior()), 1);
  272. decompose(bestTasks, sptr(CaptureObjectsBehavior()), 1);
  273. decompose(bestTasks, sptr(ClusterBehavior()), MAX_DEPTH);
  274. decompose(bestTasks, sptr(DefenceBehavior()), MAX_DEPTH);
  275. decompose(bestTasks, sptr(GatherArmyBehavior()), MAX_DEPTH);
  276. decompose(bestTasks, sptr(StayAtTownBehavior()), MAX_DEPTH);
  277. if(cb->getDate(Date::DAY) == 1)
  278. {
  279. decompose(bestTasks, sptr(StartupBehavior()), 1);
  280. }
  281. auto selectedTasks = buildPlan(bestTasks);
  282. logAi->debug("Decission madel in %ld", timeElapsed(start));
  283. if(selectedTasks.empty())
  284. {
  285. return;
  286. }
  287. bool hasAnySuccess = false;
  288. for(auto bestTask : selectedTasks)
  289. {
  290. if(cb->getPlayerStatus(playerID) != EPlayerStatus::INGAME)
  291. return;
  292. if(!areAffectedObjectsPresent(bestTask))
  293. {
  294. logAi->debug("Affected object not found. Canceling task.");
  295. continue;
  296. }
  297. std::string taskDescription = bestTask->toString();
  298. HeroRole heroRole = getTaskRole(bestTask);
  299. if(heroRole != HeroRole::MAIN || bestTask->getHeroExchangeCount() <= 1)
  300. useHeroChain = false;
  301. // TODO: better to check turn distance here instead of priority
  302. if((heroRole != HeroRole::MAIN || bestTask->priority < SMALL_SCAN_MIN_PRIORITY)
  303. && scanDepth == ScanDepth::MAIN_FULL)
  304. {
  305. useHeroChain = false;
  306. scanDepth = ScanDepth::SMALL;
  307. logAi->trace(
  308. "Goal %s has low priority %f so decreasing scan depth to gain performance.",
  309. taskDescription,
  310. bestTask->priority);
  311. }
  312. if(bestTask->priority < MIN_PRIORITY)
  313. {
  314. auto heroes = cb->getHeroesInfo();
  315. auto hasMp = vstd::contains_if(heroes, [](const CGHeroInstance * h) -> bool
  316. {
  317. return h->movementPointsRemaining() > 100;
  318. });
  319. if(hasMp && scanDepth != ScanDepth::ALL_FULL)
  320. {
  321. logAi->trace(
  322. "Goal %s has too low priority %f so increasing scan depth to full.",
  323. taskDescription,
  324. bestTask->priority);
  325. scanDepth = ScanDepth::ALL_FULL;
  326. useHeroChain = false;
  327. hasAnySuccess = true;
  328. break;;
  329. }
  330. logAi->trace("Goal %s has too low priority. It is not worth doing it.", taskDescription);
  331. continue;
  332. }
  333. if(!executeTask(bestTask))
  334. {
  335. if(hasAnySuccess)
  336. break;
  337. else
  338. return;
  339. }
  340. hasAnySuccess = true;
  341. }
  342. if(!hasAnySuccess)
  343. {
  344. logAi->trace("Nothing was done this turn. Ending turn.");
  345. return;
  346. }
  347. if(i == settings->getMaxPass())
  348. {
  349. logAi->warn("Maxpass exceeded. Terminating AI turn.");
  350. }
  351. }
  352. }
  353. bool Nullkiller::areAffectedObjectsPresent(Goals::TTask task) const
  354. {
  355. auto affectedObjs = task->getAffectedObjects();
  356. for(auto oid : affectedObjs)
  357. {
  358. if(!cb->getObj(oid, false))
  359. return false;
  360. }
  361. return true;
  362. }
  363. HeroRole Nullkiller::getTaskRole(Goals::TTask task) const
  364. {
  365. HeroPtr hero = task->getHero();
  366. HeroRole heroRole = HeroRole::MAIN;
  367. if(hero.validAndSet())
  368. heroRole = heroManager->getHeroRole(hero);
  369. return heroRole;
  370. }
  371. bool Nullkiller::executeTask(Goals::TTask task)
  372. {
  373. auto start = std::chrono::high_resolution_clock::now();
  374. std::string taskDescr = task->toString();
  375. boost::this_thread::interruption_point();
  376. logAi->debug("Trying to realize %s (value %2.3f)", taskDescr, task->priority);
  377. try
  378. {
  379. task->accept(gateway);
  380. logAi->trace("Task %s completed in %lld", taskDescr, timeElapsed(start));
  381. }
  382. catch(goalFulfilledException &)
  383. {
  384. logAi->trace("Task %s completed in %lld", taskDescr, timeElapsed(start));
  385. }
  386. catch(cannotFulfillGoalException & e)
  387. {
  388. logAi->error("Failed to realize subgoal of type %s, I will stop.", taskDescr);
  389. logAi->error("The error message was: %s", e.what());
  390. return false;
  391. }
  392. return true;
  393. }
  394. TResources Nullkiller::getFreeResources() const
  395. {
  396. auto freeRes = cb->getResourceAmount() - lockedResources;
  397. freeRes.positive();
  398. return freeRes;
  399. }
  400. void Nullkiller::lockResources(const TResources & res)
  401. {
  402. lockedResources += res;
  403. }
  404. }