From e374f24016608b7dfab737353d234c5d6b96e743 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Fri, 19 Jul 2024 15:25:24 +0200 Subject: [PATCH] complete Building-costs as evaluation-context Added building-cost including all resoruces as evaluation-context for more sophisticated building-selection and also as a countermeasure to softlocking a build-order by having no ways to obtain certain resources. For example, if the AI would drop below 5 wood, while having no market-place and no wood-income it will avoid building any buildings that neither allow trading nor produce wood. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 90 +++++++++++++++------- AI/Nullkiller/Engine/PriorityEvaluator.h | 3 +- AI/Nullkiller/Goals/AbstractGoal.h | 1 + 3 files changed, 65 insertions(+), 29 deletions(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 446b60967..6d8a5a122 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -58,8 +58,8 @@ EvaluationContext::EvaluationContext(const Nullkiller* ai) armyGrowth(0), armyInvolvement(0), isDefend(false), - isBuild(false), - involvesSailing(false) + involvesSailing(false), + isTradeBuilding(false) { } @@ -1182,14 +1182,13 @@ public: evaluationContext.heroRole = HeroRole::MAIN; evaluationContext.movementCostByRole[evaluationContext.heroRole] += bi.prerequisitesCount; int32_t cost = bi.buildCostWithPrerequisites[EGameResID::GOLD]; - auto resourcesAvailable = evaluationContext.evaluator.ai->getFreeResources(); - TResources missing = bi.buildCostWithPrerequisites - resourcesAvailable; - missing[EGameResID::GOLD] = 0; - missing.positive(); - cost += missing.marketValue(); evaluationContext.goldCost += cost; evaluationContext.closestWayRatio = 1; - evaluationContext.isBuild = true; + evaluationContext.buildingCost += bi.buildCostWithPrerequisites; + if (bi.id == BuildingID::MARKETPLACE || bi.dailyIncome[EGameResID::WOOD] > 0) + evaluationContext.isTradeBuilding = true; + + logAi->trace("Building costs for %s : %s MarketValue: %d",bi.toString(), evaluationContext.buildingCost.toString(), evaluationContext.buildingCost.marketValue()); if(bi.creatureID != CreatureID::NONE) { @@ -1278,6 +1277,7 @@ EvaluationContext PriorityEvaluator::buildEvaluationContext(Goals::TSubgoal goal for(auto subgoal : parts) { context.goldCost += subgoal->goldCost; + context.buildingCost += subgoal->buildingCost; for(auto builder : evaluationContextBuilders) { @@ -1337,6 +1337,26 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) { float score = 0; float maxWillingToLose = ai->cb->getTownsInfo().empty() ? 1 : 0.25; + + logAi->trace("BEFORE: priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, skill: %f danger: %d, threat: %d, role: %s, strategical value: %f, cwr: %f, fear: %f, fuzzy: %f", + priorityTier, + task->toString(), + evaluationContext.armyLossPersentage, + (int)evaluationContext.turn, + evaluationContext.movementCostByRole[HeroRole::MAIN], + evaluationContext.movementCostByRole[HeroRole::SCOUT], + goldRewardPerTurn, + evaluationContext.goldCost, + evaluationContext.armyReward, + evaluationContext.skillReward, + evaluationContext.danger, + evaluationContext.threat, + evaluationContext.heroRole == HeroRole::MAIN ? "main" : "scout", + evaluationContext.strategicalValue, + evaluationContext.closestWayRatio, + evaluationContext.enemyHeroDangerRatio, + fuzzyResult); + switch (priorityTier) { case 0: //Take towns @@ -1362,33 +1382,27 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) return 0; if (evaluationContext.involvesSailing && evaluationContext.movementCostByRole[HeroRole::MAIN] > 0) return 0; + if (evaluationContext.buildingCost.marketValue() > 0) + return 0; score += evaluationContext.strategicalValue * 1000; score += evaluationContext.goldReward; score += evaluationContext.skillReward * evaluationContext.armyInvolvement * (1 - evaluationContext.armyLossPersentage) * 0.05; score += evaluationContext.armyReward; score += evaluationContext.armyGrowth; - if (evaluationContext.isBuild) - { - score += 1000; - score /= evaluationContext.goldCost; - } + if (score <= 0) + return 0; else - { - if (score <= 0) - return 0; - else - score = 1000; - score *= evaluationContext.closestWayRatio; - if (evaluationContext.threat > evaluationContext.armyInvolvement && !evaluationContext.isDefend) - score *= evaluationContext.armyInvolvement / evaluationContext.threat; - if (evaluationContext.movementCost > 0) - score /= evaluationContext.movementCost; - } + score = 1000; + score *= evaluationContext.closestWayRatio; + if (evaluationContext.threat > evaluationContext.armyInvolvement && !evaluationContext.isDefend) + score *= evaluationContext.armyInvolvement / evaluationContext.threat; + if (evaluationContext.movementCost > 0) + score /= evaluationContext.movementCost; break; } case 2: //Collect guarded stuff { - if (evaluationContext.isBuild) + if (evaluationContext.buildingCost.marketValue() > 0) return 0; if (evaluationContext.enemyHeroDangerRatio > 0.5 && !evaluationContext.isDefend) return 0; @@ -1413,7 +1427,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) } break; } - case 3: //Pre-filter to see if anything is worth to be done at all + case 3: //For buildings and buying army { score += evaluationContext.conquestValue * 1000; score += evaluationContext.strategicalValue * 1000; @@ -1421,10 +1435,30 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) score += evaluationContext.skillReward * evaluationContext.armyInvolvement * (1 - evaluationContext.armyLossPersentage) * 0.05; score += evaluationContext.armyReward; score += evaluationContext.armyGrowth; - if (evaluationContext.isBuild) + if (evaluationContext.buildingCost.marketValue() > 0) { + if (!evaluationContext.isTradeBuilding && ai->getFreeResources()[EGameResID::WOOD] - evaluationContext.buildingCost[EGameResID::WOOD] < 5 && ai->buildAnalyzer->getDailyIncome()[EGameResID::WOOD] < 1) + { + logAi->trace("Should make sure to build market-place instead of %s", task->toString()); + for (auto town : cb->getTownsInfo()) + { + if (!town->hasBuiltSomeTradeBuilding()) + return 0; + } + } score += 1000; - score /= evaluationContext.goldCost; + auto resourcesAvailable = evaluationContext.evaluator.ai->getFreeResources(); + auto income = ai->buildAnalyzer->getDailyIncome(); + if (resourcesAvailable < evaluationContext.buildingCost) + { + TResources needed = evaluationContext.buildingCost - resourcesAvailable; + needed.positive(); + int turnsTo = needed.div(income); + if (turnsTo == INT_MAX) + return 0; + else + score /= turnsTo; + } } break; } diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.h b/AI/Nullkiller/Engine/PriorityEvaluator.h index d4241efdb..2e15a4926 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.h +++ b/AI/Nullkiller/Engine/PriorityEvaluator.h @@ -74,8 +74,9 @@ struct DLL_EXPORT EvaluationContext float threat; float armyInvolvement; bool isDefend; - bool isBuild; + TResources buildingCost; bool involvesSailing; + bool isTradeBuilding; EvaluationContext(const Nullkiller * ai); diff --git a/AI/Nullkiller/Goals/AbstractGoal.h b/AI/Nullkiller/Goals/AbstractGoal.h index b191f96a5..27adf052e 100644 --- a/AI/Nullkiller/Goals/AbstractGoal.h +++ b/AI/Nullkiller/Goals/AbstractGoal.h @@ -104,6 +104,7 @@ namespace Goals bool isAbstract; SETTER(bool, isAbstract) int value; SETTER(int, value) ui64 goldCost; SETTER(ui64, goldCost) + TResources buildingCost; SETTER(TResources, buildingCost) int resID; SETTER(int, resID) int objid; SETTER(int, objid) int aid; SETTER(int, aid)