mirror of
https://github.com/vcmi/vcmi.git
synced 2025-01-26 03:52:01 +02:00
NKAI: speedup exploration a bit
This commit is contained in:
parent
820f0e0c1a
commit
02ea497951
@ -15,233 +15,15 @@
|
|||||||
#include "../Goals/Composition.h"
|
#include "../Goals/Composition.h"
|
||||||
#include "../Goals/ExecuteHeroChain.h"
|
#include "../Goals/ExecuteHeroChain.h"
|
||||||
#include "../Markers/ExplorationPoint.h"
|
#include "../Markers/ExplorationPoint.h"
|
||||||
#include "CaptureObjectsBehavior.h"
|
|
||||||
#include "../Goals/CaptureObject.h"
|
#include "../Goals/CaptureObject.h"
|
||||||
#include "../../../lib/CPlayerState.h"
|
#include "../Goals/ExploreNeighbourTile.h"
|
||||||
|
#include "../Helpers/ExplorationHelper.h"
|
||||||
|
|
||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
struct ExplorationHelper
|
|
||||||
{
|
|
||||||
const CGHeroInstance * hero;
|
|
||||||
int sightRadius;
|
|
||||||
float bestValue;
|
|
||||||
TSubgoal bestGoal;
|
|
||||||
int3 bestTile;
|
|
||||||
int bestTilesDiscovered;
|
|
||||||
const Nullkiller * ai;
|
|
||||||
CCallback * cbp;
|
|
||||||
const TeamState * ts;
|
|
||||||
int3 ourPos;
|
|
||||||
bool allowDeadEndCancellation;
|
|
||||||
|
|
||||||
ExplorationHelper(const CGHeroInstance * hero, const Nullkiller * ai)
|
|
||||||
:ai(ai), cbp(ai->cb.get()), hero(hero)
|
|
||||||
{
|
|
||||||
ts = cbp->getPlayerTeam(ai->playerID);
|
|
||||||
sightRadius = hero->getSightRadius();
|
|
||||||
bestGoal = sptr(Goals::Invalid());
|
|
||||||
bestValue = 0;
|
|
||||||
bestTilesDiscovered = 0;
|
|
||||||
ourPos = hero->visitablePos();
|
|
||||||
allowDeadEndCancellation = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
TSubgoal makeComposition() const
|
|
||||||
{
|
|
||||||
Composition c;
|
|
||||||
c.addNext(ExplorationPoint(bestTile, bestTilesDiscovered));
|
|
||||||
c.addNext(bestGoal);
|
|
||||||
return sptr(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
void scanSector(int scanRadius)
|
|
||||||
{
|
|
||||||
int3 tile = int3(0, 0, ourPos.z);
|
|
||||||
|
|
||||||
const auto & slice = (*(ts->fogOfWarMap))[ourPos.z];
|
|
||||||
|
|
||||||
for(tile.x = ourPos.x - scanRadius; tile.x <= ourPos.x + scanRadius; tile.x++)
|
|
||||||
{
|
|
||||||
for(tile.y = ourPos.y - scanRadius; tile.y <= ourPos.y + scanRadius; tile.y++)
|
|
||||||
{
|
|
||||||
|
|
||||||
if(cbp->isInTheMap(tile) && slice[tile.x][tile.y])
|
|
||||||
{
|
|
||||||
scanTile(tile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void scanMap()
|
|
||||||
{
|
|
||||||
int3 mapSize = cbp->getMapSize();
|
|
||||||
int perimeter = 2 * sightRadius * (mapSize.x + mapSize.y);
|
|
||||||
|
|
||||||
std::vector<int3> from;
|
|
||||||
std::vector<int3> to;
|
|
||||||
|
|
||||||
from.reserve(perimeter);
|
|
||||||
to.reserve(perimeter);
|
|
||||||
|
|
||||||
foreach_tile_pos([&](const int3 & pos)
|
|
||||||
{
|
|
||||||
if((*(ts->fogOfWarMap))[pos.z][pos.x][pos.y])
|
|
||||||
{
|
|
||||||
bool hasInvisibleNeighbor = false;
|
|
||||||
|
|
||||||
foreach_neighbour(cbp, pos, [&](CCallback * cbp, int3 neighbour)
|
|
||||||
{
|
|
||||||
if(!(*(ts->fogOfWarMap))[neighbour.z][neighbour.x][neighbour.y])
|
|
||||||
{
|
|
||||||
hasInvisibleNeighbor = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if(hasInvisibleNeighbor)
|
|
||||||
from.push_back(pos);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
logAi->debug("Exploration scan visible area perimeter for hero %s", hero->getNameTranslated());
|
|
||||||
|
|
||||||
for(const int3 & tile : from)
|
|
||||||
{
|
|
||||||
scanTile(tile);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!bestGoal->invalid())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
allowDeadEndCancellation = false;
|
|
||||||
|
|
||||||
for(int i = 0; i < sightRadius; i++)
|
|
||||||
{
|
|
||||||
getVisibleNeighbours(from, to);
|
|
||||||
vstd::concatenate(from, to);
|
|
||||||
vstd::removeDuplicates(from);
|
|
||||||
}
|
|
||||||
|
|
||||||
logAi->debug("Exploration scan all possible tiles for hero %s", hero->getNameTranslated());
|
|
||||||
|
|
||||||
for(const int3 & tile : from)
|
|
||||||
{
|
|
||||||
scanTile(tile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void scanTile(const int3 & tile)
|
|
||||||
{
|
|
||||||
if(tile == ourPos
|
|
||||||
|| !ai->pathfinder->isTileAccessible(hero, tile)) //shouldn't happen, but it does
|
|
||||||
return;
|
|
||||||
|
|
||||||
int tilesDiscovered = howManyTilesWillBeDiscovered(tile);
|
|
||||||
if(!tilesDiscovered)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto paths = ai->pathfinder->getPathInfo(tile);
|
|
||||||
auto waysToVisit = CaptureObjectsBehavior::getVisitGoals(paths, ai, ai->cb->getTopObj(tile));
|
|
||||||
|
|
||||||
for(int i = 0; i != paths.size(); i++)
|
|
||||||
{
|
|
||||||
auto & path = paths[i];
|
|
||||||
auto goal = waysToVisit[i];
|
|
||||||
|
|
||||||
if(path.exchangeCount > 1 || path.targetHero != hero || path.movementCost() <= 0.0 || goal->invalid())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
float ourValue = (float)tilesDiscovered * tilesDiscovered / path.movementCost();
|
|
||||||
|
|
||||||
if(ourValue > bestValue) //avoid costly checks of tiles that don't reveal much
|
|
||||||
{
|
|
||||||
auto obj = cb->getTopObj(tile);
|
|
||||||
|
|
||||||
// picking up resources does not yield any exploration at all.
|
|
||||||
// if it blocks the way to some explorable tile AIPathfinder will take care of it
|
|
||||||
if(obj && obj->isBlockedVisitable())
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(isSafeToVisit(hero, path.heroArmy, path.getTotalDanger()))
|
|
||||||
{
|
|
||||||
bestGoal = goal;
|
|
||||||
bestValue = ourValue;
|
|
||||||
bestTile = tile;
|
|
||||||
bestTilesDiscovered = tilesDiscovered;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void getVisibleNeighbours(const std::vector<int3> & tiles, std::vector<int3> & out) const
|
|
||||||
{
|
|
||||||
for(const int3 & tile : tiles)
|
|
||||||
{
|
|
||||||
foreach_neighbour(cbp, tile, [&](CCallback * cbp, int3 neighbour)
|
|
||||||
{
|
|
||||||
if((*(ts->fogOfWarMap))[neighbour.z][neighbour.x][neighbour.y])
|
|
||||||
{
|
|
||||||
out.push_back(neighbour);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int howManyTilesWillBeDiscovered(const int3 & pos) const
|
|
||||||
{
|
|
||||||
int ret = 0;
|
|
||||||
int3 npos = int3(0, 0, pos.z);
|
|
||||||
|
|
||||||
const auto & slice = (*(ts->fogOfWarMap))[pos.z];
|
|
||||||
|
|
||||||
for(npos.x = pos.x - sightRadius; npos.x <= pos.x + sightRadius; npos.x++)
|
|
||||||
{
|
|
||||||
for(npos.y = pos.y - sightRadius; npos.y <= pos.y + sightRadius; npos.y++)
|
|
||||||
{
|
|
||||||
if(cbp->isInTheMap(npos)
|
|
||||||
&& pos.dist2d(npos) - 0.5 < sightRadius
|
|
||||||
&& !slice[npos.x][npos.y])
|
|
||||||
{
|
|
||||||
if(allowDeadEndCancellation
|
|
||||||
&& !hasReachableNeighbor(npos))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hasReachableNeighbor(const int3 & pos) const
|
|
||||||
{
|
|
||||||
for(const int3 & dir : int3::getDirs())
|
|
||||||
{
|
|
||||||
int3 tile = pos + dir;
|
|
||||||
if(cbp->isInTheMap(tile))
|
|
||||||
{
|
|
||||||
auto isAccessible = ai->pathfinder->isTileAccessible(hero, tile);
|
|
||||||
|
|
||||||
if(isAccessible)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string ExplorationBehavior::toString() const
|
std::string ExplorationBehavior::toString() const
|
||||||
{
|
{
|
||||||
return "Explore";
|
return "Explore";
|
||||||
@ -301,17 +83,13 @@ Goals::TGoalVec ExplorationBehavior::decompose(const Nullkiller * ai) const
|
|||||||
{
|
{
|
||||||
ExplorationHelper scanResult(hero, ai);
|
ExplorationHelper scanResult(hero, ai);
|
||||||
|
|
||||||
scanResult.scanSector(1);
|
if(scanResult.scanSector(1))
|
||||||
|
|
||||||
if(!scanResult.bestGoal->invalid())
|
|
||||||
{
|
{
|
||||||
tasks.push_back(scanResult.makeComposition());
|
tasks.push_back(scanResult.makeComposition());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
scanResult.scanSector(15);
|
if(scanResult.scanSector(15))
|
||||||
|
|
||||||
if(!scanResult.bestGoal->invalid())
|
|
||||||
{
|
{
|
||||||
tasks.push_back(scanResult.makeComposition());
|
tasks.push_back(scanResult.makeComposition());
|
||||||
continue;
|
continue;
|
||||||
@ -319,9 +97,7 @@ Goals::TGoalVec ExplorationBehavior::decompose(const Nullkiller * ai) const
|
|||||||
|
|
||||||
if(ai->getScanDepth() == ScanDepth::ALL_FULL)
|
if(ai->getScanDepth() == ScanDepth::ALL_FULL)
|
||||||
{
|
{
|
||||||
scanResult.scanMap();
|
if(scanResult.scanMap())
|
||||||
|
|
||||||
if(!scanResult.bestGoal->invalid())
|
|
||||||
{
|
{
|
||||||
tasks.push_back(scanResult.makeComposition());
|
tasks.push_back(scanResult.makeComposition());
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@ set(Nullkiller_SRCS
|
|||||||
Goals/ExchangeSwapTownHeroes.cpp
|
Goals/ExchangeSwapTownHeroes.cpp
|
||||||
Goals/CompleteQuest.cpp
|
Goals/CompleteQuest.cpp
|
||||||
Goals/StayAtTown.cpp
|
Goals/StayAtTown.cpp
|
||||||
|
Goals/ExploreNeighbourTile.cpp
|
||||||
Markers/ArmyUpgrade.cpp
|
Markers/ArmyUpgrade.cpp
|
||||||
Markers/HeroExchange.cpp
|
Markers/HeroExchange.cpp
|
||||||
Markers/UnlockCluster.cpp
|
Markers/UnlockCluster.cpp
|
||||||
@ -62,6 +63,7 @@ set(Nullkiller_SRCS
|
|||||||
Behaviors/StayAtTownBehavior.cpp
|
Behaviors/StayAtTownBehavior.cpp
|
||||||
Behaviors/ExplorationBehavior.cpp
|
Behaviors/ExplorationBehavior.cpp
|
||||||
Helpers/ArmyFormation.cpp
|
Helpers/ArmyFormation.cpp
|
||||||
|
Helpers/ExplorationHelper.cpp
|
||||||
AIGateway.cpp
|
AIGateway.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -113,6 +115,7 @@ set(Nullkiller_HEADERS
|
|||||||
Goals/CompleteQuest.h
|
Goals/CompleteQuest.h
|
||||||
Goals/Goals.h
|
Goals/Goals.h
|
||||||
Goals/StayAtTown.h
|
Goals/StayAtTown.h
|
||||||
|
Goals/ExploreNeighbourTile.h
|
||||||
Markers/ArmyUpgrade.h
|
Markers/ArmyUpgrade.h
|
||||||
Markers/HeroExchange.h
|
Markers/HeroExchange.h
|
||||||
Markers/UnlockCluster.h
|
Markers/UnlockCluster.h
|
||||||
@ -135,6 +138,7 @@ set(Nullkiller_HEADERS
|
|||||||
Behaviors/StayAtTownBehavior.h
|
Behaviors/StayAtTownBehavior.h
|
||||||
Behaviors/ExplorationBehavior.h
|
Behaviors/ExplorationBehavior.h
|
||||||
Helpers/ArmyFormation.h
|
Helpers/ArmyFormation.h
|
||||||
|
Helpers/ExplorationHelper.h
|
||||||
AIGateway.h
|
AIGateway.h
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -75,7 +75,8 @@ namespace Goals
|
|||||||
STAY_AT_TOWN_BEHAVIOR,
|
STAY_AT_TOWN_BEHAVIOR,
|
||||||
STAY_AT_TOWN,
|
STAY_AT_TOWN,
|
||||||
EXPLORATION_BEHAVIOR,
|
EXPLORATION_BEHAVIOR,
|
||||||
EXPLORATION_POINT
|
EXPLORATION_POINT,
|
||||||
|
EXPLORE_NEIGHBOUR_TILE
|
||||||
};
|
};
|
||||||
|
|
||||||
class DLL_EXPORT TSubgoal : public std::shared_ptr<AbstractGoal>
|
class DLL_EXPORT TSubgoal : public std::shared_ptr<AbstractGoal>
|
||||||
|
69
AI/Nullkiller/Goals/ExploreNeighbourTile.cpp
Normal file
69
AI/Nullkiller/Goals/ExploreNeighbourTile.cpp
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* ExploreNeighbourTile.cpp, part of VCMI engine
|
||||||
|
*
|
||||||
|
* Authors: listed in file AUTHORS in main folder
|
||||||
|
*
|
||||||
|
* License: GNU General Public License v2.0 or later
|
||||||
|
* Full text of license available in license.txt file, in main folder
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#include "StdInc.h"
|
||||||
|
#include "ExploreNeighbourTile.h"
|
||||||
|
#include "../AIGateway.h"
|
||||||
|
#include "../AIUtility.h"
|
||||||
|
#include "../Helpers/ExplorationHelper.h"
|
||||||
|
|
||||||
|
|
||||||
|
namespace NKAI
|
||||||
|
{
|
||||||
|
|
||||||
|
using namespace Goals;
|
||||||
|
|
||||||
|
bool ExploreNeighbourTile::operator==(const ExploreNeighbourTile & other) const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExploreNeighbourTile::accept(AIGateway * ai)
|
||||||
|
{
|
||||||
|
ExplorationHelper h(hero, ai->nullkiller.get());
|
||||||
|
|
||||||
|
for(int i = 0; i < tilesToExplore && hero->movementPointsRemaining() > 0; i++)
|
||||||
|
{
|
||||||
|
int3 pos = hero->visitablePos();
|
||||||
|
float value = 0;
|
||||||
|
int3 target = int3(-1);
|
||||||
|
foreach_neighbour(pos, [&](int3 tile)
|
||||||
|
{
|
||||||
|
auto pathInfo = ai->myCb->getPathsInfo(hero)->getPathInfo(tile);
|
||||||
|
|
||||||
|
if(pathInfo->turns > 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(pathInfo->accessible == EPathAccessibility::ACCESSIBLE)
|
||||||
|
{
|
||||||
|
float newValue = h.howManyTilesWillBeDiscovered(tile);
|
||||||
|
|
||||||
|
newValue /= std::min(0.1f, pathInfo->getCost());
|
||||||
|
|
||||||
|
if(newValue > value)
|
||||||
|
{
|
||||||
|
value = newValue;
|
||||||
|
target = tile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!target.valid() || !ai->moveHeroToTile(target, hero))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ExploreNeighbourTile::toString() const
|
||||||
|
{
|
||||||
|
return "Explore neighbour tiles by " + hero->getNameTranslated();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
45
AI/Nullkiller/Goals/ExploreNeighbourTile.h
Normal file
45
AI/Nullkiller/Goals/ExploreNeighbourTile.h
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* ExploreNeighbourTile.h, part of VCMI engine
|
||||||
|
*
|
||||||
|
* Authors: listed in file AUTHORS in main folder
|
||||||
|
*
|
||||||
|
* License: GNU General Public License v2.0 or later
|
||||||
|
* Full text of license available in license.txt file, in main folder
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CGoal.h"
|
||||||
|
|
||||||
|
namespace NKAI
|
||||||
|
{
|
||||||
|
|
||||||
|
class AIGateway;
|
||||||
|
class FuzzyHelper;
|
||||||
|
|
||||||
|
namespace Goals
|
||||||
|
{
|
||||||
|
class DLL_EXPORT ExploreNeighbourTile : public ElementarGoal<ExploreNeighbourTile>
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
int tilesToExplore;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ExploreNeighbourTile(const CGHeroInstance * hero, int amount)
|
||||||
|
: ElementarGoal(Goals::EXPLORE_NEIGHBOUR_TILE)
|
||||||
|
{
|
||||||
|
tilesToExplore = amount;
|
||||||
|
sethero(hero);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const ExploreNeighbourTile & other) const override;
|
||||||
|
|
||||||
|
void accept(AIGateway * ai) override;
|
||||||
|
std::string toString() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
//TSubgoal decomposeSingle() const override;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
235
AI/Nullkiller/Helpers/ExplorationHelper.cpp
Normal file
235
AI/Nullkiller/Helpers/ExplorationHelper.cpp
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
/*
|
||||||
|
* ExplorationHelper.cpp, part of VCMI engine
|
||||||
|
*
|
||||||
|
* Authors: listed in file AUTHORS in main folder
|
||||||
|
*
|
||||||
|
* License: GNU General Public License v2.0 or later
|
||||||
|
* Full text of license available in license.txt file, in main folder
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#include "StdInc.h"
|
||||||
|
#include "ExplorationHelper.h"
|
||||||
|
#include "../../../lib/mapObjects/CGTownInstance.h"
|
||||||
|
#include "../Engine/Nullkiller.h"
|
||||||
|
#include "../Goals/Invalid.h"
|
||||||
|
#include "../Goals/Composition.h"
|
||||||
|
#include "../Goals/ExecuteHeroChain.h"
|
||||||
|
#include "../Markers/ExplorationPoint.h"
|
||||||
|
#include "../../../lib/CPlayerState.h"
|
||||||
|
#include "../Behaviors/CaptureObjectsBehavior.h"
|
||||||
|
#include "../Goals/ExploreNeighbourTile.h"
|
||||||
|
|
||||||
|
namespace NKAI
|
||||||
|
{
|
||||||
|
|
||||||
|
using namespace Goals;
|
||||||
|
|
||||||
|
ExplorationHelper::ExplorationHelper(const CGHeroInstance * hero, const Nullkiller * ai)
|
||||||
|
:ai(ai), cbp(ai->cb.get()), hero(hero)
|
||||||
|
{
|
||||||
|
ts = cbp->getPlayerTeam(ai->playerID);
|
||||||
|
sightRadius = hero->getSightRadius();
|
||||||
|
bestGoal = sptr(Goals::Invalid());
|
||||||
|
bestValue = 0;
|
||||||
|
bestTilesDiscovered = 0;
|
||||||
|
ourPos = hero->visitablePos();
|
||||||
|
allowDeadEndCancellation = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
TSubgoal ExplorationHelper::makeComposition() const
|
||||||
|
{
|
||||||
|
Composition c;
|
||||||
|
c.addNext(ExplorationPoint(bestTile, bestTilesDiscovered));
|
||||||
|
c.addNextSequence({bestGoal, sptr(ExploreNeighbourTile(hero, 5))});
|
||||||
|
return sptr(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ExplorationHelper::scanSector(int scanRadius)
|
||||||
|
{
|
||||||
|
int3 tile = int3(0, 0, ourPos.z);
|
||||||
|
|
||||||
|
const auto & slice = (*(ts->fogOfWarMap))[ourPos.z];
|
||||||
|
|
||||||
|
for(tile.x = ourPos.x - scanRadius; tile.x <= ourPos.x + scanRadius; tile.x++)
|
||||||
|
{
|
||||||
|
for(tile.y = ourPos.y - scanRadius; tile.y <= ourPos.y + scanRadius; tile.y++)
|
||||||
|
{
|
||||||
|
if(cbp->isInTheMap(tile) && slice[tile.x][tile.y])
|
||||||
|
{
|
||||||
|
scanTile(tile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !bestGoal->invalid();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ExplorationHelper::scanMap()
|
||||||
|
{
|
||||||
|
int3 mapSize = cbp->getMapSize();
|
||||||
|
int perimeter = 2 * sightRadius * (mapSize.x + mapSize.y);
|
||||||
|
|
||||||
|
std::vector<int3> from;
|
||||||
|
std::vector<int3> to;
|
||||||
|
|
||||||
|
from.reserve(perimeter);
|
||||||
|
to.reserve(perimeter);
|
||||||
|
|
||||||
|
foreach_tile_pos([&](const int3 & pos)
|
||||||
|
{
|
||||||
|
if((*(ts->fogOfWarMap))[pos.z][pos.x][pos.y])
|
||||||
|
{
|
||||||
|
bool hasInvisibleNeighbor = false;
|
||||||
|
|
||||||
|
foreach_neighbour(cbp, pos, [&](CCallback * cbp, int3 neighbour)
|
||||||
|
{
|
||||||
|
if(!(*(ts->fogOfWarMap))[neighbour.z][neighbour.x][neighbour.y])
|
||||||
|
{
|
||||||
|
hasInvisibleNeighbor = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(hasInvisibleNeighbor)
|
||||||
|
from.push_back(pos);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
logAi->debug("Exploration scan visible area perimeter for hero %s", hero->getNameTranslated());
|
||||||
|
|
||||||
|
for(const int3 & tile : from)
|
||||||
|
{
|
||||||
|
scanTile(tile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!bestGoal->invalid())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
allowDeadEndCancellation = false;
|
||||||
|
|
||||||
|
for(int i = 0; i < sightRadius; i++)
|
||||||
|
{
|
||||||
|
getVisibleNeighbours(from, to);
|
||||||
|
vstd::concatenate(from, to);
|
||||||
|
vstd::removeDuplicates(from);
|
||||||
|
}
|
||||||
|
|
||||||
|
logAi->debug("Exploration scan all possible tiles for hero %s", hero->getNameTranslated());
|
||||||
|
|
||||||
|
for(const int3 & tile : from)
|
||||||
|
{
|
||||||
|
scanTile(tile);
|
||||||
|
}
|
||||||
|
|
||||||
|
return !bestGoal->invalid();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExplorationHelper::scanTile(const int3 & tile)
|
||||||
|
{
|
||||||
|
if(tile == ourPos
|
||||||
|
|| !ai->cb->getTile(tile, false)
|
||||||
|
|| !ai->pathfinder->isTileAccessible(hero, tile)) //shouldn't happen, but it does
|
||||||
|
return;
|
||||||
|
|
||||||
|
int tilesDiscovered = howManyTilesWillBeDiscovered(tile);
|
||||||
|
if(!tilesDiscovered)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto paths = ai->pathfinder->getPathInfo(tile);
|
||||||
|
auto waysToVisit = CaptureObjectsBehavior::getVisitGoals(paths, ai, ai->cb->getTopObj(tile));
|
||||||
|
|
||||||
|
for(int i = 0; i != paths.size(); i++)
|
||||||
|
{
|
||||||
|
auto & path = paths[i];
|
||||||
|
auto goal = waysToVisit[i];
|
||||||
|
|
||||||
|
if(path.exchangeCount > 1 || path.targetHero != hero || path.movementCost() <= 0.0 || goal->invalid())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
float ourValue = (float)tilesDiscovered * tilesDiscovered / path.movementCost();
|
||||||
|
|
||||||
|
if(ourValue > bestValue) //avoid costly checks of tiles that don't reveal much
|
||||||
|
{
|
||||||
|
auto obj = cb->getTopObj(tile);
|
||||||
|
|
||||||
|
// picking up resources does not yield any exploration at all.
|
||||||
|
// if it blocks the way to some explorable tile AIPathfinder will take care of it
|
||||||
|
if(obj && obj->isBlockedVisitable())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isSafeToVisit(hero, path.heroArmy, path.getTotalDanger()))
|
||||||
|
{
|
||||||
|
bestGoal = goal;
|
||||||
|
bestValue = ourValue;
|
||||||
|
bestTile = tile;
|
||||||
|
bestTilesDiscovered = tilesDiscovered;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExplorationHelper::getVisibleNeighbours(const std::vector<int3> & tiles, std::vector<int3> & out) const
|
||||||
|
{
|
||||||
|
for(const int3 & tile : tiles)
|
||||||
|
{
|
||||||
|
foreach_neighbour(cbp, tile, [&](CCallback * cbp, int3 neighbour)
|
||||||
|
{
|
||||||
|
if((*(ts->fogOfWarMap))[neighbour.z][neighbour.x][neighbour.y])
|
||||||
|
{
|
||||||
|
out.push_back(neighbour);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int ExplorationHelper::howManyTilesWillBeDiscovered(const int3 & pos) const
|
||||||
|
{
|
||||||
|
int ret = 0;
|
||||||
|
int3 npos = int3(0, 0, pos.z);
|
||||||
|
|
||||||
|
const auto & slice = (*(ts->fogOfWarMap))[pos.z];
|
||||||
|
|
||||||
|
for(npos.x = pos.x - sightRadius; npos.x <= pos.x + sightRadius; npos.x++)
|
||||||
|
{
|
||||||
|
for(npos.y = pos.y - sightRadius; npos.y <= pos.y + sightRadius; npos.y++)
|
||||||
|
{
|
||||||
|
if(cbp->isInTheMap(npos)
|
||||||
|
&& pos.dist2d(npos) - 0.5 < sightRadius
|
||||||
|
&& !slice[npos.x][npos.y])
|
||||||
|
{
|
||||||
|
if(allowDeadEndCancellation
|
||||||
|
&& !hasReachableNeighbor(npos))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ExplorationHelper::hasReachableNeighbor(const int3 & pos) const
|
||||||
|
{
|
||||||
|
for(const int3 & dir : int3::getDirs())
|
||||||
|
{
|
||||||
|
int3 tile = pos + dir;
|
||||||
|
if(cbp->isInTheMap(tile))
|
||||||
|
{
|
||||||
|
auto isAccessible = ai->pathfinder->isTileAccessible(hero, tile);
|
||||||
|
|
||||||
|
if(isAccessible)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
51
AI/Nullkiller/Helpers/ExplorationHelper.h
Normal file
51
AI/Nullkiller/Helpers/ExplorationHelper.h
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* ExplorationHelper.h, part of VCMI engine
|
||||||
|
*
|
||||||
|
* Authors: listed in file AUTHORS in main folder
|
||||||
|
*
|
||||||
|
* License: GNU General Public License v2.0 or later
|
||||||
|
* Full text of license available in license.txt file, in main folder
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../AIUtility.h"
|
||||||
|
|
||||||
|
#include "../../../lib/GameConstants.h"
|
||||||
|
#include "../../../lib/VCMI_Lib.h"
|
||||||
|
#include "../../../lib/CTownHandler.h"
|
||||||
|
#include "../../../lib/CBuildingHandler.h"
|
||||||
|
#include "../Goals/AbstractGoal.h"
|
||||||
|
|
||||||
|
namespace NKAI
|
||||||
|
{
|
||||||
|
|
||||||
|
class ExplorationHelper
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
const CGHeroInstance * hero;
|
||||||
|
int sightRadius;
|
||||||
|
float bestValue;
|
||||||
|
Goals::TSubgoal bestGoal;
|
||||||
|
int3 bestTile;
|
||||||
|
int bestTilesDiscovered;
|
||||||
|
const Nullkiller * ai;
|
||||||
|
CCallback * cbp;
|
||||||
|
const TeamState * ts;
|
||||||
|
int3 ourPos;
|
||||||
|
bool allowDeadEndCancellation;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ExplorationHelper(const CGHeroInstance * hero, const Nullkiller * ai);
|
||||||
|
Goals::TSubgoal makeComposition() const;
|
||||||
|
bool scanSector(int scanRadius);
|
||||||
|
bool scanMap();
|
||||||
|
int howManyTilesWillBeDiscovered(const int3 & pos) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void scanTile(const int3 & tile);
|
||||||
|
bool hasReachableNeighbor(const int3 & pos) const;
|
||||||
|
void getVisibleNeighbours(const std::vector<int3> & tiles, std::vector<int3> & out) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user