mirror of
https://github.com/vcmi/vcmi.git
synced 2026-06-19 22:57:37 +02:00
reworks battleAI preliminary spell target choice - review fix
This commit is contained in:
@@ -160,7 +160,7 @@ std::optional<PossibleSpellcast> BattleEvaluator::findBestCreatureSpell(const CS
|
||||
continue;
|
||||
|
||||
spells::BattleCast temp(cb->getBattle(battleID).get(), stack, spells::Mode::CREATURE_ACTIVE, spell);
|
||||
for(auto & target : SpellTargetEvaluator::getViableTargets(spell->battleMechanics(&temp).get()))
|
||||
for(const auto & target : SpellTargetEvaluator::getViableTargets(spell->battleMechanics(&temp).get()))
|
||||
{
|
||||
PossibleSpellcast ps;
|
||||
ps.dest = target;
|
||||
@@ -539,7 +539,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
|
||||
{
|
||||
spells::BattleCast temp(cb->getBattle(battleID).get(), hero, spells::Mode::HERO, spell);
|
||||
|
||||
for(auto & target : SpellTargetEvaluator::getViableTargets(spell->battleMechanics(&temp).get()))
|
||||
for(const auto & target : SpellTargetEvaluator::getViableTargets(spell->battleMechanics(&temp).get()))
|
||||
{
|
||||
PossibleSpellcast ps;
|
||||
ps.dest = target;
|
||||
|
||||
@@ -8,234 +8,239 @@
|
||||
*
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "../../lib/CStack.h"
|
||||
#include "../../lib/battle/CBattleInfoCallback.h"
|
||||
#include "../../lib/spells/Problem.h"
|
||||
#include "../../lib/CRandomGenerator.h"
|
||||
#include "SpellTargetsEvaluator.h"
|
||||
#include <vcmi/spells/Spell.h>
|
||||
#include "../../lib/spells/Problem.h"
|
||||
#include "../../lib/battle/CBattleInfoCallback.h"
|
||||
#include "../../lib/CStack.h"
|
||||
|
||||
using namespace spells;
|
||||
|
||||
std::vector<Target> SpellTargetEvaluator::getViableTargets(Mechanics * spellMechanics)
|
||||
std::vector<Target> SpellTargetEvaluator::getViableTargets(const Mechanics * spellMechanics)
|
||||
{
|
||||
std::vector<Target> result;
|
||||
if (!spellMechanics)
|
||||
{
|
||||
logGlobal->error("SpellTargetEvaluator received empty spellMechanics. Should not occur!");
|
||||
return result;
|
||||
}
|
||||
std::vector<AimType> targetTypes = spellMechanics->getTargetTypes();
|
||||
if (targetTypes.size() != 1) //multi-destination spells like teleport are not implemented - they would be too resource-intensive
|
||||
return result;
|
||||
std::vector<Target> result;
|
||||
std::vector<AimType> targetTypes = spellMechanics->getTargetTypes();
|
||||
if(targetTypes.size() != 1) //TODO: support for multi-destination spells
|
||||
return result;
|
||||
|
||||
auto targetType = targetTypes.front();
|
||||
auto targetType = targetTypes.front();
|
||||
|
||||
switch(targetType)
|
||||
{
|
||||
case AimType::CREATURE:
|
||||
return allTargetableCreatures(spellMechanics);
|
||||
case AimType::LOCATION:
|
||||
{
|
||||
if (spellMechanics->isNeutralSpell())
|
||||
return defaultLocationSpellHeuristics(spellMechanics); // theoretically anything can be a useful destination, so we balance performance and validity
|
||||
else
|
||||
return theBestLocationCasts(spellMechanics);
|
||||
}
|
||||
case AimType::NO_TARGET:
|
||||
return std::vector<Target>(1); //default-constructed target means cast without destination
|
||||
default:
|
||||
return result;
|
||||
}
|
||||
switch(targetType)
|
||||
{
|
||||
case AimType::CREATURE://TODO: support for multi-destination spells
|
||||
return allTargetableCreatures(spellMechanics);
|
||||
case AimType::LOCATION:
|
||||
{
|
||||
if(spellMechanics->isNeutralSpell())
|
||||
return defaultLocationSpellHeuristics(
|
||||
spellMechanics
|
||||
); // theoretically anything can be a useful destination, so we balance performance and validity
|
||||
else
|
||||
return theBestLocationCasts(spellMechanics);
|
||||
}
|
||||
case AimType::NO_TARGET:
|
||||
return std::vector<Target>(1); //default-constructed target means cast without destination
|
||||
default:
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Target> SpellTargetEvaluator::defaultLocationSpellHeuristics(spells::Mechanics * spellMechanics)
|
||||
std::vector<Target> SpellTargetEvaluator::defaultLocationSpellHeuristics(const spells::Mechanics * spellMechanics)
|
||||
{
|
||||
std::vector<Target> result = allTargetableCreatures(spellMechanics);
|
||||
auto units = spellMechanics->battle()->battleGetAllUnits(false);
|
||||
for(const auto * unit : units) //insert a random surrounding hex
|
||||
{
|
||||
auto surroundingHexes = unit->getSurroundingHexes();
|
||||
if (!surroundingHexes.empty())
|
||||
{
|
||||
auto randomSurroundingHex = surroundingHexes.at(rand() % surroundingHexes.size()); // don't think this method bias matter with such small numbers
|
||||
addIfCanBeCast(spellMechanics, randomSurroundingHex, result);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
std::vector<Target> result = allTargetableCreatures(spellMechanics);
|
||||
auto units = spellMechanics->battle()->battleGetAllUnits(false);
|
||||
for(const auto * unit : units) //insert a random surrounding hex
|
||||
{
|
||||
auto surroundingHexes = unit->getSurroundingHexes();
|
||||
if(!surroundingHexes.empty())
|
||||
{
|
||||
auto randomSurroundingHex = *RandomGeneratorUtil::nextItem(surroundingHexes, CRandomGenerator::getDefault()); // don't think this method bias matter with such small numbers
|
||||
addIfCanBeCast(spellMechanics, randomSurroundingHex, result);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<Target> SpellTargetEvaluator::allTargetableCreatures(spells::Mechanics * spellMechanics) {
|
||||
std::vector<Target> result;
|
||||
auto units = spellMechanics->battle()->battleGetAllUnits(false);
|
||||
for(const auto * unit : units)
|
||||
addIfCanBeCast(spellMechanics, unit->getPosition(), result);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<Target> SpellTargetEvaluator::theBestLocationCasts(spells::Mechanics * spellMechanics)
|
||||
std::vector<Target> SpellTargetEvaluator::allTargetableCreatures(const spells::Mechanics * spellMechanics)
|
||||
{
|
||||
std::vector<Target> result;
|
||||
std::map<BattleHex,std::set<const CStack *>> allCasts;
|
||||
std::map<BattleHex,std::set<const CStack *>> bestCasts;
|
||||
for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
|
||||
{
|
||||
BattleHex dest(i);
|
||||
if(canBeCastAt(spellMechanics, dest))
|
||||
{
|
||||
Target target;
|
||||
target.emplace_back(dest);
|
||||
auto temp = spellMechanics->getAffectedStacks(target);
|
||||
std::set<const CStack *> affectedStacks(temp.begin(), temp.end());
|
||||
allCasts[dest] = affectedStacks;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const& cast : allCasts)
|
||||
{
|
||||
std::set<BattleHex> worseCasts;
|
||||
if(isCastHarmful(spellMechanics, cast.second))
|
||||
continue;
|
||||
|
||||
bool isBestCast = true;
|
||||
for(auto const& bestCast : bestCasts)
|
||||
{
|
||||
Compare compare = compareAffectedStacks(spellMechanics, cast.second, bestCast.second);
|
||||
|
||||
if (compare == Compare::WORSE || compare == Compare::EQUAL)
|
||||
{
|
||||
isBestCast = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (compare == Compare::BETTER)
|
||||
{
|
||||
worseCasts.insert(bestCast.first);
|
||||
}
|
||||
}
|
||||
|
||||
if (isBestCast)
|
||||
{
|
||||
bestCasts.insert(cast);
|
||||
for (BattleHex worseCast : worseCasts)
|
||||
bestCasts.erase(worseCast);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const& cast : bestCasts)
|
||||
{
|
||||
Destination des(cast.first);
|
||||
result.push_back({des});
|
||||
}
|
||||
return result;
|
||||
std::vector<Target> result;
|
||||
auto units = spellMechanics->battle()->battleGetAllUnits(false);
|
||||
for(const auto * unit : units)
|
||||
addIfCanBeCast(spellMechanics, unit->getPosition(), result);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool SpellTargetEvaluator::isCastHarmful(spells::Mechanics * spellMechanics, std::set<const CStack *> affectedStacks)
|
||||
std::vector<Target> SpellTargetEvaluator::theBestLocationCasts(const spells::Mechanics * spellMechanics)
|
||||
{
|
||||
std::vector<Target> result;
|
||||
std::map<BattleHex, std::set<const CStack *>> allCasts;
|
||||
std::map<BattleHex, std::set<const CStack *>> bestCasts;
|
||||
for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
|
||||
{
|
||||
BattleHex dest(i);
|
||||
if(canBeCastAt(spellMechanics, dest))
|
||||
{
|
||||
Target target;
|
||||
target.emplace_back(dest);
|
||||
auto temp = spellMechanics->getAffectedStacks(target);
|
||||
std::set<const CStack *> affectedStacks(temp.begin(), temp.end());
|
||||
allCasts[dest] = affectedStacks;
|
||||
}
|
||||
}
|
||||
|
||||
for(const auto & cast : allCasts)
|
||||
{
|
||||
std::set<BattleHex> worseCasts;
|
||||
if(isCastHarmful(spellMechanics, cast.second))
|
||||
continue;
|
||||
|
||||
bool isBestCast = true;
|
||||
for(const auto & bestCast : bestCasts)
|
||||
{
|
||||
Compare compare = compareAffectedStacks(spellMechanics, cast.second, bestCast.second);
|
||||
|
||||
if(compare == Compare::WORSE || compare == Compare::EQUAL)
|
||||
{
|
||||
isBestCast = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if(compare == Compare::BETTER)
|
||||
{
|
||||
worseCasts.insert(bestCast.first);
|
||||
}
|
||||
}
|
||||
|
||||
if(isBestCast)
|
||||
{
|
||||
bestCasts.insert(cast);
|
||||
for(BattleHex worseCast : worseCasts)
|
||||
bestCasts.erase(worseCast);
|
||||
}
|
||||
}
|
||||
|
||||
for(const auto & cast : bestCasts)
|
||||
{
|
||||
Destination des(cast.first);
|
||||
result.push_back({des});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool SpellTargetEvaluator::isCastHarmful(const spells::Mechanics * spellMechanics, const std::set<const CStack *> & affectedStacks)
|
||||
{
|
||||
|
||||
bool isAffectedAlly = false;
|
||||
bool isAffectedEnemy = false;
|
||||
bool isAffectedAlly = false;
|
||||
bool isAffectedEnemy = false;
|
||||
|
||||
for (const CStack * affectedUnit : affectedStacks)
|
||||
{
|
||||
if(affectedUnit->unitSide() == spellMechanics->casterSide)
|
||||
isAffectedAlly = true;
|
||||
else
|
||||
isAffectedEnemy = true;
|
||||
}
|
||||
for(const CStack * affectedUnit : affectedStacks)
|
||||
{
|
||||
if(affectedUnit->unitSide() == spellMechanics->casterSide)
|
||||
isAffectedAlly = true;
|
||||
else
|
||||
isAffectedEnemy = true;
|
||||
}
|
||||
|
||||
return (spellMechanics->isPositiveSpell() && !isAffectedAlly) || (spellMechanics->isNegativeSpell() && !isAffectedEnemy);
|
||||
return (spellMechanics->isPositiveSpell() && !isAffectedAlly) || (spellMechanics->isNegativeSpell() && !isAffectedEnemy);
|
||||
}
|
||||
|
||||
SpellTargetEvaluator::Compare SpellTargetEvaluator::compareAffectedStacks(spells::Mechanics * spellMechanics, std::set<const CStack *> newCast, std::set<const CStack *> oldCast)
|
||||
SpellTargetEvaluator::Compare SpellTargetEvaluator::compareAffectedStacks(
|
||||
const spells::Mechanics * spellMechanics, const std::set<const CStack *> & newCast, const std::set<const CStack *> & oldCast)
|
||||
{
|
||||
if (newCast.size() == oldCast.size())
|
||||
return newCast == oldCast ? Compare::EQUAL : Compare::DIFFERENT;
|
||||
if(newCast.size() == oldCast.size())
|
||||
return newCast == oldCast ? Compare::EQUAL : Compare::DIFFERENT;
|
||||
|
||||
auto getAlliedUnits = [&spellMechanics] (std::set<const CStack *> allUnits) -> std::set<const CStack *> {
|
||||
std::set<const CStack *> alliedUnits;
|
||||
for (auto stack : allUnits) {
|
||||
if (stack->unitSide() == spellMechanics->casterSide)
|
||||
alliedUnits.insert(stack);
|
||||
}
|
||||
return alliedUnits;
|
||||
};
|
||||
auto getAlliedUnits = [&spellMechanics](const std::set<const CStack *> & allUnits) -> std::set<const CStack *>
|
||||
{
|
||||
std::set<const CStack *> alliedUnits;
|
||||
for(auto stack : allUnits)
|
||||
{
|
||||
if(stack->unitSide() == spellMechanics->casterSide)
|
||||
alliedUnits.insert(stack);
|
||||
}
|
||||
return alliedUnits;
|
||||
};
|
||||
|
||||
auto getEnemyUnits = [&spellMechanics] (std::set<const CStack *> allUnits) -> std::set<const CStack *> {
|
||||
std::set<const CStack *> enemyUnits;
|
||||
for (auto stack : allUnits) {
|
||||
if (stack->unitSide() != spellMechanics->casterSide)
|
||||
enemyUnits.insert(stack);
|
||||
}
|
||||
return enemyUnits;
|
||||
};
|
||||
auto getEnemyUnits = [&spellMechanics](const std::set<const CStack *> & allUnits) -> std::set<const CStack *>
|
||||
{
|
||||
std::set<const CStack *> enemyUnits;
|
||||
for(auto stack : allUnits)
|
||||
{
|
||||
if(stack->unitSide() != spellMechanics->casterSide)
|
||||
enemyUnits.insert(stack);
|
||||
}
|
||||
return enemyUnits;
|
||||
};
|
||||
|
||||
Compare alliedSubsetComparison = compareAffectedStacksSubset(spellMechanics, getAlliedUnits(newCast), getAlliedUnits(oldCast));
|
||||
Compare enemySubsetComparison = compareAffectedStacksSubset(spellMechanics, getEnemyUnits(newCast), getEnemyUnits(oldCast));
|
||||
|
||||
Compare alliedSubsetComparison = compareAffectedStacksSubset(spellMechanics, getAlliedUnits(newCast), getAlliedUnits(oldCast));
|
||||
Compare enemySubsetComparison = compareAffectedStacksSubset(spellMechanics, getEnemyUnits(newCast), getEnemyUnits(oldCast));
|
||||
if(spellMechanics->isPositiveSpell())
|
||||
enemySubsetComparison = reverse(enemySubsetComparison);
|
||||
else if(spellMechanics->isNegativeSpell())
|
||||
alliedSubsetComparison = reverse(alliedSubsetComparison);
|
||||
|
||||
if (spellMechanics->isPositiveSpell())
|
||||
enemySubsetComparison = reverse(enemySubsetComparison);
|
||||
else if (spellMechanics->isNegativeSpell())
|
||||
alliedSubsetComparison = reverse(alliedSubsetComparison);
|
||||
std::set<Compare> comparisonResults = {alliedSubsetComparison, enemySubsetComparison};
|
||||
std::set<std::set<Compare>> possibleBetterResults = {
|
||||
{Compare::BETTER, Compare::BETTER},
|
||||
{Compare::BETTER, Compare::EQUAL }
|
||||
};
|
||||
std::set<std::set<Compare>> possibleWorstResults = {
|
||||
{Compare::WORSE, Compare::WORSE},
|
||||
{Compare::WORSE, Compare::EQUAL}
|
||||
};
|
||||
|
||||
std::set<Compare>comparisonResults = {alliedSubsetComparison, enemySubsetComparison};
|
||||
std::set<std::set<Compare>>possibleBetterResults = {{Compare::BETTER, Compare::BETTER},{Compare::BETTER, Compare::EQUAL}};
|
||||
std::set<std::set<Compare>>possibleWorstResults = {{Compare::WORSE, Compare::WORSE},{Compare::WORSE, Compare::EQUAL}};
|
||||
if(possibleBetterResults.find(comparisonResults) != possibleBetterResults.end())
|
||||
return Compare::BETTER;
|
||||
if(possibleWorstResults.find(comparisonResults) != possibleWorstResults.end())
|
||||
return Compare::WORSE;
|
||||
|
||||
if (possibleBetterResults.find(comparisonResults) != possibleBetterResults.end())
|
||||
return Compare::BETTER;
|
||||
if (possibleWorstResults.find(comparisonResults) != possibleWorstResults.end())
|
||||
return Compare::WORSE;
|
||||
|
||||
return Compare::DIFFERENT;
|
||||
return Compare::DIFFERENT;
|
||||
}
|
||||
|
||||
SpellTargetEvaluator::Compare SpellTargetEvaluator::compareAffectedStacksSubset(spells::Mechanics * spellMechanics, std::set<const CStack *> newSubset, std::set<const CStack *> oldSubset)
|
||||
SpellTargetEvaluator::Compare SpellTargetEvaluator::compareAffectedStacksSubset(
|
||||
const spells::Mechanics * spellMechanics, const std::set<const CStack *> & newSubset, const std::set<const CStack *> & oldSubset)
|
||||
{
|
||||
if (newSubset.size() == oldSubset.size())
|
||||
return newSubset == oldSubset ? Compare::EQUAL : Compare::DIFFERENT;
|
||||
if(newSubset.size() == oldSubset.size())
|
||||
return newSubset == oldSubset ? Compare::EQUAL : Compare::DIFFERENT;
|
||||
|
||||
if (oldSubset.size() > newSubset.size())
|
||||
return reverse(compareAffectedStacksSubset(spellMechanics, oldSubset, newSubset));
|
||||
if(oldSubset.size() > newSubset.size())
|
||||
return reverse(compareAffectedStacksSubset(spellMechanics, oldSubset, newSubset));
|
||||
|
||||
const std::set<const CStack *> & biggerSet = newSubset;
|
||||
const std::set<const CStack *> & smallerSet = oldSubset;
|
||||
const std::set<const CStack *> & biggerSet = newSubset;
|
||||
const std::set<const CStack *> & smallerSet = oldSubset;
|
||||
|
||||
if (std::includes(biggerSet.begin(), biggerSet.end(),
|
||||
smallerSet.begin(), smallerSet.end()))
|
||||
return Compare::BETTER;
|
||||
else
|
||||
return Compare::DIFFERENT;
|
||||
if(std::includes(biggerSet.begin(), biggerSet.end(), smallerSet.begin(), smallerSet.end()))
|
||||
return Compare::BETTER;
|
||||
else
|
||||
return Compare::DIFFERENT;
|
||||
}
|
||||
|
||||
|
||||
SpellTargetEvaluator::Compare SpellTargetEvaluator::reverse(SpellTargetEvaluator::Compare compare)
|
||||
{
|
||||
switch(compare)
|
||||
{
|
||||
case Compare::BETTER:
|
||||
return Compare::WORSE;
|
||||
case Compare::WORSE:
|
||||
return Compare::BETTER;
|
||||
default:
|
||||
return compare;
|
||||
}
|
||||
switch(compare)
|
||||
{
|
||||
case Compare::BETTER:
|
||||
return Compare::WORSE;
|
||||
case Compare::WORSE:
|
||||
return Compare::BETTER;
|
||||
default:
|
||||
return compare;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool SpellTargetEvaluator::canBeCastAt(spells::Mechanics * spellMechanics, BattleHex hex)
|
||||
bool SpellTargetEvaluator::canBeCastAt(const spells::Mechanics * spellMechanics, BattleHex hex)
|
||||
{
|
||||
detail::ProblemImpl ignored;
|
||||
Destination des(hex);
|
||||
return spellMechanics->canBeCastAt({des}, ignored);
|
||||
detail::ProblemImpl ignored;
|
||||
Destination des(hex);
|
||||
return spellMechanics->canBeCastAt({des}, ignored);
|
||||
}
|
||||
|
||||
void SpellTargetEvaluator::addIfCanBeCast(spells::Mechanics * spellMechanics, BattleHex hex, std::vector<Target> & targets)
|
||||
void SpellTargetEvaluator::addIfCanBeCast(const spells::Mechanics * spellMechanics, BattleHex hex, std::vector<Target> & targets)
|
||||
{
|
||||
detail::ProblemImpl ignored;
|
||||
Destination des(hex);
|
||||
if(spellMechanics->canBeCastAt({des}, ignored))
|
||||
targets.push_back({des});
|
||||
detail::ProblemImpl ignored;
|
||||
Destination des(hex);
|
||||
if(spellMechanics->canBeCastAt({des}, ignored))
|
||||
targets.push_back({des});
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -9,29 +9,32 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <vcmi/spells/Magic.h>
|
||||
#include "../../lib/spells/BattleSpellMechanics.h"
|
||||
#include <vcmi/spells/Magic.h>
|
||||
|
||||
class SpellTargetEvaluator
|
||||
{
|
||||
public:
|
||||
static std::vector<spells::Target> getViableTargets(spells::Mechanics * spellMechanics);
|
||||
static std::vector<spells::Target> getViableTargets(const spells::Mechanics * spellMechanics);
|
||||
|
||||
private:
|
||||
enum Compare
|
||||
{
|
||||
EQUAL,
|
||||
DIFFERENT,
|
||||
BETTER,
|
||||
WORSE
|
||||
};
|
||||
|
||||
enum Compare {
|
||||
EQUAL,
|
||||
DIFFERENT,
|
||||
BETTER,
|
||||
WORSE
|
||||
};
|
||||
|
||||
static std::vector<spells::Target> defaultLocationSpellHeuristics(spells::Mechanics * spellMechanics);
|
||||
static std::vector<spells::Target> allTargetableCreatures(spells::Mechanics * spellMechanics);
|
||||
static std::vector<spells::Target> theBestLocationCasts(spells::Mechanics * spellMechanics);
|
||||
static Compare compareAffectedStacks(spells::Mechanics * spellMechanics, std::set<const CStack *> newCast, std::set<const CStack *> oldCast);
|
||||
static Compare compareAffectedStacksSubset(spells::Mechanics * spellMechanics, std::set<const CStack *> newSubset, std::set<const CStack *> oldSubset);
|
||||
static SpellTargetEvaluator::Compare reverse(Compare compare);
|
||||
static bool isCastHarmful(spells::Mechanics * spellMechanics, std::set<const CStack *> affectedStacks);
|
||||
static bool canBeCastAt(spells::Mechanics * spellMechanics, BattleHex hex);
|
||||
static void addIfCanBeCast(spells::Mechanics * spellMechanics, BattleHex hex, std::vector<spells::Target> & targets);
|
||||
static std::vector<spells::Target> defaultLocationSpellHeuristics(const spells::Mechanics * spellMechanics);
|
||||
static std::vector<spells::Target> allTargetableCreatures(const spells::Mechanics * spellMechanics);
|
||||
static std::vector<spells::Target> theBestLocationCasts(const spells::Mechanics * spellMechanics);
|
||||
static Compare compareAffectedStacks(
|
||||
const spells::Mechanics * spellMechanics, const std::set<const CStack *> & newCast, const std::set<const CStack *> & oldCast);
|
||||
static Compare compareAffectedStacksSubset(
|
||||
const spells::Mechanics * spellMechanics, const std::set<const CStack *> & newSubset, const std::set<const CStack *> & oldSubset);
|
||||
static SpellTargetEvaluator::Compare reverse(Compare compare);
|
||||
static bool isCastHarmful(const spells::Mechanics * spellMechanics, const std::set<const CStack *> & affectedStacks);
|
||||
static bool canBeCastAt(const spells::Mechanics * spellMechanics, BattleHex hex);
|
||||
static void addIfCanBeCast(const spells::Mechanics * spellMechanics, BattleHex hex, std::vector<spells::Target> & targets);
|
||||
};
|
||||
|
||||
+11
-3
@@ -142,6 +142,17 @@ if(ENABLE_ERM)
|
||||
)
|
||||
endif()
|
||||
|
||||
if(ENABLE_BATTLE_AI)
|
||||
file(GLOB_RECURSE BATTLE_AI_TEST_SRCS "../AI/BattleAI/*.cpp")
|
||||
list(FILTER BATTLE_AI_TEST_SRCS EXCLUDE REGEX ".*main\\.cpp$")
|
||||
|
||||
list(APPEND test_SRCS
|
||||
${BATTLE_AI_TEST_SRCS}
|
||||
battleAI/SpellTargetsEvaluatorTest.cpp
|
||||
)
|
||||
|
||||
endif()
|
||||
|
||||
assign_source_group(${test_SRCS} ${test_HEADERS})
|
||||
|
||||
set(mock_HEADERS
|
||||
@@ -187,9 +198,6 @@ endif()
|
||||
if(ENABLE_NULLKILLER2_AI)
|
||||
target_link_libraries(vcmitest PUBLIC Nullkiller2)
|
||||
endif()
|
||||
if(ENABLE_BATTLE_AI)
|
||||
target_link_libraries(vcmitest PRIVATE BattleAI)
|
||||
endif()
|
||||
|
||||
target_include_directories(vcmitest
|
||||
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
|
||||
@@ -9,13 +9,12 @@
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
|
||||
#include "../mock/mock_spells_Mechanics.h"
|
||||
#include "../mock/mock_battle_Unit.h"
|
||||
#include "../mock/BattleFake.h"
|
||||
#include "../mock/mock_battle_Unit.h"
|
||||
#include "../mock/mock_spells_Mechanics.h"
|
||||
#include "AI/BattleAI/SpellTargetsEvaluator.h"
|
||||
#include "lib/battle/CBattleInfoCallback.h"
|
||||
#include "lib/CStack.h"
|
||||
|
||||
#include "lib/battle/CBattleInfoCallback.h"
|
||||
|
||||
namespace test
|
||||
{
|
||||
@@ -27,206 +26,191 @@ using PossiblePositions = std::vector<BattleHex>;
|
||||
class CBattleInfoCallbackMock : public CBattleInfoCallback
|
||||
{
|
||||
public:
|
||||
MOCK_CONST_METHOD1(battleGetAllUnits, battle::Units(bool));
|
||||
MOCK_CONST_METHOD0(getBattle, IBattleInfo*());
|
||||
MOCK_CONST_METHOD0(getPlayerID, std::optional<PlayerColor>());
|
||||
MOCK_CONST_METHOD1(battleGetAllUnits, battle::Units(bool));
|
||||
MOCK_CONST_METHOD0(getBattle, IBattleInfo *());
|
||||
MOCK_CONST_METHOD0(getPlayerID, std::optional<PlayerColor>());
|
||||
#if SCRIPTING_ENABLED
|
||||
MOCK_CONST_METHOD0(getContextPool, scripting::Pool*());
|
||||
MOCK_CONST_METHOD0(getContextPool, scripting::Pool *());
|
||||
#endif
|
||||
};
|
||||
|
||||
class CStackMock : public CStack {
|
||||
class CStackMock : public CStack
|
||||
{
|
||||
public:
|
||||
MOCK_CONST_METHOD0(unitSide, BattleSide());
|
||||
MOCK_CONST_METHOD0(unitSide, BattleSide());
|
||||
};
|
||||
|
||||
|
||||
class SpellTargetEvaluatorTest : public ::testing::Test
|
||||
{
|
||||
public:
|
||||
MechanicsMock mechMock;
|
||||
CBattleInfoCallbackMock battleMock;
|
||||
battle::Units allUnits;
|
||||
TStacks allStacks;
|
||||
BattleSide casterSide = BattleSide::ATTACKER;
|
||||
BattleSide enemySide = BattleSide::DEFENDER;
|
||||
|
||||
MechanicsMock mechMock;
|
||||
CBattleInfoCallbackMock battleMock;
|
||||
battle::Units allUnits;
|
||||
TStacks allStacks;
|
||||
BattleSide casterSide = BattleSide::ATTACKER;
|
||||
BattleSide enemySide = BattleSide::DEFENDER;
|
||||
void SetUp() override
|
||||
{
|
||||
mechMock.casterSide = casterSide;
|
||||
ON_CALL(mechMock, battle()).WillByDefault(Return(&battleMock));
|
||||
ON_CALL(mechMock, canBeCastAt(_, _)).WillByDefault(Return(true));
|
||||
}
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
mechMock.casterSide = casterSide;
|
||||
ON_CALL(mechMock, battle()).WillByDefault(Return(&battleMock));
|
||||
ON_CALL(mechMock, canBeCastAt(_,_)).WillByDefault(Return(true));
|
||||
}
|
||||
void TearDown() override
|
||||
{
|
||||
for(const auto * unit : allUnits)
|
||||
delete unit;
|
||||
allUnits.clear();
|
||||
for(const auto * stack : allStacks)
|
||||
delete stack;
|
||||
allStacks.clear();
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
for (const auto * unit : allUnits)
|
||||
delete unit;
|
||||
allUnits.clear();
|
||||
for (const auto * stack : allStacks)
|
||||
delete stack;
|
||||
allStacks.clear();
|
||||
}
|
||||
void spellTargetTypes(std::vector<AimType> targetTypes)
|
||||
{
|
||||
ON_CALL(mechMock, getTargetTypes()).WillByDefault(Return(std::move(targetTypes)));
|
||||
}
|
||||
|
||||
void spellTargetTypes(std::vector<AimType> targetTypes)
|
||||
{
|
||||
ON_CALL(mechMock, getTargetTypes()).WillByDefault(Return(std::move(targetTypes)));
|
||||
}
|
||||
CStackMock * addStack(BattleHex position, BattleSide battleSide, bool isSuspectible = true)
|
||||
{
|
||||
auto * stack = new CStackMock();
|
||||
ON_CALL(*stack, unitSide()).WillByDefault(Return(battleSide));
|
||||
allStacks.push_back(stack);
|
||||
auto * unit = new UnitMock();
|
||||
ON_CALL(*unit, getPosition()).WillByDefault(Return(position));
|
||||
allUnits.push_back(unit);
|
||||
|
||||
CStackMock * addStack(BattleHex position, BattleSide battleSide, bool isSuspectible = true)
|
||||
{
|
||||
auto * stack = new CStackMock();
|
||||
ON_CALL(*stack, unitSide()).WillByDefault(Return(battleSide));
|
||||
allStacks.push_back(stack);
|
||||
auto * unit = new UnitMock();
|
||||
ON_CALL(*unit, getPosition()).WillByDefault(Return(position));
|
||||
allUnits.push_back(unit);
|
||||
if(!isSuspectible)
|
||||
ON_CALL(mechMock, canBeCastAt(Contains(Field(&Destination::hexValue, position)), _)).WillByDefault(Return(false));
|
||||
return stack;
|
||||
}
|
||||
|
||||
if (!isSuspectible)
|
||||
ON_CALL(mechMock, canBeCastAt(Contains(Field(&Destination::hexValue, position)),_)).WillByDefault(Return(false));
|
||||
return stack;
|
||||
}
|
||||
void setAffectedStacksForCast(BattleHex position, std::vector<const CStack *> stacks)
|
||||
{
|
||||
ON_CALL(mechMock, getAffectedStacks(Contains(Field(&Destination::hexValue, position)))).WillByDefault(Return(stacks));
|
||||
}
|
||||
|
||||
void setAffectedStacksForCast(BattleHex position, std::vector<const CStack *> stacks)
|
||||
{
|
||||
ON_CALL(mechMock, getAffectedStacks(Contains(Field(&Destination::hexValue, position)))).WillByDefault(Return(stacks));
|
||||
}
|
||||
void confirmResults(std::vector<PossiblePositions> allRequiredCasts)
|
||||
{
|
||||
std::vector<Target> result = SpellTargetEvaluator::getViableTargets(&mechMock);
|
||||
basicCheck(result);
|
||||
ASSERT_EQ(result.size(), allRequiredCasts.size());
|
||||
|
||||
void confirmResults(std::vector<PossiblePositions> allRequiredCasts)
|
||||
{
|
||||
std::vector<Target> result = SpellTargetEvaluator::getViableTargets(&mechMock);
|
||||
basicCheck(result);
|
||||
ASSERT_EQ(result.size(), allRequiredCasts.size());
|
||||
std::vector<BattleHex> targetedHexes;
|
||||
targetedHexes.reserve(result.size());
|
||||
for(Target target : result)
|
||||
targetedHexes.push_back(target.front().hexValue);
|
||||
|
||||
std::vector<BattleHex> targetedHexes;
|
||||
targetedHexes.reserve(result.size());
|
||||
for (Target target : result)
|
||||
targetedHexes.push_back(target.front().hexValue);
|
||||
for(const PossiblePositions & requiredCast : allRequiredCasts)
|
||||
EXPECT_TRUE(containCommonValue(requiredCast, targetedHexes));
|
||||
}
|
||||
|
||||
for (const PossiblePositions & requiredCast : allRequiredCasts)
|
||||
EXPECT_TRUE(containCommonValue(requiredCast, targetedHexes));
|
||||
}
|
||||
|
||||
void basicCheck(std::vector<Target> & result)
|
||||
{
|
||||
for (const Target & target : result)
|
||||
EXPECT_EQ(target.size(), 1); //multi-destination spells are not handled by targetEvaluator
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
bool containCommonValue(const std::vector<T> & v1, const std::vector<T> & v2)
|
||||
{
|
||||
for (T val1 : v1)
|
||||
{
|
||||
for (T val2 : v2)
|
||||
{
|
||||
if (val1 == val2)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void basicCheck(std::vector<Target> & result)
|
||||
{
|
||||
for(const Target & target : result)
|
||||
EXPECT_EQ(target.size(), 1); //multi-destination spells are not handled by targetEvaluator
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool containCommonValue(const std::vector<T> & v1, const std::vector<T> & v2)
|
||||
{
|
||||
for(T val1 : v1)
|
||||
{
|
||||
for(T val2 : v2)
|
||||
{
|
||||
if(val1 == val2)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
TEST_F(SpellTargetEvaluatorTest, ReturnsEmptyIfMechanicsNullptr)
|
||||
{
|
||||
spellTargetTypes({AimType::CREATURE});
|
||||
std::vector<Target> result = SpellTargetEvaluator::getViableTargets(nullptr);
|
||||
EXPECT_TRUE(result.empty());
|
||||
}
|
||||
|
||||
TEST_F(SpellTargetEvaluatorTest, ReturnsEmptyIfMultiDestinationSpell)
|
||||
{
|
||||
spellTargetTypes({AimType::CREATURE, AimType::LOCATION});
|
||||
std::vector<Target> result = SpellTargetEvaluator::getViableTargets(&mechMock);
|
||||
EXPECT_TRUE(result.empty());
|
||||
spellTargetTypes({AimType::CREATURE, AimType::LOCATION});
|
||||
std::vector<Target> result = SpellTargetEvaluator::getViableTargets(&mechMock);
|
||||
EXPECT_TRUE(result.empty());
|
||||
}
|
||||
|
||||
TEST_F(SpellTargetEvaluatorTest, ReturnSingleEmptyDestinationIfTargetIsNone)
|
||||
{
|
||||
spellTargetTypes({AimType::NO_TARGET});
|
||||
std::vector<Target> result = SpellTargetEvaluator::getViableTargets(&mechMock);
|
||||
EXPECT_EQ(result.size(), 1);
|
||||
EXPECT_TRUE(result.front().empty());
|
||||
spellTargetTypes({AimType::NO_TARGET});
|
||||
std::vector<Target> result = SpellTargetEvaluator::getViableTargets(&mechMock);
|
||||
EXPECT_EQ(result.size(), 1);
|
||||
EXPECT_TRUE(result.front().empty());
|
||||
}
|
||||
|
||||
TEST_F(SpellTargetEvaluatorTest, ReturnsSuspectibleCreaturePositionsIfSpellTargetsCreatures)
|
||||
{
|
||||
spellTargetTypes({AimType::CREATURE});
|
||||
spellTargetTypes({AimType::CREATURE});
|
||||
|
||||
addStack(BattleHex(1), casterSide);
|
||||
addStack(BattleHex(2), enemySide, false);
|
||||
addStack(BattleHex(3), casterSide);
|
||||
ON_CALL(battleMock, battleGetAllUnits(Eq(false))).WillByDefault(Return(allUnits));
|
||||
addStack(BattleHex(1), casterSide);
|
||||
addStack(BattleHex(2), enemySide, false);
|
||||
addStack(BattleHex(3), casterSide);
|
||||
ON_CALL(battleMock, battleGetAllUnits(Eq(false))).WillByDefault(Return(allUnits));
|
||||
|
||||
confirmResults({{BattleHex(1)}, {BattleHex(3)}});
|
||||
confirmResults({{BattleHex(1)}, {BattleHex(3)}});
|
||||
}
|
||||
|
||||
TEST_F(SpellTargetEvaluatorTest, ReturnsSuspectibleCreaturePositionsAndSingleRandomSurroundingHexForEachStackIfNeutralLocationSpell)
|
||||
{
|
||||
spellTargetTypes({AimType::LOCATION});
|
||||
ON_CALL(mechMock, isNeutralSpell()).WillByDefault(Return(true));
|
||||
spellTargetTypes({AimType::LOCATION});
|
||||
ON_CALL(mechMock, isNeutralSpell()).WillByDefault(Return(true));
|
||||
|
||||
addStack(BattleHex(72), casterSide);
|
||||
addStack(BattleHex(159), enemySide, false);
|
||||
addStack(BattleHex(23), casterSide);
|
||||
ON_CALL(battleMock, battleGetAllUnits(Eq(false))).WillByDefault(Return(allUnits));
|
||||
addStack(BattleHex(72), casterSide);
|
||||
addStack(BattleHex(159), enemySide, false);
|
||||
addStack(BattleHex(23), casterSide);
|
||||
ON_CALL(battleMock, battleGetAllUnits(Eq(false))).WillByDefault(Return(allUnits));
|
||||
|
||||
confirmResults(
|
||||
{
|
||||
{BattleHex(72)}, BattleHex(72).getAllNeighbouringTiles().toVector(),
|
||||
BattleHex(159).getAllNeighbouringTiles().toVector(),
|
||||
{BattleHex(23)}, BattleHex(23).getAllNeighbouringTiles().toVector()
|
||||
});
|
||||
confirmResults(
|
||||
{{BattleHex(72)},
|
||||
BattleHex(72).getAllNeighbouringTiles().toVector(),
|
||||
BattleHex(159).getAllNeighbouringTiles().toVector(),
|
||||
{BattleHex(23)},
|
||||
BattleHex(23).getAllNeighbouringTiles().toVector()}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(SpellTargetEvaluatorTest, ReturnsOneCaseOfEachOptimalCastIfNegativeLocationSpell)
|
||||
{
|
||||
spellTargetTypes({AimType::LOCATION});
|
||||
ON_CALL(mechMock, isNegativeSpell()).WillByDefault(Return(true));
|
||||
spellTargetTypes({AimType::LOCATION});
|
||||
ON_CALL(mechMock, isNegativeSpell()).WillByDefault(Return(true));
|
||||
|
||||
auto *enemyStack1 = addStack(BattleHex(90), enemySide);
|
||||
auto *enemyStack2 = addStack(BattleHex(106), enemySide);
|
||||
auto *enemyStack3 = addStack(BattleHex(1), enemySide);
|
||||
auto *enemyStack4 = addStack(BattleHex(37), enemySide);
|
||||
auto *enemyStack5 = addStack(BattleHex(41), enemySide);
|
||||
auto * enemyStack1 = addStack(BattleHex(90), enemySide);
|
||||
auto * enemyStack2 = addStack(BattleHex(106), enemySide);
|
||||
auto * enemyStack3 = addStack(BattleHex(1), enemySide);
|
||||
auto * enemyStack4 = addStack(BattleHex(37), enemySide);
|
||||
auto * enemyStack5 = addStack(BattleHex(41), enemySide);
|
||||
|
||||
auto *alliedStack1 = addStack(BattleHex(107), casterSide);
|
||||
auto *alliedStack2 = addStack(BattleHex(19), casterSide);
|
||||
auto * alliedStack1 = addStack(BattleHex(107), casterSide);
|
||||
auto * alliedStack2 = addStack(BattleHex(19), casterSide);
|
||||
|
||||
//optimal
|
||||
setAffectedStacksForCast(BattleHex(71), {enemyStack1, enemyStack2});
|
||||
setAffectedStacksForCast(BattleHex(88), {enemyStack1, enemyStack2});
|
||||
//suboptimal
|
||||
setAffectedStacksForCast(BattleHex(55), {enemyStack1});
|
||||
setAffectedStacksForCast(BattleHex(89), {enemyStack1, enemyStack2, alliedStack1});
|
||||
//optimal
|
||||
setAffectedStacksForCast(BattleHex(71), {enemyStack1, enemyStack2});
|
||||
setAffectedStacksForCast(BattleHex(88), {enemyStack1, enemyStack2});
|
||||
//suboptimal
|
||||
setAffectedStacksForCast(BattleHex(55), {enemyStack1});
|
||||
setAffectedStacksForCast(BattleHex(89), {enemyStack1, enemyStack2, alliedStack1});
|
||||
|
||||
//optimal
|
||||
setAffectedStacksForCast(BattleHex(18), {enemyStack3, alliedStack2});
|
||||
setAffectedStacksForCast(BattleHex(2), {enemyStack3, alliedStack2});
|
||||
//suboptimal
|
||||
setAffectedStacksForCast(BattleHex(53), {alliedStack2});
|
||||
//optimal
|
||||
setAffectedStacksForCast(BattleHex(18), {enemyStack3, alliedStack2});
|
||||
setAffectedStacksForCast(BattleHex(2), {enemyStack3, alliedStack2});
|
||||
//suboptimal
|
||||
setAffectedStacksForCast(BattleHex(53), {alliedStack2});
|
||||
|
||||
//optimal
|
||||
setAffectedStacksForCast(BattleHex(39), {enemyStack4, enemyStack5});
|
||||
//suboptimal
|
||||
setAffectedStacksForCast(BattleHex(21), {enemyStack4});
|
||||
setAffectedStacksForCast(BattleHex(25), {enemyStack5});
|
||||
//optimal
|
||||
setAffectedStacksForCast(BattleHex(39), {enemyStack4, enemyStack5});
|
||||
//suboptimal
|
||||
setAffectedStacksForCast(BattleHex(21), {enemyStack4});
|
||||
setAffectedStacksForCast(BattleHex(25), {enemyStack5});
|
||||
|
||||
confirmResults(
|
||||
{
|
||||
{BattleHex(71), BattleHex(88)},
|
||||
{BattleHex(2), BattleHex(18)},
|
||||
{BattleHex(39)}
|
||||
});
|
||||
confirmResults({
|
||||
{BattleHex(71), BattleHex(88)},
|
||||
{BattleHex(2), BattleHex(18)},
|
||||
{BattleHex(39)}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user