TSubgoal CollectRes::whatToDoToAchieve() { std::vector<const IMarket*> markets; std::vector<const CGObjectInstance*> visObjs; ai->retreiveVisitableObjs(visObjs, true); for(const CGObjectInstance *obj : visObjs) { if(const IMarket *m = IMarket::castFrom(obj, false)) { if(obj->ID == Obj::TOWN && obj->tempOwner == ai->playerID && m->allowsTrade(EMarketMode::RESOURCE_RESOURCE)) markets.push_back(m); else if(obj->ID == Obj::TRADING_POST) //TODO a moze po prostu test na pozwalanie handlu? markets.push_back(m); } } boost::sort(markets, [](const IMarket *m1, const IMarket *m2) -> bool { return m1->getMarketEfficiency() < m2->getMarketEfficiency(); }); markets.erase(boost::remove_if(markets, [](const IMarket *market) -> bool { return !(market->o->ID == Obj::TOWN && market->o->tempOwner == ai->playerID) && !ai->isAccessible(market->o->visitablePos()); }),markets.end()); if(!markets.size()) { for(const CGTownInstance *t : cb->getTownsInfo()) { if(cb->canBuildStructure(t, BuildingID::MARKETPLACE) == EBuildingState::ALLOWED) return sptr (Goals::BuildThis(BuildingID::MARKETPLACE, t)); } } else { const IMarket *m = markets.back(); //attempt trade at back (best prices) int howManyCanWeBuy = 0; for(Res::ERes i = Res::WOOD; i <= Res::GOLD; vstd::advance(i, 1)) { if(i == resID) continue; int toGive = -1, toReceive = -1; m->getOffer(i, resID, toGive, toReceive, EMarketMode::RESOURCE_RESOURCE); assert(toGive > 0 && toReceive > 0); howManyCanWeBuy += toReceive * (cb->getResourceAmount(i) / toGive); } if(howManyCanWeBuy + cb->getResourceAmount(static_cast<Res::ERes>(resID)) >= value) { auto backObj = cb->getTopObj(m->o->visitablePos()); //it'll be a hero if we have one there; otherwise marketplace assert(backObj); if (backObj->tempOwner != ai->playerID) { return sptr (Goals::GetObj(m->o->id.getNum())); } else { return sptr (Goals::GetObj(m->o->id.getNum()).setisElementar(true)); } } } return sptr (setisElementar(true)); //all the conditions for trade are met }
TSubgoal CollectRes::whatToDoToTrade() { std::vector<const IMarket *> markets; std::vector<const CGObjectInstance *> visObjs; ai->retrieveVisitableObjs(visObjs, true); for (const CGObjectInstance * obj : visObjs) { if (const IMarket * m = IMarket::castFrom(obj, false)) { if (obj->ID == Obj::TOWN && obj->tempOwner == ai->playerID && m->allowsTrade(EMarketMode::RESOURCE_RESOURCE)) markets.push_back(m); else if (obj->ID == Obj::TRADING_POST) markets.push_back(m); } } boost::sort(markets, [](const IMarket * m1, const IMarket * m2) -> bool { return m1->getMarketEfficiency() < m2->getMarketEfficiency(); }); markets.erase(boost::remove_if(markets, [](const IMarket * market) -> bool { if (!(market->o->ID == Obj::TOWN && market->o->tempOwner == ai->playerID)) { if (!ai->isAccessible(market->o->visitablePos())) return true; } return false; }), markets.end()); if (!markets.size()) { for (const CGTownInstance * t : cb->getTownsInfo()) { if (cb->canBuildStructure(t, BuildingID::MARKETPLACE) == EBuildingState::ALLOWED) return sptr(BuildThis(BuildingID::MARKETPLACE, t).setpriority(2)); } } else { const IMarket * m = markets.back(); //attempt trade at back (best prices) int howManyCanWeBuy = 0; for (Res::ERes i = Res::WOOD; i <= Res::GOLD; vstd::advance(i, 1)) { if (i == resID) continue; int toGive = -1, toReceive = -1; m->getOffer(i, resID, toGive, toReceive, EMarketMode::RESOURCE_RESOURCE); assert(toGive > 0 && toReceive > 0); howManyCanWeBuy += toReceive * (ai->ah->freeResources()[i] / toGive); } if (howManyCanWeBuy >= value) { auto backObj = cb->getTopObj(m->o->visitablePos()); //it'll be a hero if we have one there; otherwise marketplace assert(backObj); auto objid = m->o->id.getNum(); if (backObj->tempOwner != ai->playerID) //top object not owned { return sptr(VisitObj(objid)); //just go there } else //either it's our town, or we have hero there { return sptr(Trade(resID, value, objid).setisElementar(true)); //we can do this immediately } } } return sptr(Invalid()); //cannot trade }
TSubgoal Win::whatToDoToAchieve() { auto toBool = [=](const EventCondition &) { // TODO: proper implementation // Right now even already fulfilled goals will be included into generated list // Proper check should test if event condition is already fulfilled // Easiest way to do this is to call CGameState::checkForVictory but this function should not be // used on client side or in AI code return false; }; std::vector<EventCondition> goals; for (const TriggeredEvent & event : cb->getMapHeader()->triggeredEvents) { //TODO: try to eliminate human player(s) using loss conditions that have isHuman element if (event.effect.type == EventEffect::VICTORY) { boost::range::copy(event.trigger.getFulfillmentCandidates(toBool), std::back_inserter(goals)); } } //TODO: instead of returning first encountered goal AI should generate list of possible subgoals for (const EventCondition & goal : goals) { switch(goal.condition) { case EventCondition::HAVE_ARTIFACT: return sptr (Goals::GetArtOfType(goal.objectType)); case EventCondition::DESTROY: { if (goal.object) { return sptr (Goals::GetObj(goal.object->id.getNum())); } else { // TODO: destroy all objects of type goal.objectType // This situation represents "kill all creatures" condition from H3 break; } } case EventCondition::HAVE_BUILDING: { // TODO build other buildings apart from Grail // goal.objectType = buidingID to build // goal.object = optional, town in which building should be built // Represents "Improve town" condition from H3 (but unlike H3 it consists from 2 separate conditions) if (goal.objectType == BuildingID::GRAIL) { if(auto h = ai->getHeroWithGrail()) { //hero is in a town that can host Grail if(h->visitedTown && !vstd::contains(h->visitedTown->forbiddenBuildings, BuildingID::GRAIL)) { const CGTownInstance *t = h->visitedTown; return sptr (Goals::BuildThis(BuildingID::GRAIL, t)); } else { auto towns = cb->getTownsInfo(); towns.erase(boost::remove_if(towns, [](const CGTownInstance *t) -> bool { return vstd::contains(t->forbiddenBuildings, BuildingID::GRAIL); }), towns.end()); boost::sort(towns, isCloser); if(towns.size()) { return sptr (Goals::VisitTile(towns.front()->visitablePos()).sethero(h)); } } } double ratio = 0; // maybe make this check a bit more complex? For example: // 0.75 -> dig randomly withing 3 tiles radius // 0.85 -> radius now 2 tiles // 0.95 -> 1 tile radius, position is fully known // AFAIK H3 AI does something like this int3 grailPos = cb->getGrailPos(ratio); if(ratio > 0.99) { return sptr (Goals::DigAtTile(grailPos)); } //TODO: use FIND_OBJ else if(const CGObjectInstance * obj = ai->getUnvisitedObj(objWithID<Obj::OBELISK>)) //there are unvisited Obelisks return sptr (Goals::GetObj(obj->id.getNum())); else return sptr (Goals::Explore()); } break; } case EventCondition::CONTROL: { if (goal.object) { return sptr (Goals::GetObj(goal.object->id.getNum())); } else { //TODO: control all objects of type "goal.objectType" // Represents H3 condition "Flag all mines" break; } } case EventCondition::HAVE_RESOURCES: //TODO mines? piles? marketplace? //save? return sptr (Goals::CollectRes(static_cast<Res::ERes>(goal.objectType), goal.value)); case EventCondition::HAVE_CREATURES: return sptr (Goals::GatherTroops(goal.objectType, goal.value)); case EventCondition::TRANSPORT: { //TODO. merge with bring Grail to town? So AI will first dig grail, then transport it using this goal and builds it // Represents "transport artifact" condition: // goal.objectType = type of artifact // goal.object = destination-town where artifact should be transported break; } case EventCondition::STANDARD_WIN: return sptr (Goals::Conquer()); // Conditions that likely don't need any implementation case EventCondition::DAYS_PASSED: break; // goal.value = number of days for condition to trigger case EventCondition::DAYS_WITHOUT_TOWN: break; // goal.value = number of days to trigger this case EventCondition::IS_HUMAN: break; // Should be only used in calculation of candidates (see toBool lambda) case EventCondition::CONST_VALUE: break; default: assert(0); } } return sptr (Goals::Invalid()); }
TGoalVec GatherArmy::getAllPossibleSubgoals() { //get all possible towns, heroes and dwellings we may use TGoalVec ret; if(!hero.validAndSet()) { return ret; } //TODO: include evaluation of monsters gather in calculation for(auto t : cb->getTownsInfo()) { auto waysToVisit = ai->ah->howToVisitObj(hero, t); if(waysToVisit.size()) { //grab army from town if(!t->visitingHero && howManyReinforcementsCanGet(hero.get(), t)) { if(!vstd::contains(ai->townVisitsThisWeek[hero], t)) vstd::concatenate(ret, waysToVisit); } //buy army in town if (!t->visitingHero || t->visitingHero == hero.get(true)) { std::vector<int> values = { value, (int)howManyReinforcementsCanBuy(t->getUpperArmy(), t), (int)howManyReinforcementsCanBuy(hero.get(), t) }; int val = *std::min_element(values.begin(), values.end()); if (val) { auto goal = sptr(BuyArmy(t, val).sethero(hero)); if(!ai->ah->containsObjective(goal)) //avoid loops caused by reserving same objective twice ret.push_back(goal); else logAi->debug("Can not buy army, because of ai->ah->containsObjective"); } } //build dwelling //TODO: plan building over multiple turns? //auto bid = ah->canBuildAnyStructure(t, std::vector<BuildingID>(unitsSource, unitsSource + ARRAY_COUNT(unitsSource)), 8 - cb->getDate(Date::DAY_OF_WEEK)); //Do not use below code for now, rely on generic Build. Code below needs to know a lot of town/resource context to do more good than harm /*auto bid = ai->ah->canBuildAnyStructure(t, std::vector<BuildingID>(unitsSource, unitsSource + ARRAY_COUNT(unitsSource)), 1); if (bid.is_initialized()) { auto goal = sptr(BuildThis(bid.get(), t).setpriority(priority)); if (!ai->ah->containsObjective(goal)) //avoid loops caused by reserving same objective twice ret.push_back(goal); else logAi->debug("Can not build a structure, because of ai->ah->containsObjective"); }*/ } } auto otherHeroes = cb->getHeroesInfo(); auto heroDummy = hero; vstd::erase_if(otherHeroes, [heroDummy](const CGHeroInstance * h) { if(h == heroDummy.h) return true; else if(!ai->isAccessibleForHero(heroDummy->visitablePos(), h, true)) return true; else if(!ai->canGetArmy(heroDummy.h, h)) //TODO: return actual aiValue return true; else if(ai->getGoal(h)->goalType == GATHER_ARMY) return true; else return false; }); for(auto h : otherHeroes) { // Go to the other hero if we are faster if(!vstd::contains(ai->visitedHeroes[hero], h)) { vstd::concatenate(ret, ai->ah->howToVisitObj(hero, h)); } // Go to the other hero if we are faster if(!vstd::contains(ai->visitedHeroes[h], hero)) { vstd::concatenate(ret, ai->ah->howToVisitObj(h, hero.get())); } } std::vector<const CGObjectInstance *> objs; for(auto obj : ai->visitableObjs) { if(obj->ID == Obj::CREATURE_GENERATOR1) { auto relationToOwner = cb->getPlayerRelations(obj->getOwner(), ai->playerID); //Use flagged dwellings only when there are available creatures that we can afford if(relationToOwner == PlayerRelations::SAME_PLAYER) { auto dwelling = dynamic_cast<const CGDwelling *>(obj); ui32 val = std::min<ui32>(value, howManyReinforcementsCanBuy(hero.get(), dwelling)); if(val) { for(auto & creLevel : dwelling->creatures) { if(creLevel.first) { for(auto & creatureID : creLevel.second) { auto creature = VLC->creh->creatures[creatureID]; if(ai->ah->freeResources().canAfford(creature->cost)) objs.push_back(obj); //TODO: reserve resources? } } } } } } } for(auto h : cb->getHeroesInfo()) { for(auto obj : objs) { //find safe dwelling if(ai->isGoodForVisit(obj, h)) { vstd::concatenate(ret, ai->ah->howToVisitObj(h, obj)); } } } if(ai->canRecruitAnyHero() && ai->ah->freeGold() > GameConstants::HERO_GOLD_COST) //this is not stupid in early phase of game { 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 { ret.push_back(sptr(RecruitHero())); break; } } } } if(ret.empty()) { const bool allowGatherArmy = false; if(hero == ai->primaryHero()) ret.push_back(sptr(Explore(allowGatherArmy))); else throw cannotFulfillGoalException("No ways to gather army"); } return ret; }