1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-08-10 22:31:40 +02:00

Various Evaluation improvements

AI-heroes are reduce their confidence for attacks based on how many of the units they currently have stacks from exist somewhere else in their empire. So for example if an AI has 2 black-drakes while 3 more are waiting in their castle, they act as if their army was only 2/5th as strong as it is. This should make them more likely to first reinforce their army before attacking.

Changed the formula of scoring dwelling-upgrades in a way that doesn't overvalue upgrading vs. producing new units so much.

AI will no longer be so overconfident when fighting enemy heroes near their own castle. This lead to a lot of horrible trades, especially between different AI which made FFAs way easier in the long run.

AI should be better at preventing their enemies finding their towns undefended due to considering enemies that are not immediately in their castle's range too.

If only gold is missing but no special-resources the AI will no longer devalue what building it wants to build next and build something cheaper instead. This should lead to AI quicker reaching their tier 7 units and skipping some unnecessary lower tiers on the way to it.
This commit is contained in:
Xilmi
2024-12-28 12:48:13 +01:00
committed by Ivan Savenko
parent 6c4996ff54
commit 765658ec83
2 changed files with 74 additions and 17 deletions

View File

@@ -66,7 +66,8 @@ EvaluationContext::EvaluationContext(const Nullkiller* ai)
isArmyUpgrade(false),
isHero(false),
isEnemy(false),
explorePriority(0)
explorePriority(0),
powerRatio(0)
{
}
@@ -1018,6 +1019,45 @@ public:
if(heroRole == HeroRole::MAIN)
evaluationContext.heroRole = heroRole;
if (hero)
{
// Assuming Slots() returns a collection of slots with slot.second->getCreatureID() and slot.second->getPower()
float heroPower = 0;
float totalPower = 0;
// Map to store the aggregated power of creatures by CreatureID
std::map<int, float> totalPowerByCreatureID;
// Calculate hero power and total power by CreatureID
for (auto slot : hero->Slots())
{
int creatureID = slot.second->getCreatureID();
float slotPower = slot.second->getPower();
// Add the power of this slot to the heroPower
heroPower += slotPower;
// Accumulate the total power for the specific CreatureID
if (totalPowerByCreatureID.find(creatureID) == totalPowerByCreatureID.end())
{
// First time encountering this CreatureID, retrieve total creatures' power
totalPowerByCreatureID[creatureID] = ai->armyManager->getTotalCreaturesAvailable(creatureID).power;
}
}
// Calculate total power based on unique CreatureIDs
for (const auto& entry : totalPowerByCreatureID)
{
totalPower += entry.second;
}
// Compute the power ratio if total power is greater than zero
if (totalPower > 0)
{
evaluationContext.powerRatio = heroPower / totalPower;
}
}
if (target)
{
evaluationContext.goldReward += evaluationContext.evaluator.getGoldReward(target, hero);
@@ -1190,7 +1230,10 @@ public:
auto potentialUpgradeValue = evaluationContext.evaluator.getUpgradeArmyReward(buildThis.town, bi);
evaluationContext.addNonCriticalStrategicalValue(potentialUpgradeValue / 10000.0f / (float)bi.prerequisitesCount);
evaluationContext.armyReward += potentialUpgradeValue / (float)bi.prerequisitesCount;
if(bi.id.IsDwelling())
evaluationContext.armyReward += bi.armyStrength - evaluationContext.evaluator.ai->armyManager->evaluateStackPower(bi.baseCreatureID.toCreature(), bi.creatureGrows);
else //This is for prerequisite-buildings
evaluationContext.armyReward += evaluationContext.evaluator.ai->armyManager->evaluateStackPower(bi.baseCreatureID.toCreature(), bi.creatureGrows);
}
}
else if(bi.id == BuildingID::CITADEL || bi.id == BuildingID::CASTLE)
@@ -1333,15 +1376,17 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
else
{
float score = 0;
const bool amIInDanger = ai->cb->getTownsInfo().empty() || (evaluationContext.isDefend && evaluationContext.threatTurns == 0);
const float maxWillingToLose = amIInDanger ? 1 : ai->settings->getMaxArmyLossTarget();
const bool amIInDanger = ai->cb->getTownsInfo().empty();
const float maxWillingToLose = amIInDanger ? 1 : ai->settings->getMaxArmyLossTarget() * evaluationContext.powerRatio > 0 ? evaluationContext.powerRatio : 1.0;
float dangerThreshold = 1;
dangerThreshold *= evaluationContext.powerRatio > 0 ? evaluationContext.powerRatio : 1.0;
bool arriveNextWeek = false;
if (ai->cb->getDate(Date::DAY_OF_WEEK) + evaluationContext.turn > 7 && priorityTier < PriorityTier::FAR_KILL)
arriveNextWeek = true;
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("BEFORE: priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, army-involvement: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threatTurns: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, explorePriority: %d isDefend: %d isEnemy: %d arriveNextWeek: %d",
logAi->trace("BEFORE: priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, army-involvement: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threatTurns: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, explorePriority: %d isDefend: %d isEnemy: %d arriveNextWeek: %d powerRatio: %f",
priorityTier,
task->toString(),
evaluationContext.armyLossPersentage,
@@ -1365,7 +1410,8 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
evaluationContext.explorePriority,
evaluationContext.isDefend,
evaluationContext.isEnemy,
arriveNextWeek);
arriveNextWeek,
evaluationContext.powerRatio);
#endif
switch (priorityTier)
@@ -1378,7 +1424,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
return 0;
if(evaluationContext.conquestValue > 0)
score = evaluationContext.armyInvolvement;
if (vstd::isAlmostZero(score) || (evaluationContext.enemyHeroDangerRatio > 1 && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty()))
if (vstd::isAlmostZero(score) || (evaluationContext.enemyHeroDangerRatio > dangerThreshold && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty()))
return 0;
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
return 0;
@@ -1388,7 +1434,11 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
}
case PriorityTier::INSTADEFEND: //Defend immediately threatened towns
{
if (evaluationContext.isDefend && evaluationContext.threatTurns == 0 && evaluationContext.turn == 0)
if (evaluationContext.closestWayRatio < 1.0)
return 0;
if (evaluationContext.isEnemy && evaluationContext.turn > 0)
return 0;
if (evaluationContext.isDefend && evaluationContext.threatTurns <= evaluationContext.turn)
score = evaluationContext.armyInvolvement;
if (evaluationContext.isEnemy && maxWillingToLose - evaluationContext.armyLossPersentage < 0)
return 0;
@@ -1404,7 +1454,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
return 0;
if (evaluationContext.conquestValue > 0)
score = evaluationContext.armyInvolvement;
if (vstd::isAlmostZero(score) || (evaluationContext.enemyHeroDangerRatio > 1 && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty()))
if (vstd::isAlmostZero(score) || (evaluationContext.enemyHeroDangerRatio > dangerThreshold && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty()))
return 0;
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
return 0;
@@ -1417,7 +1467,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
{
if (!evaluationContext.isArmyUpgrade)
return 0;
if (evaluationContext.enemyHeroDangerRatio > 1)
if (evaluationContext.enemyHeroDangerRatio > dangerThreshold)
return 0;
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
return 0;
@@ -1430,7 +1480,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
}
case PriorityTier::HIGH_PRIO_EXPLORE:
{
if (evaluationContext.enemyHeroDangerRatio > 1)
if (evaluationContext.enemyHeroDangerRatio > dangerThreshold)
return 0;
if (evaluationContext.explorePriority != 1)
return 0;
@@ -1447,17 +1497,17 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
//FALL_THROUGH
case PriorityTier::FAR_HUNTER_GATHER:
{
if (evaluationContext.enemyHeroDangerRatio > 1 && !evaluationContext.isDefend)
if (evaluationContext.enemyHeroDangerRatio > dangerThreshold && !evaluationContext.isDefend)
return 0;
if (evaluationContext.buildingCost.marketValue() > 0)
return 0;
if (evaluationContext.isDefend && (evaluationContext.enemyHeroDangerRatio < 1 || evaluationContext.threatTurns > 0 || evaluationContext.turn > 0))
if (evaluationContext.isDefend && (evaluationContext.enemyHeroDangerRatio < dangerThreshold || evaluationContext.threatTurns > 0 || evaluationContext.turn > 0))
return 0;
if (evaluationContext.explorePriority == 3)
return 0;
if (evaluationContext.isArmyUpgrade)
return 0;
if ((evaluationContext.enemyHeroDangerRatio > 0 && arriveNextWeek) || evaluationContext.enemyHeroDangerRatio > 1)
if ((evaluationContext.enemyHeroDangerRatio > 0 && arriveNextWeek) || evaluationContext.enemyHeroDangerRatio > dangerThreshold)
return 0;
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
return 0;
@@ -1480,7 +1530,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
}
case PriorityTier::LOW_PRIO_EXPLORE:
{
if (evaluationContext.enemyHeroDangerRatio > 1)
if (evaluationContext.enemyHeroDangerRatio > dangerThreshold)
return 0;
if (evaluationContext.explorePriority != 3)
return 0;
@@ -1495,7 +1545,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
}
case PriorityTier::DEFEND: //Defend whatever if nothing else is to do
{
if (evaluationContext.enemyHeroDangerRatio > 1 && evaluationContext.isExchange)
if (evaluationContext.enemyHeroDangerRatio > dangerThreshold && evaluationContext.isExchange)
return 0;
if (evaluationContext.isDefend || evaluationContext.isArmyUpgrade)
score = evaluationContext.armyInvolvement;
@@ -1536,9 +1586,15 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
TResources needed = evaluationContext.buildingCost - resourcesAvailable;
needed.positive();
int turnsTo = needed.maxPurchasableCount(income);
bool haveEverythingButGold = true;
for (int i = 0; i < GameConstants::RESOURCE_QUANTITY; i++)
{
if (i != GameResID::GOLD && resourcesAvailable[i] < evaluationContext.buildingCost[i])
haveEverythingButGold = false;
}
if (turnsTo == INT_MAX)
return 0;
else
if (!haveEverythingButGold)
score /= turnsTo;
}
}

View File

@@ -84,6 +84,7 @@ struct DLL_EXPORT EvaluationContext
bool isHero;
bool isEnemy;
int explorePriority;
float powerRatio;
EvaluationContext(const Nullkiller * ai);