From 0cb6515ae8f5e8d783d5f7ab4a4db4c3f3bebc63 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Sat, 15 Jul 2017 00:15:08 +0200 Subject: [PATCH] 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 --- AI/VCAI/Fuzzy.cpp | 21 ++++++++++++- AI/VCAI/Fuzzy.h | 1 + AI/VCAI/Goals.cpp | 40 ++++++++++++++---------- AI/VCAI/VCAI.cpp | 52 ++++++++++++++++++++----------- lib/mapObjects/CGHeroInstance.cpp | 5 +++ lib/mapObjects/CGHeroInstance.h | 1 + 6 files changed, 84 insertions(+), 36 deletions(-) diff --git a/AI/VCAI/Fuzzy.cpp b/AI/VCAI/Fuzzy.cpp index 4622fd739..a42cc9db1 100644 --- a/AI/VCAI/Fuzzy.cpp +++ b/AI/VCAI/Fuzzy.cpp @@ -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 helper = {vt.strengthRatio, vt.heroStrength, vt.turnDistance, vt.missionImportance}; + std::vector 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 diff --git a/AI/VCAI/Fuzzy.h b/AI/VCAI/Fuzzy.h index f099d44a1..8a02d36fc 100644 --- a/AI/VCAI/Fuzzy.h +++ b/AI/VCAI/Fuzzy.h @@ -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(); diff --git a/AI/VCAI/Goals.cpp b/AI/VCAI/Goals.cpp index bc6680feb..abb439f62 100644 --- a/AI/VCAI/Goals.cpp +++ b/AI/VCAI/Goals.cpp @@ -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 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 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; diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 33439a339..5d8819f37 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -1352,6 +1352,7 @@ bool VCAI::tryBuildNextStructure(const CGTownInstance * t, std::vectorgetResourceAmount(); TResources currentIncome = t->dailyIncome(); int townIncome = currentIncome[Res::GOLD]; - if (tryBuildAnyStructure(t, std::vector(essential, essential + ARRAY_COUNT(essential)))) + if(tryBuildAnyStructure(t, std::vector(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(goldSource, goldSource + ARRAY_COUNT(goldSource)))) + //the more gold the better and less problems later + if(tryBuildNextStructure(t, std::vector(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(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(unitGrowth, unitGrowth + ARRAY_COUNT(unitGrowth)), 2)) + if(tryBuildNextStructure(t, std::vector(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(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(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(goldSource, goldSource + ARRAY_COUNT(goldSource)))) + if(tryBuildNextStructure(t, std::vector(spells, spells + ARRAY_COUNT(spells)))) return; - if (tryBuildNextStructure(t, std::vector(spells, spells + ARRAY_COUNT(spells)))) - return; - if (tryBuildAnyStructure(t, std::vector(extra, extra + ARRAY_COUNT(extra)))) + if(tryBuildAnyStructure(t, std::vector(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 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; } diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 57db26b5c..54124a8f7 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -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))); diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index c96d374f8..0243f3388 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -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;