1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-02-03 13:01:33 +02:00

Endless crusade against AI issues and loopholes!

- Fixed #1126
- ClearWayTo and GatherArmy goals will also consider multiple subgoals
- GatherArmy may include building dwellings in town (experimental)
This commit is contained in:
DjWarmonger 2013-12-23 20:46:01 +00:00
parent 7806bda663
commit aec04d920e
7 changed files with 326 additions and 208 deletions

View File

@ -237,6 +237,7 @@ int3 whereToExplore(HeroPtr h)
} }
} }
} }
removeDuplicates (nearbyVisitableObjs); //one object may occupy multiple tiles
boost::sort(nearbyVisitableObjs, isCloser); boost::sort(nearbyVisitableObjs, isCloser);
if(nearbyVisitableObjs.size()) if(nearbyVisitableObjs.size())
return nearbyVisitableObjs.back()->visitablePos(); return nearbyVisitableObjs.back()->visitablePos();
@ -247,8 +248,7 @@ int3 whereToExplore(HeroPtr h)
} }
catch(cannotFulfillGoalException &e) catch(cannotFulfillGoalException &e)
{ {
std::vector<std::vector<int3> > tiles; //tiles[distance_to_fow] return ai->explorationNewPoint(radius, h);
return ai->explorationNewPoint(radius, h, tiles);
} }
} }

View File

@ -337,6 +337,7 @@ FuzzyHelper::EvalVisitTile::~EvalVisitTile()
delete heroStrength; delete heroStrength;
delete tileDistance; delete tileDistance;
delete missionImportance; delete missionImportance;
delete movement;
} }
void FuzzyHelper::initVisitTile() void FuzzyHelper::initVisitTile()
@ -347,9 +348,10 @@ void FuzzyHelper::initVisitTile()
vt.heroStrength = new fl::InputLVar("heroStrength"); //we want to use weakest possible hero vt.heroStrength = new fl::InputLVar("heroStrength"); //we want to use weakest possible hero
vt.tileDistance = new fl::InputLVar("tileDistance"); //we want to use hero who is near vt.tileDistance = new fl::InputLVar("tileDistance"); //we want to use hero who is near
vt.missionImportance = new fl::InputLVar("lockedMissionImportance"); //we may want to preempt hero with low-priority mission vt.missionImportance = new fl::InputLVar("lockedMissionImportance"); //we may want to preempt hero with low-priority mission
vt.movement = new fl::InputLVar("movement");
vt.value = new fl::OutputLVar("Value"); vt.value = new fl::OutputLVar("Value");
helper += vt.strengthRatio, vt.heroStrength, vt.tileDistance, vt.missionImportance; helper += vt.strengthRatio, vt.heroStrength, vt.tileDistance, vt.missionImportance, vt.movement;
vt.strengthRatio->addTerm (new fl::ShoulderTerm("LOW", 0.9, SAFE_ATTACK_CONSTANT, true)); vt.strengthRatio->addTerm (new fl::ShoulderTerm("LOW", 0.9, SAFE_ATTACK_CONSTANT, true));
vt.strengthRatio->addTerm (new fl::ShoulderTerm("HIGH", SAFE_ATTACK_CONSTANT, SAFE_ATTACK_CONSTANT * 3, false)); vt.strengthRatio->addTerm (new fl::ShoulderTerm("HIGH", SAFE_ATTACK_CONSTANT, SAFE_ATTACK_CONSTANT * 3, false));
@ -369,6 +371,9 @@ void FuzzyHelper::initVisitTile()
vt.value->addTerm (new fl::ShoulderTerm("LOW", 0, 1.1, true)); vt.value->addTerm (new fl::ShoulderTerm("LOW", 0, 1.1, true));
vt.value->addTerm (new fl::ShoulderTerm("HIGH", 1, 5, false)); vt.value->addTerm (new fl::ShoulderTerm("HIGH", 1, 5, false));
vt.movement->addTerm (new fl::ShoulderTerm("LOW", 1, 200, true));
vt.movement->addTerm (new fl::ShoulderTerm("HIGH", 1000, 2000, false));
for (auto val : helper) for (auto val : helper)
{ {
engine.addInputLVar(val); engine.addInputLVar(val);
@ -387,6 +392,9 @@ void FuzzyHelper::initVisitTile()
//pick nearby objects if it's easy, avoid long walks //pick nearby objects if it's easy, avoid long walks
vt.rules.addRule (new fl::MamdaniRule("if tileDistance is SMALL then Value is HIGH", engine)); vt.rules.addRule (new fl::MamdaniRule("if tileDistance is SMALL then Value is HIGH", engine));
vt.rules.addRule (new fl::MamdaniRule("if tileDistance is LONG then Value is LOW", engine)); vt.rules.addRule (new fl::MamdaniRule("if tileDistance is LONG then Value is LOW", engine));
//use heroes with movement points first
vt.rules.addRule (new fl::MamdaniRule("if movement is LOW then Value is somewhat LOW", engine));
vt.rules.addRule (new fl::MamdaniRule("if movement is HIGH then Value is somewhat HIGH", engine));
engine.addRuleBlock (&vt.rules); engine.addRuleBlock (&vt.rules);
} }
@ -416,6 +424,7 @@ float FuzzyHelper::evaluate (Goals::VisitTile & g)
vt.heroStrength->setInput (g.hero->getTotalStrength()); vt.heroStrength->setInput (g.hero->getTotalStrength());
vt.tileDistance->setInput (distance); vt.tileDistance->setInput (distance);
vt.missionImportance->setInput (missionImportance); vt.missionImportance->setInput (missionImportance);
vt.movement->setInput(g.hero->movement);
engine.process (VISIT_TILE); engine.process (VISIT_TILE);
output = vt.value->output().defuzzify(); output = vt.value->output().defuzzify();
@ -435,7 +444,7 @@ float FuzzyHelper::evaluate (Goals::VisitHero & g)
} }
float FuzzyHelper::evaluate (Goals::BuildThis & g) float FuzzyHelper::evaluate (Goals::BuildThis & g)
{ {
return 0; return 1;
} }
float FuzzyHelper::evaluate (Goals::DigAtTile & g) float FuzzyHelper::evaluate (Goals::DigAtTile & g)
{ {

View File

@ -47,6 +47,7 @@ class FuzzyHelper
fl::InputLVar * heroStrength; fl::InputLVar * heroStrength;
fl::InputLVar * tileDistance; fl::InputLVar * tileDistance;
fl::InputLVar * missionImportance; fl::InputLVar * missionImportance;
fl::InputLVar * movement;
fl::OutputLVar * value; fl::OutputLVar * value;
fl::RuleBlock rules; fl::RuleBlock rules;
~EvalVisitTile(); ~EvalVisitTile();

View File

@ -29,6 +29,7 @@ TSubgoal Goals::sptr(const AbstractGoal & tmp)
std::string Goals::AbstractGoal::name() const //TODO: virtualize std::string Goals::AbstractGoal::name() const //TODO: virtualize
{ {
std::string desc;
switch (goalType) switch (goalType)
{ {
case INVALID: case INVALID:
@ -42,38 +43,53 @@ std::string Goals::AbstractGoal::name() const //TODO: virtualize
case BUILD: case BUILD:
return "BUILD"; return "BUILD";
case EXPLORE: case EXPLORE:
return "EXPLORE"; desc = "EXPLORE";
break;
case GATHER_ARMY: case GATHER_ARMY:
return "GATHER ARMY"; desc = "GATHER ARMY";
break;
case BOOST_HERO: case BOOST_HERO:
return "BOOST_HERO (unsupported)"; desc = "BOOST_HERO (unsupported)";
break;
case RECRUIT_HERO: case RECRUIT_HERO:
return "RECRUIT HERO"; return "RECRUIT HERO";
case BUILD_STRUCTURE: case BUILD_STRUCTURE:
return "BUILD STRUCTURE"; return "BUILD STRUCTURE";
case COLLECT_RES: case COLLECT_RES:
return "COLLECT RESOURCE"; desc = "COLLECT RESOURCE";
break;
case GATHER_TROOPS: case GATHER_TROOPS:
return "GATHER TROOPS"; desc = "GATHER TROOPS";
break;
case GET_OBJ: case GET_OBJ:
return "GET OBJECT " + boost::lexical_cast<std::string>(objid); desc = "GET OBJ " + cb->getObjInstance(ObjectInstanceID(objid))->getHoverText();
break;
case FIND_OBJ: case FIND_OBJ:
return "FIND OBJECT " + boost::lexical_cast<std::string>(objid); desc = "FIND OBJ " + boost::lexical_cast<std::string>(objid);
break;
case VISIT_HERO: case VISIT_HERO:
return "VISIT HERO " + boost::lexical_cast<std::string>(objid); desc = "VISIT HERO " + cb->getObjInstance(ObjectInstanceID(objid))->getHoverText();
break;
case GET_ART_TYPE: case GET_ART_TYPE:
return "GET ARTIFACT OF TYPE " + VLC->arth->artifacts[aid]->Name(); desc = "GET ARTIFACT OF TYPE " + VLC->arth->artifacts[aid]->Name();
break;
case ISSUE_COMMAND: case ISSUE_COMMAND:
return "ISSUE COMMAND (unsupported)"; return "ISSUE COMMAND (unsupported)";
case VISIT_TILE: case VISIT_TILE:
return "VISIT TILE " + tile(); desc = "VISIT TILE " + tile();
break;
case CLEAR_WAY_TO: case CLEAR_WAY_TO:
return "CLEAR WAY TO " + tile(); desc = "CLEAR WAY TO " + tile();
break;
case DIG_AT_TILE: case DIG_AT_TILE:
return "DIG AT TILE " + tile(); desc = "DIG AT TILE " + tile();
break;
default: default:
return boost::lexical_cast<std::string>(goalType); return boost::lexical_cast<std::string>(goalType);
} }
if (hero.h)
desc += " (" + hero->name + ")";
return desc;
} }
//TODO: find out why the following are not generated automatically on MVS? //TODO: find out why the following are not generated automatically on MVS?
@ -312,52 +328,56 @@ float GetArtOfType::importanceWhenLocked() const
TSubgoal ClearWayTo::whatToDoToAchieve() TSubgoal ClearWayTo::whatToDoToAchieve()
{ {
assert(tile.x >= 0); //set tile assert(cb->isInTheMap(tile)); //set tile
if(!cb->isVisible(tile)) if(!cb->isVisible(tile))
{ {
logAi->errorStream() << "Clear way should be used with visible tiles!"; logAi->errorStream() << "Clear way should be used with visible tiles!";
return sptr (Goals::Explore()); return sptr (Goals::Explore());
} }
HeroPtr h = hero ? hero : ai->primaryHero(); return (fh->chooseSolution(getAllPossibleSubgoals()));
if(!h) }
return sptr (Goals::RecruitHero());
cb->setSelection(*h); TGoalVec ClearWayTo::getAllPossibleSubgoals()
{
TGoalVec ret;
for (auto h : cb->getHeroesInfo())
{
cb->setSelection(h);
SectorMap sm; SectorMap sm;
bool dropToFile = false;
if(dropToFile) //for debug purposes
sm.write("test.txt");
int3 tileToHit = sm.firstTileToGet(h, tile); int3 tileToHit = sm.firstTileToGet(hero ? hero : h, tile);
//if(isSafeToVisit(h, tileToHit)) //if our hero is trapped, make sure we request clearing the way from OUR perspective
if(isBlockedBorderGate(tileToHit))
if (isBlockedBorderGate(tileToHit))
{ //FIXME: this way we'll not visit gate and activate quest :? { //FIXME: this way we'll not visit gate and activate quest :?
return sptr (Goals::FindObj (Obj::KEYMASTER, cb->getTile(tileToHit)->visitableObjects.back()->subID)); ret.push_back (sptr (Goals::FindObj (Obj::KEYMASTER, cb->getTile(tileToHit)->visitableObjects.back()->subID)));
} }
//FIXME: this code shouldn't be necessary ////FIXME: this code shouldn't be necessary
if(tileToHit == tile) //if(tileToHit == tile)
{ //{
logAi->errorStream() << boost::format("Very strange, tile to hit is %s and tile is also %s, while hero %s is at %s\n") // logAi->errorStream() << boost::format("Very strange, tile to hit is %s and tile is also %s, while hero %s is at %s\n")
% tileToHit % tile % h->name % h->visitablePos(); // % tileToHit % tile % h->name % h->visitablePos();
throw cannotFulfillGoalException("Retrieving first tile to hit failed (probably)!"); // throw cannotFulfillGoalException("Retrieving first tile to hit failed (probably)!");
} //}
auto topObj = backOrNull(cb->getVisitableObjs(tileToHit)); auto topObj = backOrNull(cb->getVisitableObjs(tileToHit));
if(topObj && topObj->ID == Obj::HERO && cb->getPlayerRelations(h->tempOwner, topObj->tempOwner) != PlayerRelations::ENEMIES) if(topObj && topObj->ID == Obj::HERO && cb->getPlayerRelations(h->tempOwner, topObj->tempOwner) != PlayerRelations::ENEMIES)
{ {
std::string problem = boost::str(boost::format("%s stands in the way of %s.\n") % topObj->getHoverText() % h->getHoverText()); logAi->errorStream() << boost::format("%s stands in the way of %s.\n") % topObj->getHoverText() % h->getHoverText();
throw cannotFulfillGoalException(problem);
} }
else
ret.push_back (sptr (Goals::VisitTile(tileToHit).sethero(h)));
}
if (ai->canRecruitAnyHero())
ret.push_back (sptr (Goals::RecruitHero()));
return sptr (Goals::VisitTile(tileToHit).sethero(h)); if (ret.empty())
//FIXME:: attempts to visit completely unreachable tile with hero results in stall throw cannotFulfillGoalException("There is no known way to clear the way to tile " + tile());
//TODO czy istnieje lepsza droga? return ret;
throw cannotFulfillGoalException("Cannot reach given tile!"); //how and when could this be used?
} }
float ClearWayTo::importanceWhenLocked() const float ClearWayTo::importanceWhenLocked() const
@ -387,12 +407,15 @@ TSubgoal Explore::whatToDoToAchieve()
TGoalVec Explore::getAllPossibleSubgoals() TGoalVec Explore::getAllPossibleSubgoals()
{ {
TGoalVec ret; TGoalVec ret;
std::vector<HeroPtr> heroes; std::vector<const CGHeroInstance *> heroes;
//std::vector<HeroPtr> heroes;
if (hero) if (hero)
heroes.push_back(hero); //heroes.push_back(hero);
heroes.push_back(hero.h);
else else
{ {
heroes = ai->getUnblockedHeroes(); //heroes = ai->getUnblockedHeroes();
heroes = cb->getHeroesInfo();
erase_if (heroes, [](const HeroPtr h) erase_if (heroes, [](const HeroPtr h)
{ {
return !h->movement; //saves time, immobile heroes are useless anyway return !h->movement; //saves time, immobile heroes are useless anyway
@ -429,9 +452,20 @@ TGoalVec Explore::getAllPossibleSubgoals()
if (cb->isInTheMap(t)) //valid tile was found - could be invalid (none) if (cb->isInTheMap(t)) //valid tile was found - could be invalid (none)
ret.push_back (sptr (Goals::VisitTile(t).sethero(h))); ret.push_back (sptr (Goals::VisitTile(t).sethero(h)));
} }
if (!hero && ai->canRecruitAnyHero())//if hero is assigned to that goal, no need to buy another one yet //we either don't have hero yet or none of heroes can explore
if ((!hero || ret.empty()) && ai->canRecruitAnyHero())
ret.push_back (sptr(Goals::RecruitHero())); ret.push_back (sptr(Goals::RecruitHero()));
if (ret.empty())
{
auto h = ai->primaryHero(); //we may need to gather big army to break!
if (h.h)
{
int3 t = ai->explorationNewPoint(h->getSightRadious(), h, true);
if (cb->isInTheMap(t))
ret.push_back (sptr(ClearWayTo(t).setisAbstract(true).sethero(ai->primaryHero())));
}
}
if (ret.empty()) if (ret.empty())
throw cannotFulfillGoalException("Cannot explore - no possible ways found!"); throw cannotFulfillGoalException("Cannot explore - no possible ways found!");
@ -473,7 +507,8 @@ TSubgoal VisitTile::whatToDoToAchieve()
} }
else else
{ {
return sptr (Goals::GatherArmy(evaluateDanger(tile, *ret->hero) * SAFE_ATTACK_CONSTANT).sethero(ret->hero)); return sptr (Goals::GatherArmy(evaluateDanger(tile, *ret->hero) * SAFE_ATTACK_CONSTANT)
.sethero(ret->hero).setisAbstract(true));
} }
} }
return ret; return ret;
@ -507,6 +542,7 @@ TGoalVec VisitTile::getAllPossibleSubgoals()
} }
if (ret.empty()) if (ret.empty())
ret.push_back (sptr(Goals::ClearWayTo(tile))); ret.push_back (sptr(Goals::ClearWayTo(tile)));
//important - at least one sub-goal must handle case which is impossible to fulfill (unreachable tile) //important - at least one sub-goal must handle case which is impossible to fulfill (unreachable tile)
return ret; return ret;
} }
@ -746,68 +782,54 @@ std::string GatherArmy::completeMessage() const
TSubgoal GatherArmy::whatToDoToAchieve() TSubgoal GatherArmy::whatToDoToAchieve()
{ {
//TODO: find hero if none set //TODO: find hero if none set
assert(hero); assert(hero.h);
cb->setSelection(*hero); return fh->chooseSolution (getAllPossibleSubgoals()); //find dwelling. use current hero to prevent him from doing nothing.
auto compareReinforcements = [this](const CGTownInstance *lhs, const CGTownInstance *rhs) -> bool }
TGoalVec GatherArmy::getAllPossibleSubgoals()
{
//get all possible towns, heroes and dwellings we may use
TGoalVec ret;
//TODO: include evaluation of monsters gather in calculation
for (auto t : cb->getTownsInfo())
{ {
return howManyReinforcementsCanGet(hero, lhs) < howManyReinforcementsCanGet(hero, rhs); auto pos = t->visitablePos();
}; if (ai->isAccessibleForHero(pos, hero))
std::vector<const CGTownInstance *> townsReachable;
for(const CGTownInstance *t : cb->getTownsInfo())
{ {
if(!t->visitingHero && howManyReinforcementsCanGet(hero,t)) if(!t->visitingHero && howManyReinforcementsCanGet(hero,t))
{ {
if (ai->isAccessibleForHero(t->pos, hero) && !vstd::contains (ai->townVisitsThisWeek[hero], t)) if (!vstd::contains (ai->townVisitsThisWeek[hero], t))
townsReachable.push_back(t); ret.push_back (sptr (Goals::VisitTile(pos).sethero(hero)));
}
auto bid = ai->canBuildAnyStructure(t, std::vector<BuildingID>
(unitsSource, unitsSource + ARRAY_COUNT(unitsSource)), 8 - cb->getDate(Date::DAY_OF_WEEK));
if (bid != BuildingID::NONE)
ret.push_back (sptr(BuildThis(bid, t)));
} }
} }
if(townsReachable.size()) //try towns first
{
boost::sort(townsReachable, compareReinforcements);
return sptr (Goals::VisitTile(townsReachable.back()->visitablePos()).sethero(hero));
}
else
{
if (hero == ai->primaryHero()) //we can get army from other heroes
{
auto otherHeroes = cb->getHeroesInfo(); auto otherHeroes = cb->getHeroesInfo();
auto heroDummy = hero; auto heroDummy = hero;
erase_if(otherHeroes, [heroDummy](const CGHeroInstance * h) erase_if(otherHeroes, [heroDummy](const CGHeroInstance * h)
{ {
return (h == heroDummy.h || !ai->isAccessibleForHero(heroDummy->visitablePos(), h, true) || !ai->canGetArmy(heroDummy.h, h)); return (h == heroDummy.h || !ai->isAccessibleForHero(heroDummy->visitablePos(), h, true) || !ai->canGetArmy(heroDummy.h, h));
}); });
if (otherHeroes.size()) for (auto h : otherHeroes)
{ {
boost::sort(otherHeroes, compareArmyStrength); //TODO: check if hero has at least one stack more powerful than ours? not likely to fail
int primaryPath, secondaryPath;
auto h = otherHeroes.back();
cb->setSelection(hero.h);
primaryPath = cb->getPathInfo(h->visitablePos())->turns;
cb->setSelection(h);
secondaryPath = cb->getPathInfo(hero->visitablePos())->turns;
if (primaryPath < secondaryPath) ret.push_back (sptr (Goals::VisitHero(h->id.getNum()).setisAbstract(true).sethero(hero)));
return sptr (Goals::VisitHero(h->id.getNum()).setisAbstract(true).sethero(hero));
//go to the other hero if we are faster //go to the other hero if we are faster
else ret.push_back (sptr (Goals::VisitHero(hero->id.getNum()).setisAbstract(true).sethero(h)));
return sptr (Goals::VisitHero(hero->id.getNum()).setisAbstract(true).sethero(h));
//let the other hero come to us //let the other hero come to us
} }
}
std::vector<const CGObjectInstance *> objs; //here we'll gather all dwellings std::vector <const CGObjectInstance *> objs;
ai->retreiveVisitableObjs(objs, true); for (auto obj : ai->visitableObjs)
erase_if(objs, [&](const CGObjectInstance *obj) {
if(obj->ID == Obj::CREATURE_GENERATOR1)
{ {
if(obj->ID != Obj::CREATURE_GENERATOR1)
return true;
auto relationToOwner = cb->getPlayerRelations(obj->getOwner(), ai->playerID); auto relationToOwner = cb->getPlayerRelations(obj->getOwner(), ai->playerID);
if(relationToOwner == PlayerRelations::ALLIES)
return true;
//Use flagged dwellings only when there are available creatures that we can afford //Use flagged dwellings only when there are available creatures that we can afford
if(relationToOwner == PlayerRelations::SAME_PLAYER) if(relationToOwner == PlayerRelations::SAME_PLAYER)
@ -820,42 +842,27 @@ TSubgoal GatherArmy::whatToDoToAchieve()
for(auto & creatureID : creLevel.second) for(auto & creatureID : creLevel.second)
{ {
auto creature = VLC->creh->creatures[creatureID]; auto creature = VLC->creh->creatures[creatureID];
if(ai->freeResources().canAfford(creature->cost)) if (ai->freeResources().canAfford(creature->cost))
return false; objs.push_back(obj);
} }
} }
} }
} }
}
return true; }
}); for(auto h : cb->getHeroesInfo())
if(objs.empty()) //no possible objects, we did eveyrthing already
return sptr (Goals::Explore(hero));
//TODO: check if we can recruit any creatures there, evaluate army
else
{ {
boost::sort(objs, isCloser); for (auto obj : objs)
HeroPtr h = nullptr;
for(const CGObjectInstance *obj : objs)
{ //find safe dwelling { //find safe dwelling
auto pos = obj->visitablePos(); auto pos = obj->visitablePos();
if (shouldVisit (hero, obj)) //creatures fit in army if (shouldVisit (h, obj) && isSafeToVisit(h, pos) && ai->isAccessibleForHero(pos, h))
h = hero; ret.push_back (sptr (Goals::VisitTile(pos).sethero(h)));
else
{
for(auto ourHero : cb->getHeroesInfo()) //make use of multiple heroes
{
if (shouldVisit(ourHero, obj))
h = ourHero;
}
}
if (h && isSafeToVisit(h, pos) && ai->isAccessibleForHero(pos, h))
return sptr (Goals::VisitTile(pos).sethero(h));
}
} }
} }
if (ret.empty())
ret.push_back (sptr(Goals::Explore()));
return sptr (Goals::Explore(hero)); //find dwelling. use current hero to prevent him from doing nothing. return ret;
} }
float GatherArmy::importanceWhenLocked() const float GatherArmy::importanceWhenLocked() const

View File

@ -240,7 +240,7 @@ private:
GatherArmy() : CGoal (Goals::GATHER_ARMY){}; GatherArmy() : CGoal (Goals::GATHER_ARMY){};
public: public:
GatherArmy(int val) : CGoal (Goals::GATHER_ARMY){value = val;}; GatherArmy(int val) : CGoal (Goals::GATHER_ARMY){value = val;};
TGoalVec getAllPossibleSubgoals() override {return TGoalVec();}; TGoalVec getAllPossibleSubgoals() override;
TSubgoal whatToDoToAchieve() override; TSubgoal whatToDoToAchieve() override;
std::string completeMessage() const override; std::string completeMessage() const override;
float importanceWhenLocked() const override; float importanceWhenLocked() const override;
@ -356,7 +356,7 @@ class ClearWayTo : public CGoal<ClearWayTo>
{ {
public: public:
ClearWayTo(int3 Tile) : CGoal (Goals::CLEAR_WAY_TO) {tile = Tile;}; ClearWayTo(int3 Tile) : CGoal (Goals::CLEAR_WAY_TO) {tile = Tile;};
TGoalVec getAllPossibleSubgoals() override {return TGoalVec();}; TGoalVec getAllPossibleSubgoals() override;
TSubgoal whatToDoToAchieve() override; TSubgoal whatToDoToAchieve() override;
bool operator== (ClearWayTo &g) {return g.tile == tile;} bool operator== (ClearWayTo &g) {return g.tile == tile;}
float importanceWhenLocked() const override; float importanceWhenLocked() const override;

View File

@ -406,9 +406,11 @@ void VCAI::newStackInserted(const StackLocation &location, const CStackInstance
NET_EVENT_HANDLER; NET_EVENT_HANDLER;
} }
void VCAI::heroCreated(const CGHeroInstance*) void VCAI::heroCreated(const CGHeroInstance* h)
{ {
LOG_TRACE(logAi); LOG_TRACE(logAi);
if (h->visitedTown)
townVisitsThisWeek[HeroPtr(h)].push_back(h->visitedTown);
NET_EVENT_HANDLER; NET_EVENT_HANDLER;
} }
@ -523,6 +525,9 @@ void VCAI::init(shared_ptr<CCallback> CB)
fh = new FuzzyHelper(); fh = new FuzzyHelper();
retreiveVisitableObjs(visitableObjs); retreiveVisitableObjs(visitableObjs);
//for (auto h : myCb->getHeroesInfo()) //make sure heroes won't try to revisit town in first move
// if (h->visitedTown)
// markObjectVisited(h->visitedTown);
} }
void VCAI::yourTurn() void VCAI::yourTurn()
@ -651,30 +656,6 @@ void VCAI::makeTurn()
} }
} }
break; break;
case 7: //reconsider strategy
{
if(auto h = primaryHero()) //check if our primary hero can handle danger
{
ui64 totalDanger = 0;
int dangerousObjects = 0;
std::vector<const CGObjectInstance *> objs;
retreiveVisitableObjs(objs, false);
for (auto obj : objs)
{
if (evaluateDanger(obj)) //potentilaly dnagerous
{
totalDanger += evaluateDanger(obj->visitablePos(), *h);
++dangerousObjects;
}
}
ui64 averageDanger = totalDanger / std::max(dangerousObjects, 1);
if (dangerousObjects && averageDanger > h->getHeroStrength())
{
setGoal (h, sptr(Goals::GatherArmy(averageDanger * SAFE_ATTACK_CONSTANT).sethero(h).setisAbstract(true)));
}
}
}
break;
} }
if(cb->getSelectedHero()) if(cb->getSelectedHero())
cb->recalculatePaths(); cb->recalculatePaths();
@ -1006,6 +987,84 @@ bool VCAI::tryBuildStructure(const CGTownInstance * t, BuildingID building, unsi
return false; return false;
} }
//bool VCAI::canBuildStructure(const CGTownInstance * t, BuildingID building, unsigned int maxDays=7)
//{
// if (maxDays == 0)
// {
// logAi->warnStream() << "Request to build building " << building << " in 0 days!";
// return false;
// }
//
// if (!vstd::contains(t->town->buildings, building))
// return false; // no such building in town
//
// if (t->hasBuilt(building)) //Already built? Shouldn't happen in general
// return true;
//
// const CBuilding * buildPtr = t->town->buildings.at(building);
//
// auto toBuild = buildPtr->requirements.getFulfillmentCandidates([&](const BuildingID & buildID)
// {
// return t->hasBuilt(buildID);
// });
// toBuild.push_back(building);
//
// for(BuildingID buildID : toBuild)
// {
// EBuildingState::EBuildingState canBuild = cb->canBuildStructure(t, buildID);
// if (canBuild == EBuildingState::HAVE_CAPITAL
// || canBuild == EBuildingState::FORBIDDEN
// || canBuild == EBuildingState::NO_WATER)
// return false; //we won't be able to build this
// }
//
// if (maxDays && toBuild.size() > maxDays)
// return false;
//
// TResources currentRes = cb->getResourceAmount();
// TResources income = estimateIncome();
// //TODO: calculate if we have enough resources to build it in maxDays
//
// for(const auto & buildID : toBuild)
// {
// const CBuilding *b = t->town->buildings.at(buildID);
//
// EBuildingState::EBuildingState canBuild = cb->canBuildStructure(t, buildID);
// if(canBuild == EBuildingState::ALLOWED)
// {
// if(!containsSavedRes(b->resources))
// {
// logAi->debugStream() << boost::format("Player %d will build %s in town of %s at %s") % playerID % b->Name() % t->name % t->pos;
// return true;
// }
// continue;
// }
// else if(canBuild == EBuildingState::NO_RESOURCES)
// {
// TResources cost = t->town->buildings.at(buildID)->resources;
// for (int i = 0; i < GameConstants::RESOURCE_QUANTITY; i++)
// {
// int diff = currentRes[i] - cost[i] + income[i];
// if(diff < 0)
// saving[i] = 1;
// }
// continue;
// }
// else if (canBuild == EBuildingState::PREREQUIRES)
// {
// // can happen when dependencies have their own missing dependencies
// if (canBuildStructure(t, buildID, maxDays - 1))
// return true;
// }
// else if (canBuild == EBuildingState::MISSING_BASE)
// {
// if (canBuildStructure(t, b->upgrade, maxDays - 1))
// return true;
// }
// }
// return false;
//}
bool VCAI::tryBuildAnyStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays) bool VCAI::tryBuildAnyStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays)
{ {
for(const auto & building : buildList) for(const auto & building : buildList)
@ -1018,6 +1077,18 @@ bool VCAI::tryBuildAnyStructure(const CGTownInstance * t, std::vector<BuildingID
return false; //Can't build anything return false; //Can't build anything
} }
BuildingID VCAI::canBuildAnyStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays)
{
for(const auto & building : buildList)
{
if(t->hasBuilt(building))
continue;
if (cb->canBuildStructure(t, building))
return building;
}
return BuildingID::NONE; //Can't build anything
}
bool VCAI::tryBuildNextStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays) bool VCAI::tryBuildNextStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays)
{ {
for(const auto & building : buildList) for(const auto & building : buildList)
@ -1036,20 +1107,6 @@ 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)
//Set of buildings for different goals. Does not include any prerequisites.
const BuildingID essential[] = {BuildingID::TAVERN, BuildingID::TOWN_HALL};
const BuildingID goldSource[] = {BuildingID::TOWN_HALL, BuildingID::CITY_HALL, BuildingID::CAPITOL};
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};
const BuildingID unitsUpgrade[] = { BuildingID::DWELL_LVL_1_UP, BuildingID::DWELL_LVL_2_UP, BuildingID::DWELL_LVL_3_UP,
BuildingID::DWELL_LVL_4_UP, BuildingID::DWELL_LVL_5_UP, BuildingID::DWELL_LVL_6_UP, BuildingID::DWELL_LVL_7_UP};
const BuildingID unitGrowth[] = { BuildingID::FORT, BuildingID::CITADEL, BuildingID::CASTLE, BuildingID::HORDE_1,
BuildingID::HORDE_1_UPGR, BuildingID::HORDE_2, BuildingID::HORDE_2_UPGR};
const BuildingID spells[] = {BuildingID::MAGES_GUILD_1, BuildingID::MAGES_GUILD_2, BuildingID::MAGES_GUILD_3,
BuildingID::MAGES_GUILD_4, BuildingID::MAGES_GUILD_5};
const BuildingID extra[] = {BuildingID::RESOURCE_SILO, BuildingID::SPECIAL_1, BuildingID::SPECIAL_2, BuildingID::SPECIAL_3,
BuildingID::SPECIAL_4, BuildingID::SHIPYARD}; // all remaining buildings
TResources currentRes = cb->getResourceAmount(); TResources currentRes = cb->getResourceAmount();
TResources income = estimateIncome(); TResources income = estimateIncome();
@ -1147,6 +1204,7 @@ bool VCAI::canRecruitAnyHero (const CGTownInstance * t) const
void VCAI::wander(HeroPtr h) void VCAI::wander(HeroPtr h)
{ {
TimeCheck tc("looking for wander destination");
while(1) while(1)
{ {
validateVisitableObjs(); validateVisitableObjs();
@ -1164,6 +1222,9 @@ void VCAI::wander(HeroPtr h)
if(!dests.size()) if(!dests.size())
{ {
if (cb->getVisitableObjs(h->visitablePos()).size() > 1)
moveHeroToTile(h->visitablePos(), h); //just in case we're standing on blocked subterranean gate
auto compareReinforcements = [h](const CGTownInstance *lhs, const CGTownInstance *rhs) -> bool auto compareReinforcements = [h](const CGTownInstance *lhs, const CGTownInstance *rhs) -> bool
{ {
return howManyReinforcementsCanGet(h, lhs) < howManyReinforcementsCanGet(h, rhs); return howManyReinforcementsCanGet(h, lhs) < howManyReinforcementsCanGet(h, rhs);
@ -1193,7 +1254,9 @@ void VCAI::wander(HeroPtr h)
const CGTownInstance *t = townsNotReachable.back(); const CGTownInstance *t = townsNotReachable.back();
logAi->debugStream() << boost::format("%s can't reach any town, we'll try to make our way to %s at %s") % h->name % t->name % t->visitablePos(); logAi->debugStream() << boost::format("%s can't reach any town, we'll try to make our way to %s at %s") % h->name % t->name % t->visitablePos();
int3 pos1 = h->pos; int3 pos1 = h->pos;
striveToGoal(sptr(Goals::VisitTile(t->visitablePos()).sethero(h))); striveToGoal(sptr(Goals::ClearWayTo(t->visitablePos()).sethero(h)));
//if out hero is stuck, we may need to request another hero to clear the way we see
if (pos1 == h->pos && h == primaryHero()) //hero can't move if (pos1 == h->pos && h == primaryHero()) //hero can't move
{ {
if (canRecruitAnyHero(t)) if (canRecruitAnyHero(t))
@ -1233,10 +1296,6 @@ void VCAI::wander(HeroPtr h)
else else
{ {
logAi->debugStream() << boost::format("Hero %s apparently used all MPs (%d left)") % h->name % h->movement; logAi->debugStream() << boost::format("Hero %s apparently used all MPs (%d left)") % h->name % h->movement;
reserveObject(h, dest); //reserve that object - we predict it will be reached soon
//removed - do not forget abstract goal so easily
//setGoal(h, CGoal(VISIT_TILE).sethero(h).settile(dest->visitablePos()));
} }
break; break;
} }
@ -1520,9 +1579,15 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
//BNLOG("Hero %s moved from %s to %s at %s", h->name % startHpos % visitedObject->hoverName % h->visitablePos()); //BNLOG("Hero %s moved from %s to %s at %s", h->name % startHpos % visitedObject->hoverName % h->visitablePos());
//throw goalFulfilledException (CGoal(GET_OBJ).setobjid(visitedObject->id)); //throw goalFulfilledException (CGoal(GET_OBJ).setobjid(visitedObject->id));
} }
if(h) //we could have lost hero after last move if(h) //we could have lost hero after last move
{ {
if (!ret) //reserve object we are heading towards
{
auto obj = frontOrNull(cb->getVisitableObjs(dst));
if (obj)
reserveObject(h, obj);
}
cb->recalculatePaths(); cb->recalculatePaths();
if (startHpos == h->visitablePos() && !ret) //we didn't move and didn't reach the target if (startHpos == h->visitablePos() && !ret) //we didn't move and didn't reach the target
{ {
@ -2023,9 +2088,11 @@ int3 VCAI::explorationBestNeighbour(int3 hpos, int radius, HeroPtr h)
throw cannotFulfillGoalException("No neighbour will bring new discoveries!"); throw cannotFulfillGoalException("No neighbour will bring new discoveries!");
} }
int3 VCAI::explorationNewPoint(int radius, HeroPtr h, std::vector<std::vector<int3> > &tiles) int3 VCAI::explorationNewPoint(int radius, HeroPtr h, bool breakUnsafe)
{ {
logAi->debugStream() << "Looking for an another place for exploration..."; logAi->debugStream() << "Looking for an another place for exploration...";
std::vector<std::vector<int3> > tiles; //tiles[distance_to_fow]
tiles.resize(radius); tiles.resize(radius);
foreach_tile_pos([&](const int3 &pos) foreach_tile_pos([&](const int3 &pos)
@ -2034,6 +2101,9 @@ int3 VCAI::explorationNewPoint(int radius, HeroPtr h, std::vector<std::vector<in
tiles[0].push_back(pos); tiles[0].push_back(pos);
}); });
int bestValue = 0;
int3 bestTile(-1,-1,-1);
for (int i = 1; i < radius; i++) for (int i = 1; i < radius; i++)
{ {
getVisibleNeighbours(tiles[i-1], tiles[i]); getVisibleNeighbours(tiles[i-1], tiles[i]);
@ -2043,15 +2113,18 @@ int3 VCAI::explorationNewPoint(int radius, HeroPtr h, std::vector<std::vector<in
{ {
if (cb->getTile(tile)->blocked) //does it shorten the time? if (cb->getTile(tile)->blocked) //does it shorten the time?
continue; continue;
if(cb->getPathInfo(tile)->reachable() && howManyTilesWillBeDiscovered(tile, radius) && int ourValue = howManyTilesWillBeDiscovered(tile, radius);
isSafeToVisit(h, tile) && !isBlockedBorderGate(tile)) if (ourValue > bestValue) //avoid costly checks of tiles that don't reveal much
{ {
return tile; //return first tile that will discover anything if(cb->getPathInfo(tile)->reachable() && (isSafeToVisit(h, tile) || breakUnsafe) && !isBlockedBorderGate(tile))
{
bestTile = tile; //return first tile that will discover anything
bestValue = ourValue;
} }
} }
} }
return int3 (-1,-1,-1); }
//throw cannotFulfillGoalException("No accessible tile will bring discoveries!"); return bestTile;
} }
TResources VCAI::estimateIncome() const TResources VCAI::estimateIncome() const
@ -2125,8 +2198,17 @@ void VCAI::recruitHero(const CGTownInstance * t, bool throwing)
{ {
logAi->debugStream() << boost::format("Trying to recruit a hero in %s at %s") % t->name % t->visitablePos(); logAi->debugStream() << boost::format("Trying to recruit a hero in %s at %s") % t->name % t->visitablePos();
if(auto availableHero = frontOrNull(cb->getAvailableHeroes(t))) auto heroes = cb->getAvailableHeroes(t);
cb->recruitHero(t, availableHero); if(heroes.size())
{
auto hero = heroes[0];
if (heroes.size() >= 2) //makes sense to recruit two heroes with starting amries in first week
{
if (heroes[1]->getTotalStrength() > hero->getTotalStrength())
hero = heroes[1];
}
cb->recruitHero(t, hero);
}
else if(throwing) else if(throwing)
throw cannotFulfillGoalException("No available heroes in tavern in " + t->nodeName()); throw cannotFulfillGoalException("No available heroes in tavern in " + t->nodeName());
} }
@ -2496,6 +2578,9 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj)
{ {
switch (obj->ID) switch (obj->ID)
{ {
case Obj::TOWN:
return obj->tempOwner != h->tempOwner; //do not visit our towns at random
break;
case Obj::BORDER_GATE: case Obj::BORDER_GATE:
{ {
for (auto q : ai->myCb->getMyQuests()) for (auto q : ai->myCb->getMyQuests())

View File

@ -8,6 +8,7 @@
#include "../../lib/CThreadHelper.h" #include "../../lib/CThreadHelper.h"
#include "../../lib/GameConstants.h"
#include "../../lib/VCMI_Lib.h" #include "../../lib/VCMI_Lib.h"
#include "../../lib/CBuildingHandler.h" #include "../../lib/CBuildingHandler.h"
#include "../../lib/CCreatureHandler.h" #include "../../lib/CCreatureHandler.h"
@ -109,19 +110,34 @@ struct SectorMap
int3 firstTileToGet(HeroPtr h, crint3 dst); //if h wants to reach tile dst, which tile he should visit to clear the way? int3 firstTileToGet(HeroPtr h, crint3 dst); //if h wants to reach tile dst, which tile he should visit to clear the way?
}; };
//Set of buildings for different goals. Does not include any prerequisites.
const BuildingID essential[] = {BuildingID::TAVERN, BuildingID::TOWN_HALL};
const BuildingID goldSource[] = {BuildingID::TOWN_HALL, BuildingID::CITY_HALL, BuildingID::CAPITOL};
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};
const BuildingID unitsUpgrade[] = { BuildingID::DWELL_LVL_1_UP, BuildingID::DWELL_LVL_2_UP, BuildingID::DWELL_LVL_3_UP,
BuildingID::DWELL_LVL_4_UP, BuildingID::DWELL_LVL_5_UP, BuildingID::DWELL_LVL_6_UP, BuildingID::DWELL_LVL_7_UP};
const BuildingID unitGrowth[] = { BuildingID::FORT, BuildingID::CITADEL, BuildingID::CASTLE, BuildingID::HORDE_1,
BuildingID::HORDE_1_UPGR, BuildingID::HORDE_2, BuildingID::HORDE_2_UPGR};
const BuildingID spells[] = {BuildingID::MAGES_GUILD_1, BuildingID::MAGES_GUILD_2, BuildingID::MAGES_GUILD_3,
BuildingID::MAGES_GUILD_4, BuildingID::MAGES_GUILD_5};
const BuildingID extra[] = {BuildingID::RESOURCE_SILO, BuildingID::SPECIAL_1, BuildingID::SPECIAL_2, BuildingID::SPECIAL_3,
BuildingID::SPECIAL_4, BuildingID::SHIPYARD}; // all remaining buildings
class VCAI : public CAdventureAI class VCAI : public CAdventureAI
{ {
public:
//internal methods for town development //internal methods for town development
//try build an unbuilt structure in maxDays at most (0 = indefinite) //try build an unbuilt structure in maxDays at most (0 = indefinite)
/*bool canBuildStructure(const CGTownInstance * t, BuildingID building, unsigned int maxDays=7);*/
bool tryBuildStructure(const CGTownInstance * t, BuildingID building, unsigned int maxDays=7); bool tryBuildStructure(const CGTownInstance * t, BuildingID building, unsigned int maxDays=7);
//try build ANY unbuilt structure //try build ANY unbuilt structure
BuildingID canBuildAnyStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays=7);
bool tryBuildAnyStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays=7); bool tryBuildAnyStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays=7);
//try build first unbuilt structure //try build first unbuilt structure
bool tryBuildNextStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays=7); bool tryBuildNextStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays=7);
public:
friend class FuzzyHelper; friend class FuzzyHelper;
std::map<const CGObjectInstance *, const CGObjectInstance *> knownSubterraneanGates; std::map<const CGObjectInstance *, const CGObjectInstance *> knownSubterraneanGates;
@ -160,7 +176,7 @@ public:
void tryRealize(Goals::AbstractGoal & g); void tryRealize(Goals::AbstractGoal & g);
int3 explorationBestNeighbour(int3 hpos, int radius, HeroPtr h); int3 explorationBestNeighbour(int3 hpos, int radius, HeroPtr h);
int3 explorationNewPoint(int radius, HeroPtr h, std::vector<std::vector<int3> > &tiles); int3 explorationNewPoint(int radius, HeroPtr h, bool breakUnsafe = false);
void recruitHero(); void recruitHero();
virtual std::string getBattleAIName() const override; virtual std::string getBattleAIName() const override;
@ -259,7 +275,7 @@ public:
void addVisitableObj(const CGObjectInstance *obj); void addVisitableObj(const CGObjectInstance *obj);
void markObjectVisited (const CGObjectInstance *obj); void markObjectVisited (const CGObjectInstance *obj);
void reserveObject (HeroPtr h, const CGObjectInstance *obj); void reserveObject (HeroPtr h, const CGObjectInstance *obj); //TODO: reserve all objects that heroes attempt to visit
//void removeVisitableObj(const CGObjectInstance *obj); //void removeVisitableObj(const CGObjectInstance *obj);
void validateObject(const CGObjectInstance *obj); //checks if object is still visible and if not, removes references to it void validateObject(const CGObjectInstance *obj); //checks if object is still visible and if not, removes references to it
void validateObject(ObjectIdRef obj); //checks if object is still visible and if not, removes references to it void validateObject(ObjectIdRef obj); //checks if object is still visible and if not, removes references to it