|
@@ -64,55 +64,119 @@ 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
|
|
|
{
|
|
|
- boost::this_thread::interruption_point();
|
|
|
+ Goals::TTaskVec result;
|
|
|
|
|
|
- logAi->debug("Checking behavior %s", behavior->toString());
|
|
|
+ for(auto & item : tasks)
|
|
|
+ {
|
|
|
+ result.push_back(taskptr(*item.task));
|
|
|
+ }
|
|
|
|
|
|
- auto start = std::chrono::high_resolution_clock::now();
|
|
|
-
|
|
|
- Goals::TGoalVec elementarGoals = decomposer->decompose(behavior, decompositionMaxDepth);
|
|
|
- Goals::TTaskVec tasks;
|
|
|
+ vstd::removeDuplicates(result);
|
|
|
|
|
|
- boost::this_thread::interruption_point();
|
|
|
-
|
|
|
- for(auto goal : elementarGoals)
|
|
|
- {
|
|
|
- Goals::TTask task = Goals::taskptr(*goal);
|
|
|
+ return result;
|
|
|
+}
|
|
|
|
|
|
- if(task->priority <= 0)
|
|
|
- task->priority = priorityEvaluator->evaluate(goal);
|
|
|
+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;
|
|
|
|
|
|
- tasks.push_back(task);
|
|
|
+ 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())
|
|
|
{
|
|
|
- logAi->debug("Behavior %s found no tasks. Time taken %ld", behavior->toString(), timeElapsed(start));
|
|
|
+ return taskptr(Invalid());
|
|
|
+ }
|
|
|
+
|
|
|
+ for(TSubgoal & task : tasks)
|
|
|
+ {
|
|
|
+ if(task->asTask()->priority <= 0)
|
|
|
+ task->asTask()->priority = priorityEvaluator->evaluate(task);
|
|
|
+ }
|
|
|
|
|
|
- return Goals::taskptr(Goals::Invalid());
|
|
|
+ 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);
|
|
|
}
|
|
|
|
|
|
- auto task = choseBestTask(tasks);
|
|
|
+ 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 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();
|
|
|
+
|
|
|
+ decompose(bestTasks, sptr(BuyArmyBehavior()), 1);
|
|
|
+ decompose(bestTasks, sptr(BuildingBehavior()), 1);
|
|
|
|
|
|
- bestTask = choseBestTask(fastTasks);
|
|
|
+ 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);
|
|
|
+ auto selectedTasks = buildPlan(bestTasks);
|
|
|
|
|
|
- std::string taskDescription = bestTask->toString();
|
|
|
- HeroPtr hero = bestTask->getHero();
|
|
|
- HeroRole heroRole = HeroRole::MAIN;
|
|
|
+ logAi->debug("Decission madel in %ld", timeElapsed(start));
|
|
|
|
|
|
- if(hero.validAndSet())
|
|
|
- heroRole = heroManager->getHeroRole(hero);
|
|
|
+ if(selectedTasks.empty())
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- if(heroRole != HeroRole::MAIN || bestTask->getHeroExchangeCount() <= 1)
|
|
|
- useHeroChain = false;
|
|
|
+ bool hasAnySuccess = 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)
|
|
|
+ for(auto bestTask : selectedTasks)
|
|
|
{
|
|
|
- useHeroChain = false;
|
|
|
- scanDepth = ScanDepth::SMALL;
|
|
|
+ std::string taskDescription = bestTask->toString();
|
|
|
+ HeroPtr hero = bestTask->getHero();
|
|
|
+ HeroRole heroRole = HeroRole::MAIN;
|
|
|
|
|
|
- logAi->trace(
|
|
|
- "Goal %s has low priority %f so decreasing scan depth to gain performance.",
|
|
|
- taskDescription,
|
|
|
- bestTask->priority);
|
|
|
- }
|
|
|
+ if(hero.validAndSet())
|
|
|
+ heroRole = heroManager->getHeroRole(hero);
|
|
|
|
|
|
- if(bestTask->priority < MIN_PRIORITY)
|
|
|
- {
|
|
|
- auto heroes = cb->getHeroesInfo();
|
|
|
- auto hasMp = vstd::contains_if(heroes, [](const CGHeroInstance * h) -> bool
|
|
|
- {
|
|
|
- return h->movementPointsRemaining() > 100;
|
|
|
- });
|
|
|
+ if(heroRole != HeroRole::MAIN || bestTask->getHeroExchangeCount() <= 1)
|
|
|
+ useHeroChain = false;
|
|
|
|
|
|
- if(hasMp && scanDepth != ScanDepth::ALL_FULL)
|
|
|
+ // 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 too low priority %f so increasing scan depth to full.",
|
|
|
+ "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);
|
|
|
|
|
|
- scanDepth = ScanDepth::ALL_FULL;
|
|
|
- useHeroChain = false;
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
- logAi->trace("Goal %s has too low priority. It is not worth doing it. Ending turn.", taskDescription);
|
|
|
+ if(!executeTask(bestTask))
|
|
|
+ {
|
|
|
+ if(hasAnySuccess)
|
|
|
+ break;
|
|
|
+ else
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- return;
|
|
|
+ hasAnySuccess = true;
|
|
|
}
|
|
|
|
|
|
- logAi->debug("Decission madel in %ld", timeElapsed(start));
|
|
|
-
|
|
|
- executeTask(bestTask);
|
|
|
+ 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
|