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 heroStrength;
delete turnDistance; delete turnDistance;
delete missionImportance; delete missionImportance;
delete estimatedReward;
} }
void FuzzyHelper::initVisitTile() void FuzzyHelper::initVisitTile()
@ -348,11 +349,12 @@ void FuzzyHelper::initVisitTile()
vt.heroStrength = new fl::InputVariable("heroStrength"); //we want to use weakest possible hero 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.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.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 = new fl::OutputVariable("Value");
vt.value->setMinimum(0); vt.value->setMinimum(0);
vt.value->setMaximum(5); 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) for (auto val : helper)
{ {
vt.engine.addInputVariable(val); vt.engine.addInputVariable(val);
@ -379,6 +381,10 @@ void FuzzyHelper::initVisitTile()
vt.missionImportance->addTerm(new fl::Ramp("HIGH", 2.5, 5)); vt.missionImportance->addTerm(new fl::Ramp("HIGH", 2.5, 5));
vt.missionImportance->setRange(0.0, 5.0); 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 :/ //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 //should be same as "mission Importance" to keep consistency
vt.value->addTerm(new fl::Ramp("LOW", 2.5, 0)); 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 SMALL then Value is HIGH");
vt.addRule("if turnDistance is MEDIUM then Value is MEDIUM"); vt.addRule("if turnDistance is MEDIUM then Value is MEDIUM");
vt.addRule("if turnDistance is LONG then Value is LOW"); 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) catch (fl::Exception & fe)
{ {
@ -440,12 +449,22 @@ float FuzzyHelper::evaluate (Goals::VisitTile & g)
if (danger) if (danger)
strengthRatio = (fl::scalar)g.hero.h->getTotalStrength() / 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 try
{ {
vt.strengthRatio->setInputValue(strengthRatio); vt.strengthRatio->setInputValue(strengthRatio);
vt.heroStrength->setInputValue((fl::scalar)g.hero->getTotalStrength() / ai->primaryHero()->getTotalStrength()); vt.heroStrength->setInputValue((fl::scalar)g.hero->getTotalStrength() / ai->primaryHero()->getTotalStrength());
vt.turnDistance->setInputValue(turns); vt.turnDistance->setInputValue(turns);
vt.missionImportance->setInputValue(missionImportance); vt.missionImportance->setInputValue(missionImportance);
vt.estimatedReward->setInputValue(tilePriority);
vt.engine.process(); vt.engine.process();
//engine.process(VISIT_TILE); //TODO: Process only Visit_Tile //engine.process(VISIT_TILE); //TODO: Process only Visit_Tile

View File

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

View File

@ -719,10 +719,12 @@ TSubgoal VisitTile::whatToDoToAchieve()
{ {
auto ret = fh->chooseSolution(getAllPossibleSubgoals()); 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); ret->setisElementar(true);
return ret; return ret;
} }
@ -973,40 +975,44 @@ TGoalVec Conquer::getAllPossibleSubgoals()
}; };
std::vector<const CGObjectInstance *> objs; 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); objs.push_back (obj);
} }
for (auto h : cb->getHeroesInfo()) for(auto h : cb->getHeroesInfo())
{ {
auto sm = ai->getCachedSectorMap(h); auto sm = ai->getCachedSectorMap(h);
std::vector<const CGObjectInstance *> ourObjs(objs); //copy common objects 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); ourObjs.push_back(obj);
} }
for (auto obj : ourObjs) for(auto obj : ourObjs)
{ {
int3 dest = obj->visitablePos(); int3 dest = obj->visitablePos();
auto t = sm->firstTileToGet(h, dest); //we assume that no more than one tile on the way is guarded 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))); ret.push_back(sptr(Goals::ClearWayTo(dest, h).setisAbstract(true)));
else 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))); ret.push_back(sptr(Goals::VisitHero(obj->id.getNum()).sethero(h).setisAbstract(true)));
else //just visit that tile 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 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 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())); ret.push_back(sptr(Goals::RecruitHero()));
break; 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. //Set of buildings for different goals. Does not include any prerequisites.
static const BuildingID essential[] = {BuildingID::TAVERN, BuildingID::TOWN_HALL}; static const BuildingID essential[] = {BuildingID::TAVERN, BuildingID::TOWN_HALL};
static const BuildingID goldSource[] = {BuildingID::TOWN_HALL, BuildingID::CITY_HALL, BuildingID::CAPITOL}; 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, 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}; 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, 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 //TODO: build resource silo, defences when needed
//Possible - allow "locking" on specific building (build prerequisites and then building itself) //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 currentRes = cb->getResourceAmount();
TResources currentIncome = t->dailyIncome(); TResources currentIncome = t->dailyIncome();
int townIncome = currentIncome[Res::GOLD]; 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; return;
//we're running out of gold - try to build something gold-producing. Multiplier can be tweaked, 6 is minimum due to buildings costs //the more gold the better and less problems later
if (currentRes[Res::GOLD] < townIncome * 6) if(tryBuildNextStructure(t, std::vector<BuildingID>(goldSource, goldSource + ARRAY_COUNT(goldSource))))
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; 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; return;
} }
// first in-game week or second half of any week: try build dwellings // 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(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(tryBuildAnyStructure(t, std::vector<BuildingID>(unitsSource, unitsSource + ARRAY_COUNT(unitsSource)), 8 - cb->getDate(Date::DAY_OF_WEEK)))
return; return;
//try to upgrade dwelling //try to upgrade dwelling
for(int i = 0; i < ARRAY_COUNT(unitsUpgrade); i++) 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; return;
} }
} }
//remaining tasks //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; return;
if (tryBuildNextStructure(t, std::vector<BuildingID>(spells, spells + ARRAY_COUNT(spells)))) if(tryBuildAnyStructure(t, std::vector<BuildingID>(extra, extra + ARRAY_COUNT(extra))))
return;
if (tryBuildAnyStructure(t, std::vector<BuildingID>(extra, extra + ARRAY_COUNT(extra))))
return; return;
//at the end, try to get and build any extra buildings with nonstandard slots (for example HotA 3rd level dwelling) //at the end, try to get and build any extra buildings with nonstandard slots (for example HotA 3rd level dwelling)
std::vector<BuildingID> extraBuildings; std::vector<BuildingID> extraBuildings;
for (auto buildingInfo : t->town->buildings) for(auto buildingInfo : t->town->buildings)
if (buildingInfo.first > 43) if(buildingInfo.first > 43)
extraBuildings.push_back(buildingInfo.first); extraBuildings.push_back(buildingInfo.first);
if (tryBuildAnyStructure(t, extraBuildings)) if(tryBuildAnyStructure(t, extraBuildings))
return; return;
} }
@ -2865,7 +2875,13 @@ void VCAI::validateObject(ObjectIdRef obj)
TResources VCAI::freeResources() const TResources VCAI::freeResources() const
{ {
TResources myRes = cb->getResourceAmount(); 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); vstd::amax(myRes[Res::GOLD], 0);
return myRes; return myRes;
} }

View File

@ -866,6 +866,11 @@ void CGHeroInstance::setPropertyDer( ui8 what, ui32 val )
setStackCount(SlotID(0), val); setStackCount(SlotID(0), val);
} }
TFaction CGHeroInstance::getFaction() const
{
return type->heroClass->faction;
}
double CGHeroInstance::getFightingStrength() const double CGHeroInstance::getFightingStrength() const
{ {
return sqrt((1.0 + 0.05*getPrimSkillLevel(PrimarySkill::ATTACK)) * (1.0 + 0.05*getPrimSkillLevel(PrimarySkill::DEFENSE))); 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; EAlignment::EAlignment getAlignment() const;
const std::string &getBiography() const; const std::string &getBiography() const;
bool needsLastStack()const override; 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 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; int getNativeTerrain() const;
ui32 getLowestCreatureSpeed() const; ui32 getLowestCreatureSpeed() const;