mirror of
https://github.com/vcmi/vcmi.git
synced 2025-11-29 23:07:48 +02:00
NKAI: allow multiple tasks to be executed from one calculation
This commit is contained in:
@@ -64,16 +64,104 @@ void Nullkiller::init(std::shared_ptr<CCallback> cb, AIGateway * gateway)
|
||||
armyFormation.reset(new ArmyFormation(cb, this));
|
||||
}
|
||||
|
||||
Goals::TTask Nullkiller::choseBestTask(Goals::TTaskVec & tasks) const
|
||||
TaskPlanItem::TaskPlanItem(TSubgoal task)
|
||||
:task(task), affectedObjects(task->asTask()->getAffectedObjects())
|
||||
{
|
||||
Goals::TTask bestTask = *vstd::maxElementByFun(tasks, [](Goals::TTask task) -> float{
|
||||
return task->priority;
|
||||
});
|
||||
|
||||
return bestTask;
|
||||
}
|
||||
|
||||
Goals::TTask Nullkiller::choseBestTask(Goals::TSubgoal behavior, int decompositionMaxDepth) const
|
||||
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();
|
||||
|
||||
@@ -81,38 +169,14 @@ Goals::TTask Nullkiller::choseBestTask(Goals::TSubgoal behavior, int decompositi
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
Goals::TGoalVec elementarGoals = decomposer->decompose(behavior, decompositionMaxDepth);
|
||||
Goals::TTaskVec tasks;
|
||||
decomposer->decompose(result, behavior, decompositionMaxDepth);
|
||||
|
||||
boost::this_thread::interruption_point();
|
||||
|
||||
for(auto goal : elementarGoals)
|
||||
{
|
||||
Goals::TTask task = Goals::taskptr(*goal);
|
||||
|
||||
if(task->priority <= 0)
|
||||
task->priority = priorityEvaluator->evaluate(goal);
|
||||
|
||||
tasks.push_back(task);
|
||||
}
|
||||
|
||||
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 %s. Time taken %ld",
|
||||
behavior->toString(),
|
||||
task->toString(),
|
||||
task->priority,
|
||||
timeElapsed(start));
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
void Nullkiller::resetAiState()
|
||||
@@ -124,6 +188,7 @@ void Nullkiller::resetAiState()
|
||||
lockedHeroes.clear();
|
||||
dangerHitMap->reset();
|
||||
useHeroChain = true;
|
||||
objectClusterizer->reset();
|
||||
|
||||
if(!baseGraph && settings->isObjectGraphAllowed())
|
||||
{
|
||||
@@ -251,6 +316,8 @@ void Nullkiller::makeTurn()
|
||||
|
||||
resetAiState();
|
||||
|
||||
Goals::TGoalVec bestTasks;
|
||||
|
||||
for(int i = 1; i <= settings->getMaxPass(); i++)
|
||||
{
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
@@ -260,16 +327,18 @@ void Nullkiller::makeTurn()
|
||||
|
||||
for(;i <= settings->getMaxPass(); i++)
|
||||
{
|
||||
Goals::TTaskVec fastTasks = {
|
||||
choseBestTask(sptr(BuyArmyBehavior()), 1),
|
||||
choseBestTask(sptr(BuildingBehavior()), 1)
|
||||
};
|
||||
bestTasks.clear();
|
||||
|
||||
bestTask = choseBestTask(fastTasks);
|
||||
decompose(bestTasks, sptr(BuyArmyBehavior()), 1);
|
||||
decompose(bestTasks, sptr(BuildingBehavior()), 1);
|
||||
|
||||
bestTask = choseBestTask(bestTasks);
|
||||
|
||||
if(bestTask->priority >= FAST_TASK_MINIMAL_PRIORITY)
|
||||
{
|
||||
executeTask(bestTask);
|
||||
if(!executeTask(bestTask))
|
||||
return;
|
||||
|
||||
updateAiState(i, true);
|
||||
}
|
||||
else
|
||||
@@ -278,83 +347,105 @@ void Nullkiller::makeTurn()
|
||||
}
|
||||
}
|
||||
|
||||
Goals::TTaskVec bestTasks = {
|
||||
bestTask,
|
||||
choseBestTask(sptr(RecruitHeroBehavior()), 1),
|
||||
choseBestTask(sptr(CaptureObjectsBehavior()), 1),
|
||||
choseBestTask(sptr(ClusterBehavior()), MAX_DEPTH),
|
||||
choseBestTask(sptr(DefenceBehavior()), MAX_DEPTH),
|
||||
choseBestTask(sptr(GatherArmyBehavior()), MAX_DEPTH),
|
||||
choseBestTask(sptr(StayAtTownBehavior()), MAX_DEPTH)
|
||||
};
|
||||
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(cb->getDate(Date::DAY) == 1)
|
||||
{
|
||||
bestTasks.push_back(choseBestTask(sptr(StartupBehavior()), 1));
|
||||
decompose(bestTasks, sptr(StartupBehavior()), 1);
|
||||
}
|
||||
|
||||
bestTask = choseBestTask(bestTasks);
|
||||
|
||||
std::string taskDescription = bestTask->toString();
|
||||
HeroPtr hero = bestTask->getHero();
|
||||
HeroRole heroRole = HeroRole::MAIN;
|
||||
|
||||
if(hero.validAndSet())
|
||||
heroRole = heroManager->getHeroRole(hero);
|
||||
|
||||
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;
|
||||
continue;
|
||||
}
|
||||
|
||||
logAi->trace("Goal %s has too low priority. It is not worth doing it. Ending turn.", taskDescription);
|
||||
|
||||
return;
|
||||
}
|
||||
auto selectedTasks = buildPlan(bestTasks);
|
||||
|
||||
logAi->debug("Decission madel in %ld", timeElapsed(start));
|
||||
|
||||
executeTask(bestTask);
|
||||
if(selectedTasks.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool hasAnySuccess = false;
|
||||
|
||||
for(auto bestTask : selectedTasks)
|
||||
{
|
||||
std::string taskDescription = bestTask->toString();
|
||||
HeroPtr hero = bestTask->getHero();
|
||||
HeroRole heroRole = HeroRole::MAIN;
|
||||
|
||||
if(hero.validAndSet())
|
||||
heroRole = heroManager->getHeroRole(hero);
|
||||
|
||||
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("Goal %s exceeded maxpass. Terminating AI turn.", taskDescription);
|
||||
logAi->warn("Maxpass exceeded. Terminating AI turn.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Nullkiller::executeTask(Goals::TTask task)
|
||||
bool Nullkiller::executeTask(Goals::TTask task)
|
||||
{
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
std::string taskDescr = task->toString();
|
||||
@@ -376,10 +467,10 @@ void Nullkiller::executeTask(Goals::TTask task)
|
||||
logAi->error("Failed to realize subgoal of type %s, I will stop.", taskDescr);
|
||||
logAi->error("The error message was: %s", e.what());
|
||||
|
||||
#if NKAI_TRACE_LEVEL == 0
|
||||
throw; // will be recatched and AI turn ended
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TResources Nullkiller::getFreeResources() const
|
||||
|
||||
Reference in New Issue
Block a user