mirror of
https://github.com/vcmi/vcmi.git
synced 2025-01-26 03:52:01 +02:00
BattleAI: improve movement towards unreachable
This commit is contained in:
parent
26609d7a4f
commit
bc80532f29
@ -201,6 +201,8 @@ int64_t AttackPossibility::evaluateBlockedShootersDmg(
|
|||||||
if(attackInfo.shooting)
|
if(attackInfo.shooting)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
std::set<uint32_t> checkedUnits;
|
||||||
|
|
||||||
auto attacker = attackInfo.attacker;
|
auto attacker = attackInfo.attacker;
|
||||||
auto hexes = attacker->getSurroundingHexes(hex);
|
auto hexes = attacker->getSurroundingHexes(hex);
|
||||||
for(BattleHex tile : hexes)
|
for(BattleHex tile : hexes)
|
||||||
@ -208,9 +210,13 @@ int64_t AttackPossibility::evaluateBlockedShootersDmg(
|
|||||||
auto st = state->battleGetUnitByPos(tile, true);
|
auto st = state->battleGetUnitByPos(tile, true);
|
||||||
if(!st || !state->battleMatchOwner(st, attacker))
|
if(!st || !state->battleMatchOwner(st, attacker))
|
||||||
continue;
|
continue;
|
||||||
|
if(vstd::contains(checkedUnits, st->unitId()))
|
||||||
|
continue;
|
||||||
if(!state->battleCanShoot(st))
|
if(!state->battleCanShoot(st))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
checkedUnits.insert(st->unitId());
|
||||||
|
|
||||||
// FIXME: provide distance info for Jousting bonus
|
// FIXME: provide distance info for Jousting bonus
|
||||||
BattleAttackInfo rangeAttackInfo(st, attacker, 0, true);
|
BattleAttackInfo rangeAttackInfo(st, attacker, 0, true);
|
||||||
rangeAttackInfo.defenderPos = hex;
|
rangeAttackInfo.defenderPos = hex;
|
||||||
@ -220,9 +226,10 @@ int64_t AttackPossibility::evaluateBlockedShootersDmg(
|
|||||||
|
|
||||||
auto rangeDmg = state->battleEstimateDamage(rangeAttackInfo);
|
auto rangeDmg = state->battleEstimateDamage(rangeAttackInfo);
|
||||||
auto meleeDmg = state->battleEstimateDamage(meleeAttackInfo);
|
auto meleeDmg = state->battleEstimateDamage(meleeAttackInfo);
|
||||||
|
auto cachedDmg = damageCache.getOriginalDamage(st, attacker, state);
|
||||||
|
|
||||||
int64_t gain = averageDmg(rangeDmg.damage) - averageDmg(meleeDmg.damage) + 1;
|
int64_t gain = averageDmg(rangeDmg.damage) - averageDmg(meleeDmg.damage) + 1;
|
||||||
res += gain;
|
res += gain * cachedDmg / std::max<uint64_t>(1, averageDmg(rangeDmg.damage));
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
@ -167,7 +167,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
|||||||
score
|
score
|
||||||
);
|
);
|
||||||
|
|
||||||
if (moveTarget.scorePerTurn <= score)
|
if (moveTarget.score <= score)
|
||||||
{
|
{
|
||||||
if(evaluationResult.wait)
|
if(evaluationResult.wait)
|
||||||
{
|
{
|
||||||
@ -197,7 +197,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
|||||||
}
|
}
|
||||||
|
|
||||||
//ThreatMap threatsToUs(stack); // These lines may be useful but they are't used in the code.
|
//ThreatMap threatsToUs(stack); // These lines may be useful but they are't used in the code.
|
||||||
if(moveTarget.scorePerTurn > score)
|
if(moveTarget.score > score)
|
||||||
{
|
{
|
||||||
score = moveTarget.score;
|
score = moveTarget.score;
|
||||||
cachedAttack = moveTarget.cachedAttack;
|
cachedAttack = moveTarget.cachedAttack;
|
||||||
@ -206,12 +206,11 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
|||||||
if(stack->waited())
|
if(stack->waited())
|
||||||
{
|
{
|
||||||
logAi->debug(
|
logAi->debug(
|
||||||
"Moving %s towards hex %s[%d], score: %2f/%2f",
|
"Moving %s towards hex %s[%d], score: %2f",
|
||||||
stack->getDescription(),
|
stack->getDescription(),
|
||||||
moveTarget.cachedAttack->attack.defender->getDescription(),
|
moveTarget.cachedAttack->attack.defender->getDescription(),
|
||||||
moveTarget.cachedAttack->attack.defender->getPosition().hex,
|
moveTarget.cachedAttack->attack.defender->getPosition().hex,
|
||||||
moveTarget.score,
|
moveTarget.score);
|
||||||
moveTarget.scorePerTurn);
|
|
||||||
|
|
||||||
return goTowardsNearest(stack, moveTarget.positions, *targets);
|
return goTowardsNearest(stack, moveTarget.positions, *targets);
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ AttackerValue::AttackerValue()
|
|||||||
}
|
}
|
||||||
|
|
||||||
MoveTarget::MoveTarget()
|
MoveTarget::MoveTarget()
|
||||||
: positions(), cachedAttack(), score(EvaluationResult::INEFFECTIVE_SCORE), scorePerTurn(EvaluationResult::INEFFECTIVE_SCORE)
|
: positions(), cachedAttack(), score(EvaluationResult::INEFFECTIVE_SCORE)
|
||||||
{
|
{
|
||||||
turnsToRich = 1;
|
turnsToRich = 1;
|
||||||
}
|
}
|
||||||
@ -292,7 +292,7 @@ ReachabilityInfo getReachabilityWithEnemyBypass(
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
auto dmg = damageCache.getOriginalDamage(activeStack, unit, state);
|
auto dmg = damageCache.getOriginalDamage(activeStack, unit, state);
|
||||||
auto turnsToKill = unit->getAvailableHealth() / dmg + 1;
|
auto turnsToKill = unit->getAvailableHealth() / dmg;
|
||||||
|
|
||||||
vstd::amin(turnsToKill, 100);
|
vstd::amin(turnsToKill, 100);
|
||||||
|
|
||||||
@ -364,15 +364,16 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
|||||||
attack.shootersBlockedDmg = 0; // we do not want to count on it, it is not for sure
|
attack.shootersBlockedDmg = 0; // we do not want to count on it, it is not for sure
|
||||||
|
|
||||||
auto score = calculateExchange(attack, turnsToRich, targets, damageCache, hb);
|
auto score = calculateExchange(attack, turnsToRich, targets, damageCache, hb);
|
||||||
auto scorePerTurn = BattleScore(score.enemyDamageReduce * multiplier / turnsToRich, score.ourDamageReduce);
|
|
||||||
|
score.enemyDamageReduce *= multiplier;
|
||||||
|
|
||||||
#if BATTLE_TRACE_LEVEL >= 1
|
#if BATTLE_TRACE_LEVEL >= 1
|
||||||
logAi->trace("Multiplier: %f, turns: %d, current score %f, new score %f", multiplier, turnsToRich, result.scorePerTurn, scoreValue(scorePerTurn));
|
logAi->trace("Multiplier: %f, turns: %d, current score %f, new score %f", multiplier, turnsToRich, result.score, scoreValue(score));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if(result.scorePerTurn < scoreValue(scorePerTurn))
|
if(result.score < scoreValue(score)
|
||||||
|
|| (result.turnsToRich > turnsToRich && vstd::isAlmostEqual(result.score, scoreValue(score))))
|
||||||
{
|
{
|
||||||
result.scorePerTurn = scoreValue(scorePerTurn);
|
|
||||||
result.score = scoreValue(score);
|
result.score = scoreValue(score);
|
||||||
result.positions.clear();
|
result.positions.clear();
|
||||||
|
|
||||||
@ -414,18 +415,10 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
|||||||
|
|
||||||
if(scoreValue(bypassScore) > result.score)
|
if(scoreValue(bypassScore) > result.score)
|
||||||
{
|
{
|
||||||
auto newMultiplier = multiplier * speed * turnsToRich / dists.distances[attackHex];
|
|
||||||
|
|
||||||
result.score = scoreValue(bypassScore);
|
result.score = scoreValue(bypassScore);
|
||||||
|
|
||||||
scorePerTurn = BattleScore(
|
|
||||||
score.enemyDamageReduce * newMultiplier,
|
|
||||||
score.ourDamageReduce);
|
|
||||||
|
|
||||||
result.scorePerTurn = scoreValue(scorePerTurn);
|
|
||||||
|
|
||||||
#if BATTLE_TRACE_LEVEL >= 1
|
#if BATTLE_TRACE_LEVEL >= 1
|
||||||
logAi->trace("New high score after bypass %f", scoreValue(scorePerTurn));
|
logAi->trace("New high score after bypass %f", scoreValue(bypassScore));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -488,12 +481,25 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
|
|||||||
if(!ap.attack.shooting) hexes.push_back(ap.from);
|
if(!ap.attack.shooting) hexes.push_back(ap.from);
|
||||||
|
|
||||||
std::vector<const battle::Unit *> allReachableUnits = additionalUnits;
|
std::vector<const battle::Unit *> allReachableUnits = additionalUnits;
|
||||||
|
|
||||||
for(auto hex : hexes)
|
for(auto hex : hexes)
|
||||||
{
|
{
|
||||||
vstd::concatenate(allReachableUnits, turn == 0 ? reachabilityMap.at(hex) : getOneTurnReachableUnits(turn, hex));
|
vstd::concatenate(allReachableUnits, turn == 0 ? reachabilityMap.at(hex) : getOneTurnReachableUnits(turn, hex));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for(auto hex : ap.attack.attacker->getHexes())
|
||||||
|
{
|
||||||
|
auto unitsReachingAttacker = turn == 0 ? reachabilityMap.at(hex) : getOneTurnReachableUnits(turn, hex);
|
||||||
|
for(auto unit : unitsReachingAttacker)
|
||||||
|
{
|
||||||
|
if(unit->unitSide() != ap.attack.attacker->unitSide())
|
||||||
|
{
|
||||||
|
allReachableUnits.push_back(unit);
|
||||||
|
result.enemyUnitsReachingAttacker.insert(unit->unitId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
vstd::removeDuplicates(allReachableUnits);
|
vstd::removeDuplicates(allReachableUnits);
|
||||||
|
|
||||||
auto copy = allReachableUnits;
|
auto copy = allReachableUnits;
|
||||||
@ -621,9 +627,9 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
|||||||
auto exchangeBattle = std::make_shared<HypotheticBattle>(env.get(), hb);
|
auto exchangeBattle = std::make_shared<HypotheticBattle>(env.get(), hb);
|
||||||
BattleExchangeVariant v;
|
BattleExchangeVariant v;
|
||||||
|
|
||||||
for(int turn = 0; turn < exchangeUnits.units.size(); turn++)
|
for(int exchangeTurn = 0; exchangeTurn < exchangeUnits.units.size(); exchangeTurn++)
|
||||||
{
|
{
|
||||||
for(auto unit : exchangeUnits.units.at(turn))
|
for(auto unit : exchangeUnits.units.at(exchangeTurn))
|
||||||
{
|
{
|
||||||
if(unit->isTurret())
|
if(unit->isTurret())
|
||||||
continue;
|
continue;
|
||||||
@ -652,16 +658,26 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
|||||||
});
|
});
|
||||||
|
|
||||||
bool canUseAp = true;
|
bool canUseAp = true;
|
||||||
|
const int totalTurnsCount = 10;
|
||||||
|
|
||||||
for(int turn = 0; turn < exchangeUnits.units.size(); turn++)
|
std::set<uint32_t> blockedShooters;
|
||||||
|
|
||||||
|
for(int exchangeTurn = 0; exchangeTurn < totalTurnsCount; exchangeTurn++)
|
||||||
{
|
{
|
||||||
for(auto activeUnit : exchangeUnits.units.at(turn))
|
bool isMovingTurm = exchangeTurn < turn;
|
||||||
|
int queueTurn = exchangeTurn >= exchangeUnits.units.size()
|
||||||
|
? exchangeUnits.units.size() - 1
|
||||||
|
: exchangeTurn;
|
||||||
|
|
||||||
|
for(auto activeUnit : exchangeUnits.units.at(queueTurn))
|
||||||
{
|
{
|
||||||
bool isOur = exchangeBattle->battleMatchOwner(ap.attack.attacker, activeUnit, true);
|
bool isOur = exchangeBattle->battleMatchOwner(ap.attack.attacker, activeUnit, true);
|
||||||
battle::Units & attackerQueue = isOur ? ourStacks : enemyStacks;
|
battle::Units & attackerQueue = isOur ? ourStacks : enemyStacks;
|
||||||
battle::Units & oppositeQueue = isOur ? enemyStacks : ourStacks;
|
battle::Units & oppositeQueue = isOur ? enemyStacks : ourStacks;
|
||||||
|
|
||||||
auto attacker = exchangeBattle->getForUpdate(activeUnit->unitId());
|
auto attacker = exchangeBattle->getForUpdate(activeUnit->unitId());
|
||||||
|
auto shooting = exchangeBattle->battleCanShoot(attacker.get())
|
||||||
|
&& !vstd::contains(blockedShooters, attacker->unitId());
|
||||||
|
|
||||||
if(!attacker->alive())
|
if(!attacker->alive())
|
||||||
{
|
{
|
||||||
@ -672,10 +688,23 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(isMovingTurm && !shooting
|
||||||
|
&& !vstd::contains(exchangeUnits.enemyUnitsReachingAttacker, attacker->unitId()))
|
||||||
|
{
|
||||||
|
#if BATTLE_TRACE_LEVEL>=1
|
||||||
|
logAi->trace("Attacker is moving");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
auto targetUnit = ap.attack.defender;
|
auto targetUnit = ap.attack.defender;
|
||||||
|
|
||||||
if(!isOur || !exchangeBattle->battleGetUnitByID(targetUnit->unitId())->alive())
|
if(!isOur || !exchangeBattle->battleGetUnitByID(targetUnit->unitId())->alive())
|
||||||
{
|
{
|
||||||
|
#if BATTLE_TRACE_LEVEL>=2
|
||||||
|
logAi->trace("Best target selector for %s", attacker->getDescription());
|
||||||
|
#endif
|
||||||
auto estimateAttack = [&](const battle::Unit * u) -> float
|
auto estimateAttack = [&](const battle::Unit * u) -> float
|
||||||
{
|
{
|
||||||
auto stackWithBonuses = exchangeBattle->getForUpdate(u->unitId());
|
auto stackWithBonuses = exchangeBattle->getForUpdate(u->unitId());
|
||||||
@ -688,7 +717,7 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
|||||||
hb,
|
hb,
|
||||||
true);
|
true);
|
||||||
|
|
||||||
#if BATTLE_TRACE_LEVEL>=1
|
#if BATTLE_TRACE_LEVEL>=2
|
||||||
logAi->trace("Best target selector %s->%s score = %2f", attacker->getDescription(), stackWithBonuses->getDescription(), score);
|
logAi->trace("Best target selector %s->%s score = %2f", attacker->getDescription(), stackWithBonuses->getDescription(), score);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -741,7 +770,6 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto defender = exchangeBattle->getForUpdate(targetUnit->unitId());
|
auto defender = exchangeBattle->getForUpdate(targetUnit->unitId());
|
||||||
auto shooting = exchangeBattle->battleCanShoot(attacker.get());
|
|
||||||
const int totalAttacks = attacker->getTotalAttacks(shooting);
|
const int totalAttacks = attacker->getTotalAttacks(shooting);
|
||||||
|
|
||||||
if(canUseAp && activeUnit->unitId() == ap.attack.attacker->unitId()
|
if(canUseAp && activeUnit->unitId() == ap.attack.attacker->unitId()
|
||||||
@ -760,6 +788,9 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!shooting)
|
||||||
|
blockedShooters.insert(defender->unitId());
|
||||||
|
|
||||||
canUseAp = false;
|
canUseAp = false;
|
||||||
|
|
||||||
vstd::erase_if(attackerQueue, [&](const battle::Unit * u) -> bool
|
vstd::erase_if(attackerQueue, [&](const battle::Unit * u) -> bool
|
||||||
@ -785,11 +816,20 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
|||||||
for(auto hex : hexes)
|
for(auto hex : hexes)
|
||||||
reachabilityMap[hex] = getOneTurnReachableUnits(turn, hex);
|
reachabilityMap[hex] = getOneTurnReachableUnits(turn, hex);
|
||||||
|
|
||||||
|
auto score = v.getScore();
|
||||||
|
|
||||||
|
if(turn > 0)
|
||||||
|
{
|
||||||
|
auto turnMultiplier = 1 - std::min(0.2, 0.05 * turn);
|
||||||
|
|
||||||
|
score.enemyDamageReduce *= turnMultiplier;
|
||||||
|
}
|
||||||
|
|
||||||
#if BATTLE_TRACE_LEVEL>=1
|
#if BATTLE_TRACE_LEVEL>=1
|
||||||
logAi->trace("Exchange score: enemy: %2f, our -%2f", v.getScore().enemyDamageReduce, v.getScore().ourDamageReduce);
|
logAi->trace("Exchange score: enemy: %2f, our -%2f", score.enemyDamageReduce, score.ourDamageReduce);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return v.getScore();
|
return score;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BattleExchangeEvaluator::canBeHitThisTurn(const AttackPossibility & ap)
|
bool BattleExchangeEvaluator::canBeHitThisTurn(const AttackPossibility & ap)
|
||||||
|
@ -54,7 +54,6 @@ struct AttackerValue
|
|||||||
struct MoveTarget
|
struct MoveTarget
|
||||||
{
|
{
|
||||||
float score;
|
float score;
|
||||||
float scorePerTurn;
|
|
||||||
std::vector<BattleHex> positions;
|
std::vector<BattleHex> positions;
|
||||||
std::optional<AttackPossibility> cachedAttack;
|
std::optional<AttackPossibility> cachedAttack;
|
||||||
uint8_t turnsToRich;
|
uint8_t turnsToRich;
|
||||||
@ -64,7 +63,7 @@ struct MoveTarget
|
|||||||
|
|
||||||
struct EvaluationResult
|
struct EvaluationResult
|
||||||
{
|
{
|
||||||
static const int64_t INEFFECTIVE_SCORE = -10000;
|
static const int64_t INEFFECTIVE_SCORE = -100000000;
|
||||||
|
|
||||||
AttackPossibility bestAttack;
|
AttackPossibility bestAttack;
|
||||||
MoveTarget bestMove;
|
MoveTarget bestMove;
|
||||||
@ -120,6 +119,8 @@ struct ReachabilityData
|
|||||||
|
|
||||||
// far shooters
|
// far shooters
|
||||||
std::vector<const battle::Unit *> shooters;
|
std::vector<const battle::Unit *> shooters;
|
||||||
|
|
||||||
|
std::set<uint32_t> enemyUnitsReachingAttacker;
|
||||||
};
|
};
|
||||||
|
|
||||||
class BattleExchangeEvaluator
|
class BattleExchangeEvaluator
|
||||||
|
@ -89,6 +89,14 @@ void DangerHitMapAnalyzer::updateHitMap()
|
|||||||
|
|
||||||
heroes[hero->tempOwner][hero] = HeroRole::MAIN;
|
heroes[hero->tempOwner][hero] = HeroRole::MAIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(obj->ID == Obj::TOWN)
|
||||||
|
{
|
||||||
|
auto town = dynamic_cast<const CGTownInstance *>(obj);
|
||||||
|
|
||||||
|
if(town->garrisonHero)
|
||||||
|
heroes[town->garrisonHero->tempOwner][town->garrisonHero] = HeroRole::MAIN;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto ourTowns = cb->getTownsInfo();
|
auto ourTowns = cb->getTownsInfo();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user