1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

VCAI tweaks (#311)

* Add extra priority support for town capture evaluation
* Improve building algorithm
* GatherArmy: check free gold instead of total for when hiring heroes
This commit is contained in:
Dydzio 2017-07-15 00:15:08 +02:00 committed by ArseniyShestakov
parent 98140aab6b
commit 0cb6515ae8
6 changed files with 84 additions and 36 deletions

View File

@ -338,6 +338,7 @@ FuzzyHelper::EvalVisitTile::~EvalVisitTile()
delete heroStrength;
delete turnDistance;
delete missionImportance;
delete estimatedReward;
}
void FuzzyHelper::initVisitTile()
@ -348,11 +349,12 @@ void FuzzyHelper::initVisitTile()
vt.heroStrength = new fl::InputVariable("heroStrength"); //we want to use weakest possible hero
vt.turnDistance = new fl::InputVariable("turnDistance"); //we want to use hero who is near
vt.missionImportance = new fl::InputVariable("lockedMissionImportance"); //we may want to preempt hero with low-priority mission
vt.estimatedReward = new fl::InputVariable("estimatedReward"); //indicate AI that content of the file is important or it is probably bad
vt.value = new fl::OutputVariable("Value");
vt.value->setMinimum(0);
vt.value->setMaximum(5);
std::vector<fl::InputVariable*> helper = {vt.strengthRatio, vt.heroStrength, vt.turnDistance, vt.missionImportance};
std::vector<fl::InputVariable*> helper = {vt.strengthRatio, vt.heroStrength, vt.turnDistance, vt.missionImportance, vt.estimatedReward};
for (auto val : helper)
{
vt.engine.addInputVariable(val);
@ -379,6 +381,10 @@ void FuzzyHelper::initVisitTile()
vt.missionImportance->addTerm(new fl::Ramp("HIGH", 2.5, 5));
vt.missionImportance->setRange(0.0, 5.0);
vt.estimatedReward->addTerm(new fl::Ramp("LOW", 2.5, 0));
vt.estimatedReward->addTerm(new fl::Ramp("HIGH", 2.5, 5));
vt.estimatedReward->setRange(0.0, 5.0);
//an issue: in 99% cases this outputs center of mass (2.5) regardless of actual input :/
//should be same as "mission Importance" to keep consistency
vt.value->addTerm(new fl::Ramp("LOW", 2.5, 0));
@ -405,6 +411,9 @@ void FuzzyHelper::initVisitTile()
vt.addRule("if turnDistance is SMALL then Value is HIGH");
vt.addRule("if turnDistance is MEDIUM then Value is MEDIUM");
vt.addRule("if turnDistance is LONG then Value is LOW");
//some goals are more rewarding by definition f.e. capturing town is more important than collecting resource - experimental
vt.addRule("if estimatedReward is HIGH then Value is very HIGH");
vt.addRule("if estimatedReward is LOW then Value is somewhat LOW");
}
catch (fl::Exception & fe)
{
@ -440,12 +449,22 @@ float FuzzyHelper::evaluate (Goals::VisitTile & g)
if (danger)
strengthRatio = (fl::scalar)g.hero.h->getTotalStrength() / danger;
float tilePriority = 0;
if(g.objid == -1)
vt.estimatedReward->setEnabled(false);
else if(g.objid == Obj::TOWN) //TODO: move to getObj eventually and add appropiate logic there
{
vt.estimatedReward->setEnabled(true);
tilePriority = 5;
}
try
{
vt.strengthRatio->setInputValue(strengthRatio);
vt.heroStrength->setInputValue((fl::scalar)g.hero->getTotalStrength() / ai->primaryHero()->getTotalStrength());
vt.turnDistance->setInputValue(turns);
vt.missionImportance->setInputValue(missionImportance);
vt.estimatedReward->setInputValue(tilePriority);
vt.engine.process();
//engine.process(VISIT_TILE); //TODO: Process only Visit_Tile

View File

@ -49,6 +49,7 @@ class FuzzyHelper
fl::InputVariable * heroStrength;
fl::InputVariable * turnDistance;
fl::InputVariable * missionImportance;
fl::InputVariable * estimatedReward;
fl::OutputVariable * value;
fl::RuleBlock rules;
~EvalVisitTile();

View File

@ -719,10 +719,12 @@ TSubgoal VisitTile::whatToDoToAchieve()
{
auto ret = fh->chooseSolution(getAllPossibleSubgoals());
if (ret->hero)
if(ret->hero)
{
if (isSafeToVisit(ret->hero, tile) && ai->isAccessibleForHero(tile, ret->hero))
if(isSafeToVisit(ret->hero, tile) && ai->isAccessibleForHero(tile, ret->hero))
{
if(cb->getTile(tile)->topVisitableId().num == Obj::TOWN) //if target is town, fuzzy system will use additional "estimatedReward" variable to increase priority a bit
ret->objid = Obj::TOWN; //TODO: move to getObj eventually and add appropiate logic there
ret->setisElementar(true);
return ret;
}
@ -973,40 +975,44 @@ TGoalVec Conquer::getAllPossibleSubgoals()
};
std::vector<const CGObjectInstance *> objs;
for (auto obj : ai->visitableObjs)
for(auto obj : ai->visitableObjs)
{
if (conquerable(obj))
if(conquerable(obj))
objs.push_back (obj);
}
for (auto h : cb->getHeroesInfo())
for(auto h : cb->getHeroesInfo())
{
auto sm = ai->getCachedSectorMap(h);
std::vector<const CGObjectInstance *> ourObjs(objs); //copy common objects
for (auto obj : ai->reservedHeroesMap[h]) //add objects reserved by this hero
for(auto obj : ai->reservedHeroesMap[h]) //add objects reserved by this hero
{
if (conquerable(obj))
if(conquerable(obj))
ourObjs.push_back(obj);
}
for (auto obj : ourObjs)
for(auto obj : ourObjs)
{
int3 dest = obj->visitablePos();
auto t = sm->firstTileToGet(h, dest); //we assume that no more than one tile on the way is guarded
if (t.valid()) //we know any path at all
if(t.valid()) //we know any path at all
{
if (ai->isTileNotReserved(h, t)) //no other hero wants to conquer that tile
if(ai->isTileNotReserved(h, t)) //no other hero wants to conquer that tile
{
if (isSafeToVisit(h, dest))
if(isSafeToVisit(h, dest))
{
if (dest != t) //there is something blocking our way
if(dest != t) //there is something blocking our way
ret.push_back(sptr(Goals::ClearWayTo(dest, h).setisAbstract(true)));
else
{
if (obj->ID.num == Obj::HERO) //enemy hero may move to other position
if(obj->ID.num == Obj::HERO) //enemy hero may move to other position
ret.push_back(sptr(Goals::VisitHero(obj->id.getNum()).sethero(h).setisAbstract(true)));
else //just visit that tile
ret.push_back(sptr(Goals::VisitTile(dest).sethero(h).setisAbstract(true)));
if(obj->ID.num == Obj::TOWN)
//if target is town, fuzzy system will use additional "estimatedReward" variable to increase priority a bit
ret.push_back(sptr(Goals::VisitTile(dest).sethero(h).setobjid(obj->ID.num).setisAbstract(true))); //TODO: change to getObj eventually and and move appropiate logic there
else
ret.push_back(sptr(Goals::VisitTile(dest).sethero(h).setisAbstract(true)));
}
}
else //we need to get army in order to conquer that place
@ -1124,13 +1130,13 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
}
}
if (ai->canRecruitAnyHero()) //this is not stupid in early phase of game
if(ai->canRecruitAnyHero() && ai->freeResources()[Res::GOLD] > GameConstants::HERO_GOLD_COST) //this is not stupid in early phase of game
{
if (auto t = ai->findTownWithTavern())
if(auto t = ai->findTownWithTavern())
{
for (auto h : cb->getAvailableHeroes(t)) //we assume that all towns have same set of heroes
{
if (h && h->getTotalStrength() > 500) //do not buy heroes with single creatures for GatherArmy
if(h && h->getTotalStrength() > 500) //do not buy heroes with single creatures for GatherArmy
{
ret.push_back(sptr(Goals::RecruitHero()));
break;

View File

@ -1352,6 +1352,7 @@ bool VCAI::tryBuildNextStructure(const CGTownInstance * t, std::vector<BuildingI
//Set of buildings for different goals. Does not include any prerequisites.
static const BuildingID essential[] = {BuildingID::TAVERN, BuildingID::TOWN_HALL};
static const BuildingID goldSource[] = {BuildingID::TOWN_HALL, BuildingID::CITY_HALL, BuildingID::CAPITOL};
static const BuildingID capitolRequirements[] = { BuildingID::FORT, BuildingID::CITADEL };
static const BuildingID unitsSource[] = { BuildingID::DWELL_LVL_1, BuildingID::DWELL_LVL_2, BuildingID::DWELL_LVL_3,
BuildingID::DWELL_LVL_4, BuildingID::DWELL_LVL_5, BuildingID::DWELL_LVL_6, BuildingID::DWELL_LVL_7};
static const BuildingID unitsUpgrade[] = { BuildingID::DWELL_LVL_1_UP, BuildingID::DWELL_LVL_2_UP, BuildingID::DWELL_LVL_3_UP,
@ -1370,55 +1371,64 @@ void VCAI::buildStructure(const CGTownInstance * t)
//TODO: build resource silo, defences when needed
//Possible - allow "locking" on specific building (build prerequisites and then building itself)
//below algorithm focuses on economy growth at start of the game.
TResources currentRes = cb->getResourceAmount();
TResources currentIncome = t->dailyIncome();
int townIncome = currentIncome[Res::GOLD];
if (tryBuildAnyStructure(t, std::vector<BuildingID>(essential, essential + ARRAY_COUNT(essential))))
if(tryBuildAnyStructure(t, std::vector<BuildingID>(essential, essential + ARRAY_COUNT(essential))))
return;
//we're running out of gold - try to build something gold-producing. Multiplier can be tweaked, 6 is minimum due to buildings costs
if (currentRes[Res::GOLD] < townIncome * 6)
if (tryBuildNextStructure(t, std::vector<BuildingID>(goldSource, goldSource + ARRAY_COUNT(goldSource))))
//the more gold the better and less problems later
if(tryBuildNextStructure(t, std::vector<BuildingID>(goldSource, goldSource + ARRAY_COUNT(goldSource))))
return;
//workaround for mantis #2696 - build fort and citadel - building castle will be handled without bug
if(vstd::contains(t->builtBuildings, BuildingID::CITY_HALL) && cb->canBuildStructure(t, BuildingID::CAPITOL) != EBuildingState::HAVE_CAPITAL &&
cb->canBuildStructure(t, BuildingID::CAPITOL) != EBuildingState::FORBIDDEN)
if(tryBuildNextStructure(t, std::vector<BuildingID>(capitolRequirements, capitolRequirements + ARRAY_COUNT(capitolRequirements))))
return;
if (cb->getDate(Date::DAY_OF_WEEK) > 6)// last 2 days of week - try to focus on growth
if((!vstd::contains(t->builtBuildings, BuildingID::CAPITOL) && cb->canBuildStructure(t, BuildingID::CAPITOL) != EBuildingState::HAVE_CAPITAL && cb->canBuildStructure(t, BuildingID::CAPITOL) != EBuildingState::FORBIDDEN)
|| (!vstd::contains(t->builtBuildings, BuildingID::CITY_HALL) && cb->canBuildStructure(t, BuildingID::CAPITOL) == EBuildingState::HAVE_CAPITAL && cb->canBuildStructure(t, BuildingID::CITY_HALL) != EBuildingState::FORBIDDEN)
|| (!vstd::contains(t->builtBuildings, BuildingID::TOWN_HALL) && cb->canBuildStructure(t, BuildingID::TOWN_HALL) != EBuildingState::FORBIDDEN))
return; //save money for capitol or city hall if capitol unavailable, do not build other things (unless gold source buildings are disabled in map editor)
if(cb->getDate(Date::DAY_OF_WEEK) > 6)// last 2 days of week - try to focus on growth
{
if (tryBuildNextStructure(t, std::vector<BuildingID>(unitGrowth, unitGrowth + ARRAY_COUNT(unitGrowth)), 2))
if(tryBuildNextStructure(t, std::vector<BuildingID>(unitGrowth, unitGrowth + ARRAY_COUNT(unitGrowth)), 2))
return;
}
// first in-game week or second half of any week: try build dwellings
if (cb->getDate(Date::DAY) < 7 || cb->getDate(Date::DAY_OF_WEEK) > 3)
if (tryBuildAnyStructure(t, std::vector<BuildingID>(unitsSource, unitsSource + ARRAY_COUNT(unitsSource)), 8 - cb->getDate(Date::DAY_OF_WEEK)))
if(cb->getDate(Date::DAY) < 7 || cb->getDate(Date::DAY_OF_WEEK) > 3)
if(tryBuildAnyStructure(t, std::vector<BuildingID>(unitsSource, unitsSource + ARRAY_COUNT(unitsSource)), 8 - cb->getDate(Date::DAY_OF_WEEK)))
return;
//try to upgrade dwelling
for(int i = 0; i < ARRAY_COUNT(unitsUpgrade); i++)
{
if (t->hasBuilt(unitsSource[i]) && !t->hasBuilt(unitsUpgrade[i]))
if(t->hasBuilt(unitsSource[i]) && !t->hasBuilt(unitsUpgrade[i]))
{
if (tryBuildStructure(t, unitsUpgrade[i]))
if(tryBuildStructure(t, unitsUpgrade[i]))
return;
}
}
//remaining tasks
if (tryBuildNextStructure(t, std::vector<BuildingID>(goldSource, goldSource + ARRAY_COUNT(goldSource))))
if(tryBuildNextStructure(t, std::vector<BuildingID>(spells, spells + ARRAY_COUNT(spells))))
return;
if (tryBuildNextStructure(t, std::vector<BuildingID>(spells, spells + ARRAY_COUNT(spells))))
return;
if (tryBuildAnyStructure(t, std::vector<BuildingID>(extra, extra + ARRAY_COUNT(extra))))
if(tryBuildAnyStructure(t, std::vector<BuildingID>(extra, extra + ARRAY_COUNT(extra))))
return;
//at the end, try to get and build any extra buildings with nonstandard slots (for example HotA 3rd level dwelling)
std::vector<BuildingID> extraBuildings;
for (auto buildingInfo : t->town->buildings)
if (buildingInfo.first > 43)
for(auto buildingInfo : t->town->buildings)
if(buildingInfo.first > 43)
extraBuildings.push_back(buildingInfo.first);
if (tryBuildAnyStructure(t, extraBuildings))
if(tryBuildAnyStructure(t, extraBuildings))
return;
}
@ -2865,7 +2875,13 @@ void VCAI::validateObject(ObjectIdRef obj)
TResources VCAI::freeResources() const
{
TResources myRes = cb->getResourceAmount();
myRes[Res::GOLD] -= GOLD_RESERVE;
auto iterator = cb->getTownsInfo();
if(std::none_of(iterator.begin(), iterator.end(), [](const CGTownInstance * x)->bool
{
return x->builtBuildings.find(BuildingID::CAPITOL) != x->builtBuildings.end();
})
/*|| std::all_of(iterator.begin(), iterator.end(), [](const CGTownInstance * x) -> bool { return x->forbiddenBuildings.find(BuildingID::CAPITOL) != x->forbiddenBuildings.end(); })*/ )
myRes[Res::GOLD] -= GOLD_RESERVE; //what if capitol is blocked from building in all possessed towns (set in map editor)? What about reserve for city hall or something similar in that case?
vstd::amax(myRes[Res::GOLD], 0);
return myRes;
}

View File

@ -866,6 +866,11 @@ void CGHeroInstance::setPropertyDer( ui8 what, ui32 val )
setStackCount(SlotID(0), val);
}
TFaction CGHeroInstance::getFaction() const
{
return type->heroClass->faction;
}
double CGHeroInstance::getFightingStrength() const
{
return sqrt((1.0 + 0.05*getPrimSkillLevel(PrimarySkill::ATTACK)) * (1.0 + 0.05*getPrimSkillLevel(PrimarySkill::DEFENSE)));

View File

@ -147,6 +147,7 @@ public:
EAlignment::EAlignment getAlignment() const;
const std::string &getBiography() const;
bool needsLastStack()const override;
TFaction getFaction() const;
ui32 getTileCost(const TerrainTile &dest, const TerrainTile &from, const TurnInfo * ti) const; //move cost - applying pathfinding skill, road and terrain modifiers. NOT includes diagonal move penalty, last move levelling
int getNativeTerrain() const;
ui32 getLowestCreatureSpeed() const;