Browse Source

First implementation of fuzzy logic in VisitTile goal.

DjWarmonger 12 years ago
parent
commit
d085f8eee8
7 changed files with 355 additions and 96 deletions
  1. 173 58
      AI/VCAI/Fuzzy.cpp
  2. 25 7
      AI/VCAI/Fuzzy.h
  3. 121 29
      AI/VCAI/Goals.cpp
  4. 24 0
      AI/VCAI/Goals.h
  5. 9 0
      CCallback.cpp
  6. 1 0
      CCallback.h
  7. 2 2
      lib/CGameState.h

+ 173 - 58
AI/VCAI/Fuzzy.cpp

@@ -5,8 +5,9 @@
 #include "../../lib/CObjectHandler.h"
 #include "../../lib/CCreatureHandler.h"
 #include "../../lib/VCMI_Lib.h"
+#include "../../CCallback.h"
 //#include "Goals.cpp"
-//#include "VCAI.h"
+#include "VCAI.h"
 
 /*
  * Fuzzy.cpp, part of VCMI engine
@@ -31,6 +32,9 @@ using namespace vstd;
 
 FuzzyHelper *fh;
 
+extern boost::thread_specific_ptr<CCallback> cb;
+extern boost::thread_specific_ptr<VCAI> ai;
+
 struct armyStructure
 {
 	float walkers, shooters, flyers;
@@ -83,8 +87,12 @@ armyStructure evaluateArmyStructure (const CArmedInstance * army)
 
 FuzzyHelper::FuzzyHelper()
 {
+	engine.hedgeSet().add(new fl::HedgeSomewhat());
+	engine.hedgeSet().add(new fl::HedgeVery());
+
 	initBank();
 	initTacticalAdvantage();
+	initVisitTile();
 }
 
 void FuzzyHelper::initBank()
@@ -117,14 +125,16 @@ void FuzzyHelper::initTacticalAdvantage()
 	{
 		//Tactical advantage calculation
 		std::vector<fl::InputLVar*> helper;
-		ourShooters = new fl::InputLVar("OurShooters");
-		ourWalkers = new fl::InputLVar("OurWalkers");
-		ourFlyers = new fl::InputLVar("OurFlyers");
-		enemyShooters = new fl::InputLVar("EnemyShooters");
-		enemyWalkers = new fl::InputLVar("EnemyWalkers");
-		enemyFlyers = new fl::InputLVar("EnemyFlyers");
 
-		helper += ourShooters, ourWalkers, ourFlyers, enemyShooters, enemyWalkers, enemyFlyers;
+		//TODO: delete all that stuff upon destruction
+		ta.ourShooters = new fl::InputLVar("OurShooters");
+		ta.ourWalkers = new fl::InputLVar("OurWalkers");
+		ta.ourFlyers = new fl::InputLVar("OurFlyers");
+		ta.enemyShooters = new fl::InputLVar("EnemyShooters");
+		ta.enemyWalkers = new fl::InputLVar("EnemyWalkers");
+		ta.enemyFlyers = new fl::InputLVar("EnemyFlyers");
+
+		helper += ta.ourShooters, ta.ourWalkers, ta.ourFlyers, ta.enemyShooters, ta.enemyWalkers, ta.enemyFlyers;
 
 		for (auto val : helper)
 		{
@@ -134,10 +144,10 @@ void FuzzyHelper::initTacticalAdvantage()
 		}
 		helper.clear();
 
-		ourSpeed =  new fl::InputLVar("OurSpeed");
-		enemySpeed = new fl::InputLVar("EnemySpeed");
+		ta.ourSpeed =  new fl::InputLVar("OurSpeed");
+		ta.enemySpeed = new fl::InputLVar("EnemySpeed");
 
-		helper += ourSpeed, enemySpeed;
+		helper += ta.ourSpeed, ta.enemySpeed;
 
 		for (auto val : helper)
 		{
@@ -146,40 +156,37 @@ void FuzzyHelper::initTacticalAdvantage()
 			val->addTerm (new fl::ShoulderTerm("HIGH", 10.5, 16, false));
 			engine.addInputLVar(val);
 		}
-		castleWalls = new fl::InputLVar("CastleWalls");
-		castleWalls->addTerm(new fl::SingletonTerm("NONE", CGTownInstance::NONE));
-		castleWalls->addTerm(new fl::TrapezoidalTerm("MEDIUM", CGTownInstance::FORT, 2.5));
-		castleWalls->addTerm(new fl::ShoulderTerm("HIGH", CGTownInstance::CITADEL - 0.1, CGTownInstance::CASTLE));
-		engine.addInputLVar(castleWalls);
-
-		bankPresent = new fl::InputLVar("Bank");
-		bankPresent->addTerm(new fl::SingletonTerm("FALSE", 0));
-		bankPresent->addTerm(new fl::SingletonTerm("TRUE", 1));
-		engine.addInputLVar(bankPresent);
-
-		threat = new fl::OutputLVar("Threat");
-		threat->addTerm (new fl::TriangularTerm("LOW", MIN_AI_STRENGHT, 1));
-		threat->addTerm (new fl::TriangularTerm("MEDIUM", 0.8, 1.2));
-		threat->addTerm (new fl::ShoulderTerm("HIGH", 1, 1.5, false));
-		engine.addOutputLVar(threat);
-
-		engine.hedgeSet().add(new fl::HedgeSomewhat());
-		engine.hedgeSet().add(new fl::HedgeVery());
-
-		tacticalAdvantage.addRule(new fl::MamdaniRule("if OurShooters is MANY and EnemySpeed is LOW then Threat is very LOW", engine));
-		tacticalAdvantage.addRule(new fl::MamdaniRule("if OurSpeed is LOW and OurShooters is FEW and EnemyShooters is MANY then Threat is very HIGH", engine));
-		tacticalAdvantage.addRule(new fl::MamdaniRule("if (OurShooters is MANY and OurFlyers is MANY) and EnemyShooters is MANY then Threat is LOW", engine));
-		tacticalAdvantage.addRule(new fl::MamdaniRule("if OurShooters is MANY and EnemySpeed is HIGH then Threat is somewhat HIGH", engine));
+		ta.castleWalls = new fl::InputLVar("CastleWalls");
+		ta.castleWalls->addTerm(new fl::SingletonTerm("NONE", CGTownInstance::NONE));
+		ta.castleWalls->addTerm(new fl::TrapezoidalTerm("MEDIUM", CGTownInstance::FORT, 2.5));
+		ta.castleWalls->addTerm(new fl::ShoulderTerm("HIGH", CGTownInstance::CITADEL - 0.1, CGTownInstance::CASTLE));
+		engine.addInputLVar(ta.castleWalls);
+
+		ta.bankPresent = new fl::InputLVar("Bank");
+		ta.bankPresent->addTerm(new fl::SingletonTerm("FALSE", 0));
+		ta.bankPresent->addTerm(new fl::SingletonTerm("TRUE", 1));
+		engine.addInputLVar(ta.bankPresent);
+
+		ta.threat = new fl::OutputLVar("Threat");
+		ta.threat->addTerm (new fl::TriangularTerm("LOW", MIN_AI_STRENGHT, 1));
+		ta.threat->addTerm (new fl::TriangularTerm("MEDIUM", 0.8, 1.2));
+		ta.threat->addTerm (new fl::ShoulderTerm("HIGH", 1, 1.5, false));
+		engine.addOutputLVar(ta.threat);
+
+		ta.tacticalAdvantage.addRule(new fl::MamdaniRule("if OurShooters is MANY and EnemySpeed is LOW then Threat is very LOW", engine));
+		ta.tacticalAdvantage.addRule(new fl::MamdaniRule("if OurSpeed is LOW and OurShooters is FEW and EnemyShooters is MANY then Threat is very HIGH", engine));
+		ta.tacticalAdvantage.addRule(new fl::MamdaniRule("if (OurShooters is MANY and OurFlyers is MANY) and EnemyShooters is MANY then Threat is LOW", engine));
+		ta.tacticalAdvantage.addRule(new fl::MamdaniRule("if OurShooters is MANY and EnemySpeed is HIGH then Threat is somewhat HIGH", engine));
 		//tacticalAdvantage.addRule(new fl::MamdaniRule("if OurShooters is MANY and EnemyShooters is MANY then Threat is MEDIUM", engine));
 
-		tacticalAdvantage.addRule(new fl::MamdaniRule("if Bank is TRUE and OurShooters is MANY then Threat is somewhat HIGH", engine));
-		tacticalAdvantage.addRule(new fl::MamdaniRule("if Bank is TRUE and EnemyShooters is MANY then Threat is LOW", engine));
+		ta.tacticalAdvantage.addRule(new fl::MamdaniRule("if Bank is TRUE and OurShooters is MANY then Threat is somewhat HIGH", engine));
+		ta.tacticalAdvantage.addRule(new fl::MamdaniRule("if Bank is TRUE and EnemyShooters is MANY then Threat is LOW", engine));
 
-		tacticalAdvantage.addRule(new fl::MamdaniRule("if CastleWalls is HIGH and OurWalkers is MANY then Threat is very HIGH", engine));
-		tacticalAdvantage.addRule(new fl::MamdaniRule("if CastleWalls is HIGH and OurFlyers is MANY and OurShooters is MANY then Threat is MEDIUM", engine));
-		tacticalAdvantage.addRule(new fl::MamdaniRule("if CastleWalls is MEDIUM and OurShooters is MANY and EnemyWalkers is MANY then Threat is LOW", engine));
+		ta.tacticalAdvantage.addRule(new fl::MamdaniRule("if CastleWalls is HIGH and OurWalkers is MANY then Threat is very HIGH", engine));
+		ta.tacticalAdvantage.addRule(new fl::MamdaniRule("if CastleWalls is HIGH and OurFlyers is MANY and OurShooters is MANY then Threat is MEDIUM", engine));
+		ta.tacticalAdvantage.addRule(new fl::MamdaniRule("if CastleWalls is MEDIUM and OurShooters is MANY and EnemyWalkers is MANY then Threat is LOW", engine));
 
-		engine.addRuleBlock (&tacticalAdvantage);
+		engine.addRuleBlock (&ta.tacticalAdvantage);
 	}
 	catch(fl::ParsingException & pe)
 	{
@@ -243,32 +250,32 @@ float FuzzyHelper::getTacticalAdvantage (const CArmedInstance *we, const CArmedI
 		armyStructure ourStructure = evaluateArmyStructure(we);
 		armyStructure enemyStructure = evaluateArmyStructure(enemy);
 
-		ourWalkers->setInput(ourStructure.walkers);
-		ourShooters->setInput(ourStructure.shooters);
-		ourFlyers->setInput(ourStructure.flyers);
-		ourSpeed->setInput(ourStructure.maxSpeed);
+		ta.ourWalkers->setInput(ourStructure.walkers);
+		ta.ourShooters->setInput(ourStructure.shooters);
+		ta.ourFlyers->setInput(ourStructure.flyers);
+		ta.ourSpeed->setInput(ourStructure.maxSpeed);
 
-		enemyWalkers->setInput(enemyStructure.walkers);
-		enemyShooters->setInput(enemyStructure.shooters);
-		enemyFlyers->setInput(enemyStructure.flyers);
-		enemySpeed->setInput(enemyStructure.maxSpeed);
+		ta.enemyWalkers->setInput(enemyStructure.walkers);
+		ta.enemyShooters->setInput(enemyStructure.shooters);
+		ta.enemyFlyers->setInput(enemyStructure.flyers);
+		ta.enemySpeed->setInput(enemyStructure.maxSpeed);
 
 		bool bank = dynamic_cast<const CBank*>(enemy);
 		if (bank)
-			bankPresent->setInput(1);
+			ta.bankPresent->setInput(1);
 		else
-			bankPresent->setInput(0);
+			ta.bankPresent->setInput(0);
 
 		const CGTownInstance * fort = dynamic_cast<const CGTownInstance*>(enemy);
 		if (fort)
 		{
-			castleWalls->setInput (fort->fortLevel());
+			ta.castleWalls->setInput (fort->fortLevel());
 		}
 		else
-			castleWalls->setInput(0);
+			ta.castleWalls->setInput(0);
 
 		engine.process (TACTICAL_ADVANTAGE);
-		output = threat->output().defuzzify();
+		output = ta.threat->output().defuzzify();
 	}
 	catch (fl::FuzzyException & fe)
 	{
@@ -277,9 +284,28 @@ float FuzzyHelper::getTacticalAdvantage (const CArmedInstance *we, const CArmedI
 	return output;
 }
 
+FuzzyHelper::TacticalAdvantage::~TacticalAdvantage()
+{
+	//TODO: smart pointers?
+	delete ourWalkers;
+	delete ourShooters;
+	delete ourFlyers;
+	delete enemyWalkers;
+	delete enemyShooters;
+	delete enemyFlyers;
+	delete ourSpeed;
+	delete enemySpeed;
+	delete bankPresent;
+	delete castleWalls;
+	delete threat;
+}
+
 //shared_ptr<AbstractGoal> chooseSolution (std::vector<shared_ptr<AbstractGoal>> & vec)
 Goals::TSubgoal FuzzyHelper::chooseSolution (Goals::TGoalVec & vec)
 {
+	if (vec.empty()) //no possibilities found
+		return sptr(Goals::Invalid());
+
 	typedef std::pair<Goals::TSubgoal, float> goalValue;
 	std::vector <goalValue> values;
 
@@ -294,20 +320,109 @@ Goals::TSubgoal FuzzyHelper::chooseSolution (Goals::TGoalVec & vec)
 	};
 
 	boost::sort (values, compareGoals);
-	return values.end()->first;
+	return values.back().first;
 }
 
 float FuzzyHelper::evaluate (Goals::Explore & g)
 {
-	return 0;
+	return 1;
 }
 float FuzzyHelper::evaluate (Goals::RecruitHero & g)
 {
-	return 0;
+	return 1; //just try to recruit hero as one of options
+}
+FuzzyHelper::EvalVisitTile::~EvalVisitTile()
+{
+	delete strengthRatio;
+	delete heroStrength;
+	delete tileDistance;
+	delete missionImportance;
+}
+
+void FuzzyHelper::initVisitTile()
+{
+	std::vector<fl::InputLVar*> helper;
+
+	vt.strengthRatio = new fl::InputLVar("strengthRatio"); //hero must be strong enough to defeat guards
+	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.missionImportance = new fl::InputLVar("lockedMissionImportance"); //we may want to preempt hero with low-priority mission
+	vt.value = new fl::OutputLVar("Value");
+
+	helper += vt.strengthRatio, vt.heroStrength, vt.tileDistance, vt.missionImportance;
+
+	vt.strengthRatio->addTerm (new fl::ShoulderTerm("LOW", 0.3, SAFE_ATTACK_CONSTANT, true));
+	vt.strengthRatio->addTerm (new fl::ShoulderTerm("HIGH", SAFE_ATTACK_CONSTANT, SAFE_ATTACK_CONSTANT * 3, false));
+
+	vt.heroStrength->addTerm (new fl::ShoulderTerm("LOW", 1, 2500, true)); //assumed strength of new hero from tavern
+	vt.heroStrength->addTerm (new fl::TriangularTerm("MEDIUM", 2500, 40000)); //assumed strength of hero able to defeat bank
+	vt.heroStrength->addTerm (new fl::ShoulderTerm("HIGH", 40000, 1e5, false)); //assumed strength of hero able to clear utopia
+
+	vt.tileDistance->addTerm (new fl::ShoulderTerm("SMALL", 0, 3.5, true));
+	vt.tileDistance->addTerm (new fl::TriangularTerm("MEDIUM", 3, 10.5));
+	vt.tileDistance->addTerm (new fl::ShoulderTerm("LONG", 10, 50, false));
+
+	vt.missionImportance->addTerm (new fl::ShoulderTerm("LOW", 0, 3.1, true));
+	vt.missionImportance->addTerm (new fl::TriangularTerm("MEDIUM", 2, 9.5));
+	vt.missionImportance->addTerm (new fl::ShoulderTerm("HIGH", 4.5, 10, false));
+
+	vt.value->addTerm (new fl::ShoulderTerm("LOW", 0, 1.1, true));
+	vt.value->addTerm (new fl::ShoulderTerm("HIGH", 1, 5, false));
+	
+	for (auto val : helper)
+	{
+		engine.addInputLVar(val);
+	}
+	engine.addOutputLVar (vt.value);
+
+	//vt.rules.addRule (new fl::MamdaniRule("if OurShooters is MANY and EnemySpeed is LOW then Threat is very LOW", engine));
+	//use unarmed scouts if possible
+	vt.rules.addRule (new fl::MamdaniRule("if strengthRatio is HIGH and heroStrength is LOW then Value is very HIGH", engine));
+	//if medium heroes can't scratch enemy, don't try to arm them
+	vt.rules.addRule (new fl::MamdaniRule("if strengthRatio is LOW and heroStrength is MEDIUM then Value is LOW", engine));
+	//do not cancel important goals
+	vt.rules.addRule (new fl::MamdaniRule("if lockedMissionImportance is HIGH then Value is very LOW", engine));
+	//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 LONG then Value is LOW", engine));
+
+	engine.addRuleBlock (&vt.rules);
 }
 float FuzzyHelper::evaluate (Goals::VisitTile & g)
 {
-	return 0;
+	if (!g.hero)
+		return 0;
+
+	float output = 0;
+
+	cb->setSelection (g.hero.h);
+	int distance = cb->getDistance(g.tile); //at this point we already assume tile is reachable
+	
+	float missionImportance = 0;
+	if (vstd::contains(ai->lockedHeroes, g.hero))
+		missionImportance = ai->lockedHeroes[g.hero]->importanceWhenLocked();
+
+	float strengthRatio = 100; //we are much stronger than enemy
+	ui64 danger = evaluateDanger (g.tile, g.hero.h);
+	if (danger)
+		strengthRatio = g.hero.h->getTotalStrength() / danger;
+
+	try
+	{
+		vt.strengthRatio->setInput (strengthRatio);
+		vt.heroStrength->setInput (g.hero->getTotalStrength());
+		vt.tileDistance->setInput (distance);
+		vt.missionImportance->setInput (missionImportance);
+
+		engine.process (VISIT_TILE);
+		output = vt.value->output().defuzzify();
+	}
+	catch (fl::FuzzyException & fe)
+	{
+        logAi->errorStream() << "evaluate VisitTile " << fe.name() << ": " << fe.message();
+	}
+	return output;
+
 }
 float FuzzyHelper::evaluate (Goals::VisitHero & g)
 {

+ 25 - 7
AI/VCAI/Fuzzy.h

@@ -28,19 +28,37 @@ class FuzzyHelper
 	fl::OutputLVar* bankDanger;
 	fl::RuleBlock bankBlock;
 
-	fl::InputLVar * ourWalkers, * ourShooters, * ourFlyers, * enemyWalkers, * enemyShooters, * enemyFlyers;
-	fl::InputLVar * ourSpeed, * enemySpeed;
-	fl::InputLVar * bankPresent;
-	fl::InputLVar * castleWalls;
-	fl::OutputLVar * threat;
-	fl::RuleBlock tacticalAdvantage;
+	class TacticalAdvantage
+	{
+	public:
+		fl::InputLVar * ourWalkers, * ourShooters, * ourFlyers, * enemyWalkers, * enemyShooters, * enemyFlyers;
+		fl::InputLVar * ourSpeed, * enemySpeed;
+		fl::InputLVar * bankPresent;
+		fl::InputLVar * castleWalls;
+		fl::OutputLVar * threat;
+		fl::RuleBlock tacticalAdvantage;
+		~TacticalAdvantage();
+	} ta;
+
+	class EvalVisitTile
+	{
+	public:
+		fl::InputLVar * strengthRatio;
+		fl::InputLVar * heroStrength;
+		fl::InputLVar * tileDistance;
+		fl::InputLVar * missionImportance;
+		fl::OutputLVar * value;
+		fl::RuleBlock rules;
+		~EvalVisitTile();
+	} vt;
 
 public:
-	enum RuleBlocks {BANK_DANGER, TACTICAL_ADVANTAGE};
+	enum RuleBlocks {BANK_DANGER, TACTICAL_ADVANTAGE, VISIT_TILE}; //where is it used and why it's needed?
 
 	FuzzyHelper();
 	void initBank();
 	void initTacticalAdvantage();
+	void initVisitTile();
 
 	float evaluate (Goals::Explore & g);
 	float evaluate (Goals::RecruitHero & g);

+ 121 - 29
AI/VCAI/Goals.cpp

@@ -15,6 +15,7 @@
 
 extern boost::thread_specific_ptr<CCallback> cb;
 extern boost::thread_specific_ptr<VCAI> ai;
+extern FuzzyHelper * fh; //TODO: this logic should be moved inside VCAI
 
 using namespace vstd;
 using namespace Goals;
@@ -225,6 +226,11 @@ TSubgoal FindObj::whatToDoToAchieve()
 	else
 		return sptr (Goals::Explore());
 }
+float FindObj::importanceWhenLocked() const
+{
+	return 1; //we will probably fins it anyway, someday
+}
+
 std::string GetObj::completeMessage() const
 {
 	return "hero " + hero.get()->name + " captured Object ID = " + boost::lexical_cast<std::string>(objid); 
@@ -239,6 +245,12 @@ TSubgoal GetObj::whatToDoToAchieve()
 	return sptr (Goals::VisitTile(pos));
 }
 
+float GetObj::importanceWhenLocked() const
+{
+	return 3;
+}
+
+
 bool GetObj::fulfillsMe (shared_ptr<VisitTile> goal)
 {
 	if (cb->getObj(ObjectInstanceID(objid))->visitablePos() == goal->tile)
@@ -268,6 +280,11 @@ TSubgoal VisitHero::whatToDoToAchieve()
 	return sptr (Goals::Invalid());
 }
 
+float VisitHero::importanceWhenLocked() const
+{
+	return 4;
+}
+
 bool VisitHero::fulfillsMe (shared_ptr<VisitTile> goal)
 {
 	if (cb->getObj(ObjectInstanceID(objid))->visitablePos() == goal->tile)
@@ -284,6 +301,11 @@ TSubgoal GetArtOfType::whatToDoToAchieve()
 	return sptr (Goals::Invalid());
 }
 
+float GetArtOfType::importanceWhenLocked() const
+{
+	return 2;
+}
+
 TSubgoal ClearWayTo::whatToDoToAchieve()
 {
 	assert(tile.x >= 0); //set tile
@@ -334,6 +356,11 @@ TSubgoal ClearWayTo::whatToDoToAchieve()
 	throw cannotFulfillGoalException("Cannot reach given tile!"); //how and when could this be used?
 }
 
+float ClearWayTo::importanceWhenLocked() const
+{
+	return 5;
+}
+
 std::string Explore::completeMessage() const
 {
 	return "Hero " + hero.get()->name + " completed exploration";
@@ -452,6 +479,11 @@ TSubgoal Explore::whatToDoToAchieve()
 	return iAmElementar(); //FIXME: how can this be called?
 };
 
+float Explore::importanceWhenLocked() const
+{
+	return 1; //exploration is natural and lowpriority process
+}
+
 TSubgoal RecruitHero::whatToDoToAchieve()
 {
 	const CGTownInstance *t = ai->findTownWithTavern();
@@ -471,42 +503,63 @@ std::string VisitTile::completeMessage() const
 
 TSubgoal VisitTile::whatToDoToAchieve()
 {
-	if(!cb->isVisible(tile))
-		return sptr (Goals::Explore());
+	//here only temporarily
+	auto ret = fh->chooseSolution (getAllPossibleSubgoals());
 
-	if(hero && !ai->isAccessibleForHero(tile, hero))
-		hero = nullptr;
-
-	if(!hero)
+	if (ret->hero)
 	{
-		if(cb->getHeroesInfo().empty())
+		if (isSafeToVisit(ret->hero, tile))
 		{
-			return sptr (Goals::RecruitHero());
-		}
-
-		for(const CGHeroInstance *h : cb->getHeroesInfo())
-		{
-			if(ai->isAccessibleForHero(tile, h))
-			{
-				hero = h;
-				break;
-			}
+			ret->setisElementar(true);
+			return ret;
 		}
-	}
-
-	if(hero)
-	{
-		if(isSafeToVisit(hero, tile))
-			return sptr (setisElementar(true));
 		else
 		{
-			return sptr (Goals::GatherArmy(evaluateDanger(tile, *hero) * SAFE_ATTACK_CONSTANT).sethero(hero));
+			return sptr (Goals::GatherArmy(evaluateDanger(tile, *ret->hero) * SAFE_ATTACK_CONSTANT).sethero(ret->hero));
 		}
 	}
-	else	//inaccessible for all heroes
-	{
-		return sptr (Goals::ClearWayTo(tile));
-	}
+	return ret;
+	//if(!cb->isVisible(tile))
+	//	return sptr (Goals::Explore());
+
+	//if(hero && !ai->isAccessibleForHero(tile, hero))
+	//	hero = nullptr;
+
+	//if(!hero)
+	//{
+	//	if(cb->getHeroesInfo().empty())
+	//	{
+	//		return sptr (Goals::RecruitHero());
+	//	}
+
+	//	for(const CGHeroInstance *h : cb->getHeroesInfo())
+	//	{
+	//		if(ai->isAccessibleForHero(tile, h))
+	//		{
+	//			hero = h;
+	//			break;
+	//		}
+	//	}
+	//}
+
+	//if(hero)
+	//{
+	//	if(isSafeToVisit(hero, tile))
+	//		return sptr (setisElementar(true));
+	//	else
+	//	{
+	//		return sptr (Goals::GatherArmy(evaluateDanger(tile, *hero) * SAFE_ATTACK_CONSTANT).sethero(hero));
+	//	}
+	//}
+	//else	//inaccessible for all heroes
+	//{
+	//	return sptr (Goals::ClearWayTo(tile));
+	//}
+}
+
+float VisitTile::importanceWhenLocked() const
+{
+	return 5; //depends on a distance, but we should really reach the tile once it was selected
 }
 
 TGoalVec VisitTile::getAllPossibleSubgoals()
@@ -518,11 +571,15 @@ TGoalVec VisitTile::getAllPossibleSubgoals()
 	{
 		for (auto h : cb->getHeroesInfo())
 		{
-			ret.push_back (sptr(Goals::VisitTile(tile).sethero(h)));
+			if (ai->isAccessibleForHero(tile, h))
+				ret.push_back (sptr(Goals::VisitTile(tile).sethero(h)));
 		}
 		if (ai->canRecruitAnyHero())
 			ret.push_back (sptr(Goals::RecruitHero()));
 	}
+	if (ret.empty())
+		ret.push_back (sptr(Goals::ClearWayTo(tile)));
+	//important - at least one sub-goal must handle case which is impossible to fulfill (unreachable tile)
 	return ret;
 }
 
@@ -539,6 +596,11 @@ TSubgoal DigAtTile::whatToDoToAchieve()
 	return sptr (Goals::VisitTile(tile));
 }
 
+float DigAtTile::importanceWhenLocked() const
+{
+	return 20; //do not! interrupt tile digging
+}
+
 TSubgoal BuildThis::whatToDoToAchieve()
 {
 	//TODO check res
@@ -547,6 +609,11 @@ TSubgoal BuildThis::whatToDoToAchieve()
 	return iAmElementar();
 }
 
+float BuildThis::importanceWhenLocked() const
+{
+	return 5;
+}
+
 TSubgoal CollectRes::whatToDoToAchieve()
 {
 	std::vector<const IMarket*> markets;
@@ -614,6 +681,11 @@ TSubgoal CollectRes::whatToDoToAchieve()
 	return sptr (Goals::Invalid()); //FIXME: unused?
 }
 
+float CollectRes::importanceWhenLocked() const
+{
+	return 2;
+}
+
 TSubgoal GatherTroops::whatToDoToAchieve()
 {
 	std::vector<const CGDwelling *> dwellings;
@@ -669,6 +741,11 @@ TSubgoal GatherTroops::whatToDoToAchieve()
 	//TODO: exchange troops between heroes
 }
 
+float GatherTroops::importanceWhenLocked() const
+{
+	return 2;
+}
+
 TSubgoal Conquer::whatToDoToAchieve()
 {
 	auto hs = cb->getHeroesInfo();
@@ -748,12 +825,22 @@ TSubgoal Conquer::whatToDoToAchieve()
 
 	return sptr (Goals::Explore()); //enemy is inaccessible
 }
+float Conquer::importanceWhenLocked() const
+{
+	return 10; //defeating opponent is hig priority, always
+}
+
 
 TSubgoal Build::whatToDoToAchieve()
 {
 	return iAmElementar();
 }
 
+float Build::importanceWhenLocked() const
+{
+	return 1;
+}
+
 TSubgoal Invalid::whatToDoToAchieve()
 {
 	return iAmElementar();
@@ -879,6 +966,11 @@ TSubgoal GatherArmy::whatToDoToAchieve()
 	return sptr (Goals::Explore(hero)); //find dwelling. use current hero to prevent him from doing nothing.
 }
 
+float GatherArmy::importanceWhenLocked() const
+{
+	return 2.5;
+}
+
 //TSubgoal AbstractGoal::whatToDoToAchieve()
 //{
 //    logAi->debugStream() << boost::format("Decomposing goal of type %s") % name();

+ 24 - 0
AI/VCAI/Goals.h

@@ -92,6 +92,7 @@ public:
 	virtual AbstractGoal * clone() const = 0;
 
 	EGoals goalType;
+
 	std::string name() const;
 	virtual std::string completeMessage() const {return "This goal is unspecified!";};
 
@@ -103,6 +104,9 @@ public:
 
 	virtual TGoalVec getAllPossibleSubgoals() = 0;
 	virtual TSubgoal whatToDoToAchieve() = 0;
+	virtual float importanceWhenLocked() const {return -1e10;}; //how much would it cost to interrupt the goal
+	//probably could use some sophisticated fuzzy evalluation for it as well
+
 	///Visitor pattern
 	//TODO: make accept work for shared_ptr... somehow
 	virtual void accept (VCAI * ai); //unhandled goal will report standard error
@@ -186,6 +190,7 @@ class Invalid : public CGoal<Invalid>
 	Invalid() : CGoal (Goals::INVALID){};
 	TGoalVec getAllPossibleSubgoals() override {return TGoalVec();};
 	TSubgoal whatToDoToAchieve() override;
+	//float importanceWhenLocked() const override;
 };
 class Win : public CGoal<Win>
 {
@@ -193,6 +198,7 @@ class Win : public CGoal<Win>
 	Win() : CGoal (Goals::WIN){};
 	TGoalVec getAllPossibleSubgoals() override {return TGoalVec();};
 	TSubgoal whatToDoToAchieve() override;
+	//float importanceWhenLocked() const override; //can't be locked, doesn't make much sense anyway
 };
 class NotLose : public CGoal<NotLose>
 {
@@ -200,6 +206,7 @@ class NotLose : public CGoal<NotLose>
 	NotLose() : CGoal (Goals::DO_NOT_LOSE){};
 	TGoalVec getAllPossibleSubgoals() override {return TGoalVec();};
 	TSubgoal whatToDoToAchieve() override;
+	float importanceWhenLocked() const override;
 };
 class Conquer : public CGoal<Conquer>
 {
@@ -207,6 +214,7 @@ class Conquer : public CGoal<Conquer>
 	Conquer() : CGoal (Goals::CONQUER){};
 	TGoalVec getAllPossibleSubgoals() override {return TGoalVec();};
 	TSubgoal whatToDoToAchieve() override;
+	float importanceWhenLocked() const override;
 };
 class Build : public CGoal<Build>
 {
@@ -214,6 +222,7 @@ class Build : public CGoal<Build>
 	Build() : CGoal (Goals::BUILD){};
 	TGoalVec getAllPossibleSubgoals() override {return TGoalVec();};
 	TSubgoal whatToDoToAchieve() override;
+	float importanceWhenLocked() const override;
 };
 class Explore : public CGoal<Explore>
 {
@@ -223,6 +232,7 @@ class Explore : public CGoal<Explore>
 	TGoalVec getAllPossibleSubgoals() override {return TGoalVec();};
 	TSubgoal whatToDoToAchieve() override;
 	std::string completeMessage() const override;
+	float importanceWhenLocked() const override;
 };
 class GatherArmy : public CGoal<GatherArmy>
 {
@@ -233,6 +243,7 @@ public:
 	TGoalVec getAllPossibleSubgoals() override {return TGoalVec();};
 	TSubgoal whatToDoToAchieve() override;
 	std::string completeMessage() const override;
+	float importanceWhenLocked() const override;
 };
 class BoostHero : public CGoal<BoostHero>
 {
@@ -240,6 +251,7 @@ class BoostHero : public CGoal<BoostHero>
 	BoostHero() : CGoal (Goals::INVALID){}; //TODO
 	TGoalVec getAllPossibleSubgoals() override {return TGoalVec();};
 	TSubgoal whatToDoToAchieve() override;
+	float importanceWhenLocked() const override;
 };
 class RecruitHero : public CGoal<RecruitHero>
 {
@@ -247,6 +259,7 @@ class RecruitHero : public CGoal<RecruitHero>
 	RecruitHero() : CGoal (Goals::RECRUIT_HERO){};
 	TGoalVec getAllPossibleSubgoals() override {return TGoalVec();};
 	TSubgoal whatToDoToAchieve() override;
+	//float importanceWhenLocked() const override;
 };
 class BuildThis : public CGoal<BuildThis>
 {
@@ -257,6 +270,7 @@ public:
 	BuildThis(BuildingID Bid) : CGoal (Goals::BUILD_STRUCTURE) {bid = Bid;};
 	TGoalVec getAllPossibleSubgoals() override {return TGoalVec();};
 	TSubgoal whatToDoToAchieve() override;
+	float importanceWhenLocked() const override;
 };
 class CollectRes : public CGoal<CollectRes>
 {
@@ -266,6 +280,7 @@ public:
 	CollectRes(int rid, int val) : CGoal (Goals::COLLECT_RES) {resID = rid; value = val;};
 	TGoalVec getAllPossibleSubgoals() override {return TGoalVec();};
 	TSubgoal whatToDoToAchieve() override;
+	float importanceWhenLocked() const override;
 };
 class GatherTroops : public CGoal<GatherTroops>
 {
@@ -275,6 +290,7 @@ public:
 	GatherTroops(int type, int val) : CGoal (Goals::GATHER_TROOPS){objid = type; value = val;};
 	TGoalVec getAllPossibleSubgoals() override {return TGoalVec();};
 	TSubgoal whatToDoToAchieve() override;
+	float importanceWhenLocked() const override;
 };
 class GetObj : public CGoal<GetObj>
 {
@@ -287,6 +303,7 @@ public:
 	bool operator== (GetObj &g) {return g.objid ==  objid;}
 	bool fulfillsMe (shared_ptr<VisitTile> goal) override;
 	std::string completeMessage() const override;
+	float importanceWhenLocked() const override;
 };
 class FindObj : public CGoal<FindObj>
 {
@@ -297,6 +314,7 @@ public:
 	FindObj(int ID, int subID) : CGoal(Goals::FIND_OBJ) {objid = ID; resID = subID;};
 	TGoalVec getAllPossibleSubgoals() override {return TGoalVec();};
 	TSubgoal whatToDoToAchieve() override;
+	float importanceWhenLocked() const override;
 };
 class VisitHero : public CGoal<VisitHero>
 {
@@ -309,6 +327,7 @@ public:
 	bool operator== (VisitHero &g) {return g.objid == objid;}
 	bool fulfillsMe (shared_ptr<VisitTile> goal) override;
 	std::string completeMessage() const override;
+	float importanceWhenLocked() const override;
 };
 class GetArtOfType : public CGoal<GetArtOfType>
 {
@@ -318,6 +337,7 @@ public:
 	GetArtOfType(int type) : CGoal (Goals::GET_ART_TYPE){aid = type;};
 	TGoalVec getAllPossibleSubgoals() override {return TGoalVec();};
 	TSubgoal whatToDoToAchieve() override;
+	float importanceWhenLocked() const override;
 };
 class VisitTile : public CGoal<VisitTile>
 	//tile, in conjunction with hero elementar; assumes tile is reachable
@@ -330,6 +350,7 @@ public:
 	TSubgoal whatToDoToAchieve() override;
 	bool operator== (VisitTile &g) {return g.tile == tile;}
 	std::string completeMessage() const override;
+	float importanceWhenLocked() const override;
 }; 
 class ClearWayTo : public CGoal<ClearWayTo>
 {
@@ -338,6 +359,7 @@ public:
 	TGoalVec getAllPossibleSubgoals() override {return TGoalVec();};
 	TSubgoal whatToDoToAchieve() override;
 	bool operator== (ClearWayTo &g) {return g.tile == tile;}
+	float importanceWhenLocked() const override;
 };
 class DigAtTile : public CGoal<DigAtTile>
 	//elementar with hero on tile
@@ -349,6 +371,7 @@ public:
 	TGoalVec getAllPossibleSubgoals() override {return TGoalVec();};
 	TSubgoal whatToDoToAchieve() override;
 	bool operator== (DigAtTile &g) {return g.tile == tile;}
+	float importanceWhenLocked() const override;
 };
 
 class CIssueCommand : public CGoal<CIssueCommand>
@@ -359,6 +382,7 @@ class CIssueCommand : public CGoal<CIssueCommand>
 	CIssueCommand(std::function<bool()> _command): CGoal(ISSUE_COMMAND), command(_command) {}
 	TGoalVec getAllPossibleSubgoals() override {return TGoalVec();};
 	TSubgoal whatToDoToAchieve() override;
+	//float importanceWhenLocked() const override; //unsupported yet, but shoudl be highest
 };
 
 }

+ 9 - 0
CCallback.cpp

@@ -311,6 +311,15 @@ const CGPathNode * CCallback::getPathInfo( int3 tile )
 	return &cl->pathInfo->nodes[tile.x][tile.y][tile.z];
 }
 
+const int CCallback::getDistance( int3 tile )
+{
+	CGPath ret;
+	if (getPath2 (tile, ret))
+		return ret.nodes.size();
+	else
+		return 255;
+}
+
 bool CCallback::getPath2( int3 dest, CGPath &ret )
 {
 	if (!gs->map->isInTheMap(dest))

+ 1 - 0
CCallback.h

@@ -108,6 +108,7 @@ public:
 
 	//client-specific functionalities (pathfinding)
 	virtual const CGPathNode *getPathInfo(int3 tile); //uses main, client pathfinder info
+	virtual const int getDistance(int3 tile);
 	virtual bool getPath2(int3 dest, CGPath &ret); //uses main, client pathfinder info
 
 	virtual void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out, int3 src = int3(-1,-1,-1), int movement = -1);

+ 2 - 2
lib/CGameState.h

@@ -227,8 +227,8 @@ struct DLL_LINKAGE CGPathNode
 
 	EAccessibility accessible;
 	ui8 land;
-	ui8 turns;
-	ui32 moveRemains;
+	ui8 turns; //how many turns we have to wait before reachng the tile - 0 means current turn
+	ui32 moveRemains; //remaining tiles after hero reaches the tile
 	CGPathNode * theNodeBefore;
 	int3 coord; //coordinates