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

AI: tweak explore to work with new pathfinding.

This commit is contained in:
Andrii Danylchenko 2018-12-24 15:19:04 +02:00
parent a1ff355927
commit edc5abe49d
8 changed files with 285 additions and 277 deletions

View File

@ -403,46 +403,6 @@ bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater)
return false;
}
int3 whereToExplore(HeroPtr h)
{
TimeCheck tc("where to explore");
int radius = h->getSightRadius();
int3 hpos = h->visitablePos();
//look for nearby objs -> visit them if they're close enouh
const int DIST_LIMIT = 3;
const int MP_LIMIT = DIST_LIMIT * 150; // aproximate cost of diagonal movement
std::vector<const CGObjectInstance *> nearbyVisitableObjs;
for(int x = hpos.x - DIST_LIMIT; x <= hpos.x + DIST_LIMIT; ++x) //get only local objects instead of all possible objects on the map
{
for(int y = hpos.y - DIST_LIMIT; y <= hpos.y + DIST_LIMIT; ++y)
{
for(auto obj : cb->getVisitableObjs(int3(x, y, hpos.z), false))
{
if(ai->isGoodForVisit(obj, h, MP_LIMIT))
{
nearbyVisitableObjs.push_back(obj);
}
}
}
}
vstd::removeDuplicates(nearbyVisitableObjs); //one object may occupy multiple tiles
boost::sort(nearbyVisitableObjs, CDistanceSorter(h.get()));
if(nearbyVisitableObjs.size())
return nearbyVisitableObjs.back()->visitablePos();
try //check if nearby tiles allow us to reveal anything - this is quick
{
return ai->explorationBestNeighbour(hpos, radius, h);
}
catch(cannotFulfillGoalException & e)
{
//perform exhaustive search
return ai->explorationNewPoint(h);
}
}
bool isBlockedBorderGate(int3 tileToHit) //TODO: is that function needed? should be handled by pathfinder
{
if(cb->getTile(tileToHit)->topVisitableId() != Obj::BORDER_GATE)
@ -461,51 +421,6 @@ bool isBlockVisitObj(const int3 & pos)
return false;
}
bool hasReachableNeighbor(const int3 &pos, HeroPtr hero, CCallback * cbp)
{
for(crint3 dir : int3::getDirs())
{
int3 tile = pos + dir;
if(cbp->isInTheMap(tile) && cbp->getPathsInfo(hero.get())->getPathInfo(tile)->reachable())
{
return true;
}
}
return false;
}
int howManyTilesWillBeDiscovered(const int3 & pos, int radious, CCallback * cbp, HeroPtr hero)
{
int ret = 0;
for(int x = pos.x - radious; x <= pos.x + radious; x++)
{
for(int y = pos.y - radious; y <= pos.y + radious; y++)
{
int3 npos = int3(x, y, pos.z);
if(cbp->isInTheMap(npos) && pos.dist2d(npos) - 0.5 < radious && !cbp->isVisible(npos))
{
if(hasReachableNeighbor(npos, hero, cbp))
ret++;
}
}
}
return ret;
}
void getVisibleNeighbours(const std::vector<int3> & tiles, std::vector<int3> & out)
{
for(const int3 & tile : tiles)
{
foreach_neighbour(tile, [&](int3 neighbour)
{
if(cb->isVisible(neighbour))
out.push_back(neighbour);
});
}
}
creInfo infoFromDC(const dwellingContent & dc)
{
creInfo ci;

View File

@ -159,9 +159,6 @@ void foreach_tile_pos(CCallback * cbp, std::function<void(CCallback * cbp, const
void foreach_neighbour(const int3 & pos, std::function<void(const int3 & pos)> foo);
void foreach_neighbour(CCallback * cbp, const int3 & pos, std::function<void(CCallback * cbp, const int3 & pos)> foo); // avoid costly retrieval of thread-specific pointer
int howManyTilesWillBeDiscovered(const int3 & pos, int radious, CCallback * cbp, HeroPtr hero);
void getVisibleNeighbours(const std::vector<int3> & tiles, std::vector<int3> & out);
bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater);
bool isBlockedBorderGate(int3 tileToHit);
bool isBlockVisitObj(const int3 & pos);
@ -181,7 +178,6 @@ bool compareArmyStrength(const CArmedInstance * a1, const CArmedInstance * a2);
bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2);
ui64 howManyReinforcementsCanBuy(const CArmedInstance * h, const CGDwelling * t);
ui64 howManyReinforcementsCanGet(const CArmedInstance * h, const CGTownInstance * t);
int3 whereToExplore(HeroPtr h);
uint32_t distanceToTile(const CGHeroInstance * hero, int3 pos);
class CDistanceSorter

View File

@ -318,8 +318,15 @@ void HeroMovementGoalEngineBase::setSharedFuzzyVariables(Goals::AbstractGoal & g
{
float turns = calculateTurnDistanceInputValue(goal);
float missionImportanceData = 0;
if(vstd::contains(ai->lockedHeroes, goal.hero))
{
missionImportanceData = ai->lockedHeroes[goal.hero]->priority;
}
else if(goal.parent)
{
missionImportanceData = goal.parent->priority;
}
float strengthRatioData = 10.0f; //we are much stronger than enemy
ui64 danger = evaluateDanger(goal.tile, goal.hero.h);
@ -410,6 +417,9 @@ VisitTileEngine::VisitTileEngine() //so far no VisitTile-specific variables that
float VisitTileEngine::evaluate(Goals::VisitTile & goal)
{
// for now any visit tile is usually much more in priority then visit obj so lets reduce it
const int scale = 2;
//we assume that hero is already set and we want to choose most suitable one for the mission
if(!goal.hero)
return 0;
@ -421,7 +431,8 @@ float VisitTileEngine::evaluate(Goals::VisitTile & goal)
try
{
engine.process();
goal.priority = value->getValue();
goal.priority = value->getValue() / scale;
}
catch(fl::Exception & fe)
{

View File

@ -78,6 +78,11 @@ ui64 FuzzyHelper::estimateBankDanger(const CBank * bank)
float FuzzyHelper::evaluate(Goals::VisitTile & g)
{
if(g.parent)
{
g.parent->accept(this);
}
return visitTileEngine.evaluate(g);
}
@ -108,6 +113,11 @@ float FuzzyHelper::evaluate(Goals::CompleteQuest & g)
float FuzzyHelper::evaluate(Goals::VisitObj & g)
{
if(g.parent)
{
g.parent->accept(this);
}
return visitObjEngine.evaluate(g);
}
float FuzzyHelper::evaluate(Goals::VisitHero & g)

View File

@ -19,7 +19,6 @@
#include "../../../lib/CPathfinder.h"
#include "../../../lib/StringConstants.h"
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<VCAI> ai;
extern FuzzyHelper * fh;
@ -132,29 +131,18 @@ TGoalVec Explore::getAllPossibleSubgoals()
vstd::concatenate(ret, waysToVisitObj);
}
int3 t = whereToExplore(h);
if(t.valid())
TSubgoal goal = howToExplore(h);
if(goal->invalid())
{
ret.push_back(sptr(VisitTile(t).sethero(h)));
ai->markHeroUnableToExplore(h); //there is no freely accessible tile, do not poll this hero anymore
}
else
{
//FIXME: possible issues when gathering army to break
if(hero.h == h || //exporation is assigned to this hero
(!hero && h == primaryHero)) //not assigned to specific hero, let our main hero do the job
{
t = ai->explorationDesperate(h); //check this only ONCE, high cost
if (t.valid()) //don't waste time if we are completely blocked
{
auto waysToVisitTile = ai->ah->howToVisitTile(h, t);
vstd::concatenate(ret, waysToVisitTile);
continue;
}
}
ai->markHeroUnableToExplore(h); //there is no freely accessible tile, do not poll this hero anymore
ret.push_back(goal);
}
}
//we either don't have hero yet or none of heroes can explore
if((!hero || ret.empty()) && ai->canRecruitAnyHero())
ret.push_back(sptr(RecruitHero()));
@ -179,3 +167,220 @@ bool Explore::fulfillsMe(TSubgoal goal)
}
return false;
}
bool Explore::hasReachableNeighbor(const int3 &pos, HeroPtr hero, CCallback * cbp, VCAI * vcai) const
{
for(crint3 dir : int3::getDirs())
{
int3 tile = pos + dir;
if(cbp->isInTheMap(tile))
{
auto paths = vcai->ah->getPathsToTile(hero, tile);
return paths.size();
}
}
return false;
}
int Explore::howManyTilesWillBeDiscovered(
const int3 & pos,
int radious,
CCallback * cbp,
HeroPtr hero,
std::function<bool (const int3 &)> filter) const
{
int ret = 0;
for(int x = pos.x - radious; x <= pos.x + radious; x++)
{
for(int y = pos.y - radious; y <= pos.y + radious; y++)
{
int3 npos = int3(x, y, pos.z);
if(cbp->isInTheMap(npos)
&& pos.dist2d(npos) - 0.5 < radious
&& !cbp->isVisible(npos)
&& filter(npos))
{
ret++;
}
}
}
return ret;
}
TSubgoal Explore::explorationBestNeighbour(int3 hpos, int radius, HeroPtr h) const
{
std::map<int3, int> dstToRevealedTiles;
VCAI * aip = ai.get();
CCallback * cbp = cb.get();
for(crint3 dir : int3::getDirs())
{
int3 tile = hpos + dir;
if(cb->isInTheMap(tile))
{
if(isBlockVisitObj(tile))
continue;
if(isSafeToVisit(h, tile) && ai->isAccessibleForHero(tile, h))
{
auto distance = hpos.dist2d(tile); // diagonal movement opens more tiles but spends more mp
int tilesDiscovered = howManyTilesWillBeDiscovered(tile, radius, cbp, h, [&](int3 neighbor) -> bool {
return hasReachableNeighbor(neighbor, h, cbp, aip);
});
dstToRevealedTiles[tile] = tilesDiscovered / distance;
}
}
}
if(dstToRevealedTiles.empty()) //yes, it DID happen!
return sptr(Invalid());
auto best = dstToRevealedTiles.begin();
for(auto i = dstToRevealedTiles.begin(); i != dstToRevealedTiles.end(); i++)
{
const CGPathNode * pn = cb->getPathsInfo(h.get())->getPathInfo(i->first);
//const TerrainTile *t = cb->getTile(i->first);
if(best->second < i->second && pn->reachable() && pn->accessible == CGPathNode::ACCESSIBLE)
best = i;
}
if(best->second)
return sptr(VisitTile(best->first).sethero(h));
return sptr(Invalid());
}
TSubgoal Explore::explorationNewPoint(HeroPtr h) const
{
int radius = h->getSightRadius();
CCallback * cbp = cb.get();
VCAI *aip = ai.get();
std::vector<std::vector<int3>> tiles; //tiles[distance_to_fow]
tiles.resize(radius);
foreach_tile_pos([&](const int3 & pos)
{
if(!cbp->isVisible(pos))
tiles[0].push_back(pos);
});
float bestValue = 0; //discovered tile to node distance ratio
TSubgoal bestWay = sptr(Invalid());
int3 ourPos = h->convertPosition(h->pos, false);
for(int i = 1; i < radius; i++)
{
getVisibleNeighbours(tiles[i - 1], tiles[i], cbp);
vstd::removeDuplicates(tiles[i]);
for(const int3 & tile : tiles[i])
{
if(tile == ourPos) //shouldn't happen, but it does
continue;
auto waysToVisit = aip->ah->howToVisitTile(h, tile);
for(auto goal : waysToVisit)
{
if(goal->evaluationContext.movementCost == 0) // should not happen
continue;
int tilesDiscovered = howManyTilesWillBeDiscovered(tile, radius, cbp, h, [](int3 neighbor) -> bool { return true; });
float ourValue = (float) tilesDiscovered / goal->evaluationContext.movementCost; //+1 prevents erratic jumps
if(ourValue > bestValue) //avoid costly checks of tiles that don't reveal much
{
auto obj = cb->getTopObj(tile);
if(obj && obj->blockVisit && !isObjectRemovable(obj)) //we can't stand on that object
{
continue;
}
if(isSafeToVisit(h, tile))
{
bestWay = goal;
bestValue = ourValue;
}
}
}
}
if(!bestWay->invalid())
{
return bestWay;
}
}
return bestWay;
}
TSubgoal Explore::howToExplore(HeroPtr h) const
{
TimeCheck tc("where to explore");
int radius = h->getSightRadius();
int3 hpos = h->visitablePos();
//look for nearby objs -> visit them if they're close enouh
const int DIST_LIMIT = 3;
const int MP_LIMIT = DIST_LIMIT * 150; // aproximate cost of diagonal movement
std::vector<const CGObjectInstance *> nearbyVisitableObjs;
for(int x = hpos.x - DIST_LIMIT; x <= hpos.x + DIST_LIMIT; ++x) //get only local objects instead of all possible objects on the map
{
for(int y = hpos.y - DIST_LIMIT; y <= hpos.y + DIST_LIMIT; ++y)
{
for(auto obj : cb->getVisitableObjs(int3(x, y, hpos.z), false))
{
if(ai->isGoodForVisit(obj, h, MP_LIMIT))
{
nearbyVisitableObjs.push_back(obj);
}
}
}
}
vstd::removeDuplicates(nearbyVisitableObjs); //one object may occupy multiple tiles
boost::sort(nearbyVisitableObjs, CDistanceSorter(h.get()));
if(nearbyVisitableObjs.size())
{
TSubgoal pickupNearestObj = fh->chooseSolution(ai->ah->howToVisitObj(h, nearbyVisitableObjs.back(), false));
if(!pickupNearestObj->invalid())
{
return pickupNearestObj;
}
}
//check if nearby tiles allow us to reveal anything - this is quick
TSubgoal result = explorationBestNeighbour(hpos, radius, h);
if(result->invalid())
{
//perform exhaustive search
result = explorationNewPoint(h);
}
return result;
}
void Explore::getVisibleNeighbours(const std::vector<int3> & tiles, std::vector<int3> & out, CCallback * cbp) const
{
for(const int3 & tile : tiles)
{
foreach_neighbour(tile, [&](int3 neighbour)
{
if(cbp->isVisible(neighbour))
{
auto tile = cbp->getTile(neighbour);
if(tile && !tile->blocked)
out.push_back(neighbour);
}
});
}
}

View File

@ -36,5 +36,18 @@ namespace Goals
std::string completeMessage() const override;
bool fulfillsMe(TSubgoal goal) override;
virtual bool operator==(const Explore & other) const override;
private:
TSubgoal howToExplore(HeroPtr h) const;
TSubgoal explorationNewPoint(HeroPtr h) const;
TSubgoal explorationBestNeighbour(int3 hpos, int radius, HeroPtr h) const;
bool hasReachableNeighbor(const int3 &pos, HeroPtr hero, CCallback * cbp, VCAI * vcai) const;
void getVisibleNeighbours(const std::vector<int3> & tiles, std::vector<int3> & out, CCallback * cbp) const;
int howManyTilesWillBeDiscovered(
const int3 & pos,
int radious,
CCallback * cbp,
HeroPtr hero,
std::function<bool(const int3 &)> filter) const;
};
}

View File

@ -1531,33 +1531,42 @@ void VCAI::wander(HeroPtr h)
vstd::concatenate(targetObjectGoals, ah->howToVisitObj(h, destination, false));
}
auto bestObjectGoal = fh->chooseSolution(targetObjectGoals);
//wander should not cause heroes to be reserved - they are always considered free
if(bestObjectGoal->goalType == Goals::VISIT_OBJ)
if(targetObjectGoals.size())
{
auto chosenObject = cb->getObjInstance(ObjectInstanceID(bestObjectGoal->objid));
if(chosenObject != nullptr)
logAi->debug("Of all %d destinations, object %s at pos=%s seems nice", dests.size(), chosenObject->getObjectName(), chosenObject->pos.toString());
auto bestObjectGoal = fh->chooseSolution(targetObjectGoals);
//wander should not cause heroes to be reserved - they are always considered free
if(bestObjectGoal->goalType == Goals::VISIT_OBJ)
{
auto chosenObject = cb->getObjInstance(ObjectInstanceID(bestObjectGoal->objid));
if(chosenObject != nullptr)
logAi->debug("Of all %d destinations, object %s at pos=%s seems nice", dests.size(), chosenObject->getObjectName(), chosenObject->pos.toString());
}
else
logAi->debug("Trying to realize goal of type %s as part of wandering.", bestObjectGoal->name());
try
{
decomposeGoal(bestObjectGoal)->accept(this);
}
catch(const goalFulfilledException & e)
{
if(e.goal->goalType == Goals::EGoals::VISIT_TILE || e.goal->goalType == Goals::EGoals::VISIT_OBJ)
continue;
throw e;
}
}
else
logAi->debug("Trying to realize goal of type %s as part of wandering.", bestObjectGoal->name());
try
{
decomposeGoal(bestObjectGoal)->accept(this);
}
catch(const goalFulfilledException & e)
{
if(e.goal->goalType == Goals::EGoals::VISIT_TILE || e.goal->goalType == Goals::EGoals::VISIT_OBJ)
continue;
throw e;
logAi->debug("Nowhere more to go...");
break;
}
visitTownIfAny(h);
}
}
visitTownIfAny(h); //in case hero is just sitting in town
}
@ -2472,154 +2481,6 @@ void VCAI::buildArmyIn(const CGTownInstance * t)
moveCreaturesToHero(t);
}
int3 VCAI::explorationBestNeighbour(int3 hpos, int radius, HeroPtr h)
{
std::map<int3, int> dstToRevealedTiles;
for(crint3 dir : int3::getDirs())
{
int3 tile = hpos + dir;
if(cb->isInTheMap(tile))
{
if(isBlockVisitObj(tile))
continue;
if(isSafeToVisit(h, tile) && isAccessibleForHero(tile, h))
{
auto distance = hpos.dist2d(tile); // diagonal movement opens more tiles but spends more mp
dstToRevealedTiles[tile] = howManyTilesWillBeDiscovered(tile, radius, cb.get(), h) / distance;
}
}
}
if(dstToRevealedTiles.empty()) //yes, it DID happen!
throw cannotFulfillGoalException("No neighbour will bring new discoveries!");
auto best = dstToRevealedTiles.begin();
for(auto i = dstToRevealedTiles.begin(); i != dstToRevealedTiles.end(); i++)
{
const CGPathNode * pn = cb->getPathsInfo(h.get())->getPathInfo(i->first);
//const TerrainTile *t = cb->getTile(i->first);
if(best->second < i->second && pn->reachable() && pn->accessible == CGPathNode::ACCESSIBLE)
best = i;
}
if(best->second)
return best->first;
throw cannotFulfillGoalException("No neighbour will bring new discoveries!");
}
int3 VCAI::explorationNewPoint(HeroPtr h)
{
int radius = h->getSightRadius();
CCallback * cbp = cb.get();
const CGHeroInstance * hero = h.get();
std::vector<std::vector<int3>> tiles; //tiles[distance_to_fow]
tiles.resize(radius);
foreach_tile_pos([&](const int3 & pos)
{
if(!cbp->isVisible(pos))
tiles[0].push_back(pos);
});
float bestValue = 0; //discovered tile to node distance ratio
int3 bestTile(-1, -1, -1);
int3 ourPos = h->convertPosition(h->pos, false);
for(int i = 1; i < radius; i++)
{
getVisibleNeighbours(tiles[i - 1], tiles[i]);
vstd::removeDuplicates(tiles[i]);
for(const int3 & tile : tiles[i])
{
if(tile == ourPos) //shouldn't happen, but it does
continue;
if(!cb->getPathsInfo(hero)->getPathInfo(tile)->reachable()) //this will remove tiles that are guarded by monsters (or removable objects)
continue;
CGPath path;
cb->getPathsInfo(hero)->getPath(path, tile);
float ourValue = (float)howManyTilesWillBeDiscovered(tile, radius, cbp, h) / (path.nodes.size() + 1); //+1 prevents erratic jumps
if(ourValue > bestValue) //avoid costly checks of tiles that don't reveal much
{
auto obj = cb->getTopObj(tile);
if (obj)
if (obj->blockVisit && !isObjectRemovable(obj)) //we can't stand on that object
continue;
if(isSafeToVisit(h, tile))
{
bestTile = tile;
bestValue = ourValue;
}
}
}
}
return bestTile;
}
int3 VCAI::explorationDesperate(HeroPtr h)
{
int radius = h->getSightRadius();
std::vector<std::vector<int3>> tiles; //tiles[distance_to_fow]
tiles.resize(radius);
CCallback * cbp = cb.get();
foreach_tile_pos([&](const int3 & pos)
{
if(!cbp->isVisible(pos))
tiles[0].push_back(pos);
});
ui64 lowestDanger = -1;
int3 bestTile(-1, -1, -1);
for(int i = 1; i < radius; i++)
{
getVisibleNeighbours(tiles[i - 1], tiles[i]);
vstd::removeDuplicates(tiles[i]);
for(const int3 & tile : tiles[i])
{
if(cbp->getTile(tile)->blocked) //does it shorten the time?
continue;
if(!howManyTilesWillBeDiscovered(tile, radius, cbp, h)) //avoid costly checks of tiles that don't reveal much
continue;
auto paths = ah->getPathsToTile(h, tile);
for(auto path : paths)
{
auto t = path.firstTileToGet();
if(t == bestTile)
continue;
auto obj = cb->getTopObj(t);
if (obj)
if (obj->blockVisit && !isObjectRemovable(obj)) //we can't stand on object or remove it
continue;
ui64 ourDanger = path.getTotalDanger(h);
if(ourDanger < lowestDanger)
{
if(!ourDanger) //at least one safe place found
return t;
bestTile = t;
lowestDanger = ourDanger;
}
}
}
}
return bestTile;
}
void VCAI::checkHeroArmy(HeroPtr h)
{
auto it = lockedHeroes.find(h);

View File

@ -131,9 +131,6 @@ public:
void tryRealize(Goals::Invalid & g);
void tryRealize(Goals::AbstractGoal & g);
int3 explorationBestNeighbour(int3 hpos, int radius, HeroPtr h);
int3 explorationNewPoint(HeroPtr h);
int3 explorationDesperate(HeroPtr h);
bool isTileNotReserved(const CGHeroInstance * h, int3 t) const; //the tile is not occupied by allied hero and the object is not reserved
std::string getBattleAIName() const override;