| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576 | /** 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 sharedstd::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 true;}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;	if (task->asTask()->priority <= 0)		return;	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, int priorityTier) const{	TaskPlan taskPlan;	tbb::parallel_for(tbb::blocked_range<size_t>(0, tasks.size()), [this, &tasks, priorityTier](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, priorityTier);			}		});	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;	float totalHeroStrength = 0;	int totalTownLevel = 0;	for (auto heroInfo : cb->getHeroesInfo())	{		totalHeroStrength += heroInfo->getTotalStrength();	}	for (auto townInfo : cb->getTownsInfo())	{		totalTownLevel += townInfo->getTownLevel();	}	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(RecruitHeroBehavior()), 1);			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(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, 0);		if (selectedTasks.empty() && !settings->isUseFuzzy())			selectedTasks = buildPlan(bestTasks, 1);		std::sort(selectedTasks.begin(), selectedTasks.end(), [](const TTask& a, const TTask& b) 		{			return a->priority > b->priority;		});		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((settings->isUseFuzzy() && bestTask->priority < MIN_PRIORITY) || (!settings->isUseFuzzy() && bestTask->priority <= 0))			{				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;}}
 |