mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-14 10:12:59 +02:00
Decisionmaking-changes
There's 3 new evaluation-contexts that are now taken into account: Whether an action is building, whether an action involves sailing and the newly introduced threat. The value-evaluation of creatures now also takes special resources into account. No longer treating other AIs differently than players when it comes to how afraid we shall be of them. The cost of buildings for decision-making now determines missing resources. Available resources are ignored when it comes to how they impact the cost. But missing-resources will heftily impact the assumed price by calculating their market-value. This shall encourage the AI to rather build what it currently can build instead of saving up for something that it lacking the special resources for. AI is no longer willing to sacrifice more than 25% of their army for any attack except when it has no towns left. Revamped the priority-tiers of AI decision-making. Higest priority is conquering enemy towns and killing enemy heroes. However, the AI will no longer try to do so when the target is more than one turn away and protected by a nearby enemy-hero that could kill the one tasked with dealing with the target. Except when they have no towns left. Then they get desperate and try everything. As a general rule of thumb one could say the AI will prioritize conquest over collecting freebies over investing army to get something that isn't a city. It's a bit more complex than that but this is roughly what can be expected. It will also highly value their own heroes safety during all this.
This commit is contained in:
parent
95ba57dfe2
commit
4a552d411c
@ -54,9 +54,12 @@ EvaluationContext::EvaluationContext(const Nullkiller* ai)
|
||||
conquestValue(0),
|
||||
evaluator(ai),
|
||||
enemyHeroDangerRatio(0),
|
||||
threat(0),
|
||||
armyGrowth(0),
|
||||
armyInvolvement(0),
|
||||
isDefend(false)
|
||||
isDefend(false),
|
||||
isBuild(false),
|
||||
involvesSailing(false)
|
||||
{
|
||||
}
|
||||
|
||||
@ -246,7 +249,7 @@ int getDwellingArmyCost(const CGObjectInstance * target)
|
||||
auto creature = creLevel.second.back().toCreature();
|
||||
auto creaturesAreFree = creature->getLevel() == 1;
|
||||
if(!creaturesAreFree)
|
||||
cost += creature->getRecruitCost(EGameResID::GOLD) * creLevel.first;
|
||||
cost += creature->getFullRecruitCost().marketValue() * creLevel.first;
|
||||
}
|
||||
}
|
||||
|
||||
@ -686,7 +689,7 @@ int32_t getArmyCost(const CArmedInstance * army)
|
||||
|
||||
for(auto stack : army->Slots())
|
||||
{
|
||||
value += stack.second->getCreatureID().toCreature()->getRecruitCost(EGameResID::GOLD) * stack.second->count;
|
||||
value += stack.second->getCreatureID().toCreature()->getFullRecruitCost().marketValue() * stack.second->count;
|
||||
}
|
||||
|
||||
return value;
|
||||
@ -823,15 +826,8 @@ void addTileDanger(EvaluationContext & evaluationContext, const int3 & tile, uin
|
||||
if(enemyDanger.danger)
|
||||
{
|
||||
auto dangerRatio = enemyDanger.danger / (double)ourStrength;
|
||||
auto enemyHero = evaluationContext.evaluator.ai->cb->getObj(enemyDanger.hero.hid, false);
|
||||
bool isAI = enemyHero && isAnotherAi(enemyHero, *evaluationContext.evaluator.ai->cb);
|
||||
|
||||
if(isAI)
|
||||
{
|
||||
dangerRatio *= 1.5; // lets make AI bit more afraid of other AI.
|
||||
}
|
||||
|
||||
vstd::amax(evaluationContext.enemyHeroDangerRatio, dangerRatio);
|
||||
vstd::amax(evaluationContext.threat, enemyDanger.threat);
|
||||
}
|
||||
}
|
||||
|
||||
@ -907,6 +903,8 @@ public:
|
||||
for(auto & node : path.nodes)
|
||||
{
|
||||
vstd::amax(costsPerHero[node.targetHero], node.cost);
|
||||
if (node.layer == EPathfindingLayer::SAIL)
|
||||
evaluationContext.involvesSailing = true;
|
||||
}
|
||||
|
||||
for(auto pair : costsPerHero)
|
||||
@ -1056,8 +1054,15 @@ public:
|
||||
evaluationContext.goldReward += 7 * bi.dailyIncome[EGameResID::GOLD] / 2; // 7 day income but half we already have
|
||||
evaluationContext.heroRole = HeroRole::MAIN;
|
||||
evaluationContext.movementCostByRole[evaluationContext.heroRole] += bi.prerequisitesCount;
|
||||
evaluationContext.goldCost += bi.buildCostWithPrerequisites[EGameResID::GOLD];
|
||||
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;
|
||||
|
||||
if(bi.creatureID != CreatureID::NONE)
|
||||
{
|
||||
@ -1204,37 +1209,105 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
||||
else
|
||||
{
|
||||
float score = 0;
|
||||
if (priorityTier == 0)
|
||||
float maxWillingToLose = ai->cb->getTownsInfo().empty() ? 1 : 0.25;
|
||||
switch (priorityTier)
|
||||
{
|
||||
score += evaluationContext.conquestValue * 1000;
|
||||
if (score == 0 || (evaluationContext.enemyHeroDangerRatio > 1 && evaluationContext.movementCost > 1))
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (evaluationContext.enemyHeroDangerRatio > 1 && !evaluationContext.isDefend && evaluationContext.movementCost <= 1)
|
||||
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;
|
||||
score -= evaluationContext.goldCost;
|
||||
}
|
||||
if (score > 0)
|
||||
{
|
||||
score *= evaluationContext.closestWayRatio;
|
||||
if (evaluationContext.enemyHeroDangerRatio > 1)
|
||||
score /= evaluationContext.enemyHeroDangerRatio;
|
||||
if (evaluationContext.movementCost > 0)
|
||||
score /= evaluationContext.movementCost;
|
||||
score *= (1 - evaluationContext.armyLossPersentage);
|
||||
result = score;
|
||||
case 0: //Take towns
|
||||
{
|
||||
score += evaluationContext.conquestValue * 1000;
|
||||
if (score == 0 || (evaluationContext.enemyHeroDangerRatio > 0.5 && evaluationContext.turn > 0 && !ai->cb->getTownsInfo().empty()))
|
||||
return 0;
|
||||
score *= evaluationContext.closestWayRatio;
|
||||
if (evaluationContext.threat > evaluationContext.armyInvolvement && !evaluationContext.isDefend)
|
||||
score *= evaluationContext.armyInvolvement / evaluationContext.threat;
|
||||
if (evaluationContext.movementCost > 0)
|
||||
score /= evaluationContext.movementCost;
|
||||
score *= (maxWillingToLose - evaluationContext.armyLossPersentage);
|
||||
break;
|
||||
}
|
||||
case 1: //Collect unguarded stuff
|
||||
{
|
||||
if (evaluationContext.enemyHeroDangerRatio > 0.5 && !evaluationContext.isDefend)
|
||||
return 0;
|
||||
if (evaluationContext.isDefend && evaluationContext.enemyHeroDangerRatio == 0)
|
||||
return 0;
|
||||
if (evaluationContext.armyLossPersentage > 0)
|
||||
return 0;
|
||||
if (evaluationContext.involvesSailing && evaluationContext.movementCostByRole[HeroRole::MAIN] > 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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2: //Collect guarded stuff
|
||||
{
|
||||
if (evaluationContext.isBuild)
|
||||
return 0;
|
||||
if (evaluationContext.enemyHeroDangerRatio > 0.5 && !evaluationContext.isDefend)
|
||||
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;
|
||||
score -= evaluationContext.goldCost;
|
||||
score -= evaluationContext.armyInvolvement * evaluationContext.armyLossPersentage;
|
||||
if (score > 0)
|
||||
{
|
||||
if (evaluationContext.enemyHeroDangerRatio > 1)
|
||||
if (evaluationContext.threat > 0)
|
||||
score = evaluationContext.armyInvolvement / evaluationContext.threat;
|
||||
score *= evaluationContext.closestWayRatio;
|
||||
if (evaluationContext.threat > evaluationContext.armyInvolvement && !evaluationContext.isDefend)
|
||||
score *= evaluationContext.armyInvolvement / evaluationContext.threat;
|
||||
if (evaluationContext.movementCost > 0)
|
||||
score /= evaluationContext.movementCost;
|
||||
score *= (maxWillingToLose - evaluationContext.armyLossPersentage);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 3: //Pre-filter to see if anything is worth to be done at all
|
||||
{
|
||||
score += evaluationContext.conquestValue * 1000;
|
||||
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;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
result = score;
|
||||
}
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, skill: %f danger: %d, role: %s, strategical value: %f, cwr: %f, fear: %f, result %f",
|
||||
logAi->trace("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, result %f",
|
||||
priorityTier,
|
||||
task->toString(),
|
||||
evaluationContext.armyLossPersentage,
|
||||
(int)evaluationContext.turn,
|
||||
@ -1245,10 +1318,12 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
||||
evaluationContext.armyReward,
|
||||
evaluationContext.skillReward,
|
||||
evaluationContext.danger,
|
||||
evaluationContext.threat,
|
||||
evaluationContext.heroRole == HeroRole::MAIN ? "main" : "scout",
|
||||
evaluationContext.strategicalValue,
|
||||
evaluationContext.closestWayRatio,
|
||||
evaluationContext.enemyHeroDangerRatio,
|
||||
fuzzyResult,
|
||||
result);
|
||||
#endif
|
||||
|
||||
|
@ -70,8 +70,11 @@ struct DLL_EXPORT EvaluationContext
|
||||
uint8_t turn;
|
||||
RewardEvaluator evaluator;
|
||||
float enemyHeroDangerRatio;
|
||||
float threat;
|
||||
float armyInvolvement;
|
||||
bool isDefend;
|
||||
bool isBuild;
|
||||
bool involvesSailing;
|
||||
|
||||
EvaluationContext(const Nullkiller * ai);
|
||||
|
||||
@ -94,7 +97,7 @@ public:
|
||||
~PriorityEvaluator();
|
||||
void initVisitTile();
|
||||
|
||||
float evaluate(Goals::TSubgoal task, int priorityTier = 1);
|
||||
float evaluate(Goals::TSubgoal task, int priorityTier = 3);
|
||||
|
||||
private:
|
||||
const Nullkiller * ai;
|
||||
|
Loading…
Reference in New Issue
Block a user