Browse Source

VCAI tweaks (#311)

* Add extra priority support for town capture evaluation
* Improve building algorithm
* GatherArmy: check free gold instead of total for when hiring heroes
Dydzio 8 years ago
parent
commit
0cb6515ae8
6 changed files with 84 additions and 36 deletions
  1. 20 1
      AI/VCAI/Fuzzy.cpp
  2. 1 0
      AI/VCAI/Fuzzy.h
  3. 23 17
      AI/VCAI/Goals.cpp
  4. 34 18
      AI/VCAI/VCAI.cpp
  5. 5 0
      lib/mapObjects/CGHeroInstance.cpp
  6. 1 0
      lib/mapObjects/CGHeroInstance.h

+ 20 - 1
AI/VCAI/Fuzzy.cpp

@@ -338,6 +338,7 @@ FuzzyHelper::EvalVisitTile::~EvalVisitTile()
 	delete heroStrength;
 	delete heroStrength;
 	delete turnDistance;
 	delete turnDistance;
 	delete missionImportance;
 	delete missionImportance;
+	delete estimatedReward;
 }
 }
 
 
 void FuzzyHelper::initVisitTile()
 void FuzzyHelper::initVisitTile()
@@ -348,11 +349,12 @@ void FuzzyHelper::initVisitTile()
 		vt.heroStrength = new fl::InputVariable("heroStrength"); //we want to use weakest possible hero
 		vt.heroStrength = new fl::InputVariable("heroStrength"); //we want to use weakest possible hero
 		vt.turnDistance = new fl::InputVariable("turnDistance"); //we want to use hero who is near
 		vt.turnDistance = new fl::InputVariable("turnDistance"); //we want to use hero who is near
 		vt.missionImportance = new fl::InputVariable("lockedMissionImportance"); //we may want to preempt hero with low-priority mission
 		vt.missionImportance = new fl::InputVariable("lockedMissionImportance"); //we may want to preempt hero with low-priority mission
+		vt.estimatedReward = new fl::InputVariable("estimatedReward"); //indicate AI that content of the file is important or it is probably bad
 		vt.value = new fl::OutputVariable("Value");
 		vt.value = new fl::OutputVariable("Value");
 		vt.value->setMinimum(0);
 		vt.value->setMinimum(0);
 		vt.value->setMaximum(5);
 		vt.value->setMaximum(5);
 
 
-		std::vector<fl::InputVariable*> helper = {vt.strengthRatio, vt.heroStrength, vt.turnDistance, vt.missionImportance};
+		std::vector<fl::InputVariable*> helper = {vt.strengthRatio, vt.heroStrength, vt.turnDistance, vt.missionImportance, vt.estimatedReward};
 		for (auto val : helper)
 		for (auto val : helper)
 		{
 		{
 			vt.engine.addInputVariable(val);
 			vt.engine.addInputVariable(val);
@@ -379,6 +381,10 @@ void FuzzyHelper::initVisitTile()
 		vt.missionImportance->addTerm(new fl::Ramp("HIGH", 2.5, 5));
 		vt.missionImportance->addTerm(new fl::Ramp("HIGH", 2.5, 5));
 		vt.missionImportance->setRange(0.0, 5.0);
 		vt.missionImportance->setRange(0.0, 5.0);
 
 
+		vt.estimatedReward->addTerm(new fl::Ramp("LOW", 2.5, 0));
+		vt.estimatedReward->addTerm(new fl::Ramp("HIGH", 2.5, 5));
+		vt.estimatedReward->setRange(0.0, 5.0);
+
 		//an issue: in 99% cases this outputs center of mass (2.5) regardless of actual input :/
 		//an issue: in 99% cases this outputs center of mass (2.5) regardless of actual input :/
 		 //should be same as "mission Importance" to keep consistency
 		 //should be same as "mission Importance" to keep consistency
 		vt.value->addTerm(new fl::Ramp("LOW", 2.5, 0));
 		vt.value->addTerm(new fl::Ramp("LOW", 2.5, 0));
@@ -405,6 +411,9 @@ void FuzzyHelper::initVisitTile()
 		vt.addRule("if turnDistance is SMALL then Value is HIGH");
 		vt.addRule("if turnDistance is SMALL then Value is HIGH");
 		vt.addRule("if turnDistance is MEDIUM then Value is MEDIUM");
 		vt.addRule("if turnDistance is MEDIUM then Value is MEDIUM");
 		vt.addRule("if turnDistance is LONG then Value is LOW");
 		vt.addRule("if turnDistance is LONG then Value is LOW");
+		//some goals are more rewarding by definition f.e. capturing town is more important than collecting resource - experimental
+		vt.addRule("if estimatedReward is HIGH then Value is very HIGH");
+		vt.addRule("if estimatedReward is LOW then Value is somewhat LOW");
 	}
 	}
 	catch (fl::Exception & fe)
 	catch (fl::Exception & fe)
 	{
 	{
@@ -440,12 +449,22 @@ float FuzzyHelper::evaluate (Goals::VisitTile & g)
 	if (danger)
 	if (danger)
 		strengthRatio = (fl::scalar)g.hero.h->getTotalStrength() / danger;
 		strengthRatio = (fl::scalar)g.hero.h->getTotalStrength() / danger;
 
 
+	float tilePriority = 0;
+	if(g.objid == -1)
+		vt.estimatedReward->setEnabled(false);
+	else if(g.objid == Obj::TOWN) //TODO: move to getObj eventually and add appropiate logic there
+	{
+		vt.estimatedReward->setEnabled(true);
+		tilePriority = 5;
+	}
+		
 	try
 	try
 	{
 	{
 		vt.strengthRatio->setInputValue(strengthRatio);
 		vt.strengthRatio->setInputValue(strengthRatio);
 		vt.heroStrength->setInputValue((fl::scalar)g.hero->getTotalStrength() / ai->primaryHero()->getTotalStrength());
 		vt.heroStrength->setInputValue((fl::scalar)g.hero->getTotalStrength() / ai->primaryHero()->getTotalStrength());
 		vt.turnDistance->setInputValue(turns);
 		vt.turnDistance->setInputValue(turns);
 		vt.missionImportance->setInputValue(missionImportance);
 		vt.missionImportance->setInputValue(missionImportance);
+		vt.estimatedReward->setInputValue(tilePriority);
 
 
 		vt.engine.process();
 		vt.engine.process();
 		//engine.process(VISIT_TILE); //TODO: Process only Visit_Tile
 		//engine.process(VISIT_TILE); //TODO: Process only Visit_Tile

+ 1 - 0
AI/VCAI/Fuzzy.h

@@ -49,6 +49,7 @@ class FuzzyHelper
 		fl::InputVariable * heroStrength;
 		fl::InputVariable * heroStrength;
 		fl::InputVariable * turnDistance;
 		fl::InputVariable * turnDistance;
 		fl::InputVariable * missionImportance;
 		fl::InputVariable * missionImportance;
+		fl::InputVariable * estimatedReward;
 		fl::OutputVariable * value;
 		fl::OutputVariable * value;
 		fl::RuleBlock rules;
 		fl::RuleBlock rules;
 		~EvalVisitTile();
 		~EvalVisitTile();

+ 23 - 17
AI/VCAI/Goals.cpp

@@ -719,10 +719,12 @@ TSubgoal VisitTile::whatToDoToAchieve()
 {
 {
 	auto ret = fh->chooseSolution(getAllPossibleSubgoals());
 	auto ret = fh->chooseSolution(getAllPossibleSubgoals());
 
 
-	if (ret->hero)
+	if(ret->hero)
 	{
 	{
-		if (isSafeToVisit(ret->hero, tile) && ai->isAccessibleForHero(tile, ret->hero))
+		if(isSafeToVisit(ret->hero, tile) && ai->isAccessibleForHero(tile, ret->hero))
 		{
 		{
+			if(cb->getTile(tile)->topVisitableId().num == Obj::TOWN) //if target is town, fuzzy system will use additional "estimatedReward" variable to increase priority a bit
+				ret->objid = Obj::TOWN; //TODO: move to getObj eventually and add appropiate logic there
 			ret->setisElementar(true);
 			ret->setisElementar(true);
 			return ret;
 			return ret;
 		}
 		}
@@ -973,40 +975,44 @@ TGoalVec Conquer::getAllPossibleSubgoals()
 	};
 	};
 
 
 	std::vector<const CGObjectInstance *> objs;
 	std::vector<const CGObjectInstance *> objs;
-	for (auto obj : ai->visitableObjs)
+	for(auto obj : ai->visitableObjs)
 	{
 	{
-		if (conquerable(obj))
+		if(conquerable(obj))
 			objs.push_back (obj);
 			objs.push_back (obj);
 	}
 	}
 
 
-	for (auto h : cb->getHeroesInfo())
+	for(auto h : cb->getHeroesInfo())
 	{
 	{
 		auto sm = ai->getCachedSectorMap(h);
 		auto sm = ai->getCachedSectorMap(h);
 		std::vector<const CGObjectInstance *> ourObjs(objs); //copy common objects
 		std::vector<const CGObjectInstance *> ourObjs(objs); //copy common objects
 
 
-		for (auto obj : ai->reservedHeroesMap[h]) //add objects reserved by this hero
+		for(auto obj : ai->reservedHeroesMap[h]) //add objects reserved by this hero
 		{
 		{
-			if (conquerable(obj))
+			if(conquerable(obj))
 				ourObjs.push_back(obj);
 				ourObjs.push_back(obj);
 		}
 		}
-		for (auto obj : ourObjs)
+		for(auto obj : ourObjs)
 		{
 		{
 			int3 dest = obj->visitablePos();
 			int3 dest = obj->visitablePos();
 			auto t = sm->firstTileToGet(h, dest); //we assume that no more than one tile on the way is guarded
 			auto t = sm->firstTileToGet(h, dest); //we assume that no more than one tile on the way is guarded
-			if (t.valid()) //we know any path at all
+			if(t.valid()) //we know any path at all
 			{
 			{
-				if (ai->isTileNotReserved(h, t)) //no other hero wants to conquer that tile
+				if(ai->isTileNotReserved(h, t)) //no other hero wants to conquer that tile
 				{
 				{
-					if (isSafeToVisit(h, dest))
+					if(isSafeToVisit(h, dest))
 					{
 					{
-						if (dest != t) //there is something blocking our way
+						if(dest != t) //there is something blocking our way
 							ret.push_back(sptr(Goals::ClearWayTo(dest, h).setisAbstract(true)));
 							ret.push_back(sptr(Goals::ClearWayTo(dest, h).setisAbstract(true)));
 						else
 						else
 						{
 						{
-							if (obj->ID.num == Obj::HERO) //enemy hero may move to other position
+							if(obj->ID.num == Obj::HERO) //enemy hero may move to other position
 								ret.push_back(sptr(Goals::VisitHero(obj->id.getNum()).sethero(h).setisAbstract(true)));
 								ret.push_back(sptr(Goals::VisitHero(obj->id.getNum()).sethero(h).setisAbstract(true)));
 							else //just visit that tile
 							else //just visit that tile
-								ret.push_back(sptr(Goals::VisitTile(dest).sethero(h).setisAbstract(true)));
+								if(obj->ID.num == Obj::TOWN)
+									//if target is town, fuzzy system will use additional "estimatedReward" variable to increase priority a bit
+									ret.push_back(sptr(Goals::VisitTile(dest).sethero(h).setobjid(obj->ID.num).setisAbstract(true))); //TODO: change to getObj eventually and and move appropiate logic there
+								else
+									ret.push_back(sptr(Goals::VisitTile(dest).sethero(h).setisAbstract(true)));
 						}
 						}
 					}
 					}
 					else //we need to get army in order to conquer that place
 					else //we need to get army in order to conquer that place
@@ -1124,13 +1130,13 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
 		}
 		}
 	}
 	}
 
 
-	if (ai->canRecruitAnyHero()) //this is not stupid in early phase of game
+	if(ai->canRecruitAnyHero() && ai->freeResources()[Res::GOLD] > GameConstants::HERO_GOLD_COST) //this is not stupid in early phase of game
 	{
 	{
-		if (auto t = ai->findTownWithTavern())
+		if(auto t = ai->findTownWithTavern())
 		{
 		{
 			for (auto h : cb->getAvailableHeroes(t)) //we assume that all towns have same set of heroes
 			for (auto h : cb->getAvailableHeroes(t)) //we assume that all towns have same set of heroes
 			{
 			{
-				if (h && h->getTotalStrength() > 500) //do not buy heroes with single creatures for GatherArmy
+				if(h && h->getTotalStrength() > 500) //do not buy heroes with single creatures for GatherArmy
 				{
 				{
 					ret.push_back(sptr(Goals::RecruitHero()));
 					ret.push_back(sptr(Goals::RecruitHero()));
 					break;
 					break;

+ 34 - 18
AI/VCAI/VCAI.cpp

@@ -1352,6 +1352,7 @@ bool VCAI::tryBuildNextStructure(const CGTownInstance * t, std::vector<BuildingI
 //Set of buildings for different goals. Does not include any prerequisites.
 //Set of buildings for different goals. Does not include any prerequisites.
 static const BuildingID essential[] = {BuildingID::TAVERN, BuildingID::TOWN_HALL};
 static const BuildingID essential[] = {BuildingID::TAVERN, BuildingID::TOWN_HALL};
 static const BuildingID goldSource[] = {BuildingID::TOWN_HALL, BuildingID::CITY_HALL, BuildingID::CAPITOL};
 static const BuildingID goldSource[] = {BuildingID::TOWN_HALL, BuildingID::CITY_HALL, BuildingID::CAPITOL};
+static const BuildingID capitolRequirements[] = { BuildingID::FORT, BuildingID::CITADEL };
 static const BuildingID unitsSource[] = { BuildingID::DWELL_LVL_1, BuildingID::DWELL_LVL_2, BuildingID::DWELL_LVL_3,
 static 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};
 	BuildingID::DWELL_LVL_4, BuildingID::DWELL_LVL_5, BuildingID::DWELL_LVL_6, BuildingID::DWELL_LVL_7};
 static const BuildingID unitsUpgrade[] = { BuildingID::DWELL_LVL_1_UP, BuildingID::DWELL_LVL_2_UP, BuildingID::DWELL_LVL_3_UP,
 static const BuildingID unitsUpgrade[] = { BuildingID::DWELL_LVL_1_UP, BuildingID::DWELL_LVL_2_UP, BuildingID::DWELL_LVL_3_UP,
@@ -1370,55 +1371,64 @@ 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)
 
 
+	//below algorithm focuses on economy growth at start of the game.
 	TResources currentRes = cb->getResourceAmount();
 	TResources currentRes = cb->getResourceAmount();
 	TResources currentIncome = t->dailyIncome();
 	TResources currentIncome = t->dailyIncome();
 	int townIncome = currentIncome[Res::GOLD];
 	int townIncome = currentIncome[Res::GOLD];
 
 
-	if (tryBuildAnyStructure(t, std::vector<BuildingID>(essential, essential + ARRAY_COUNT(essential))))
+	if(tryBuildAnyStructure(t, std::vector<BuildingID>(essential, essential + ARRAY_COUNT(essential))))
 		return;
 		return;
 
 
-	//we're running out of gold - try to build something gold-producing. Multiplier can be tweaked, 6 is minimum due to buildings costs
-	if (currentRes[Res::GOLD] < townIncome * 6)
-		if (tryBuildNextStructure(t, std::vector<BuildingID>(goldSource, goldSource + ARRAY_COUNT(goldSource))))
+	//the more gold the better and less problems later
+	if(tryBuildNextStructure(t, std::vector<BuildingID>(goldSource, goldSource + ARRAY_COUNT(goldSource))))
+		return;
+
+	//workaround for mantis #2696 - build fort and citadel - building castle will be handled without bug
+	if(vstd::contains(t->builtBuildings, BuildingID::CITY_HALL) && cb->canBuildStructure(t, BuildingID::CAPITOL) != EBuildingState::HAVE_CAPITAL &&
+		cb->canBuildStructure(t, BuildingID::CAPITOL) != EBuildingState::FORBIDDEN)
+		if(tryBuildNextStructure(t, std::vector<BuildingID>(capitolRequirements, capitolRequirements + ARRAY_COUNT(capitolRequirements))))
 			return;
 			return;
 
 
-	if (cb->getDate(Date::DAY_OF_WEEK) > 6)// last 2 days of week - try to focus on growth
+	if((!vstd::contains(t->builtBuildings, BuildingID::CAPITOL) && cb->canBuildStructure(t, BuildingID::CAPITOL) != EBuildingState::HAVE_CAPITAL && cb->canBuildStructure(t, BuildingID::CAPITOL) != EBuildingState::FORBIDDEN)
+		|| (!vstd::contains(t->builtBuildings, BuildingID::CITY_HALL) && cb->canBuildStructure(t, BuildingID::CAPITOL) == EBuildingState::HAVE_CAPITAL && cb->canBuildStructure(t, BuildingID::CITY_HALL) != EBuildingState::FORBIDDEN)
+		|| (!vstd::contains(t->builtBuildings, BuildingID::TOWN_HALL) && cb->canBuildStructure(t, BuildingID::TOWN_HALL) != EBuildingState::FORBIDDEN))
+		return; //save money for capitol or city hall if capitol unavailable, do not build other things (unless gold source buildings are disabled in map editor)
+
+	if(cb->getDate(Date::DAY_OF_WEEK) > 6)// last 2 days of week - try to focus on growth
 	{
 	{
-		if (tryBuildNextStructure(t, std::vector<BuildingID>(unitGrowth, unitGrowth + ARRAY_COUNT(unitGrowth)), 2))
+		if(tryBuildNextStructure(t, std::vector<BuildingID>(unitGrowth, unitGrowth + ARRAY_COUNT(unitGrowth)), 2))
 			return;
 			return;
 	}
 	}
 
 
 	// first in-game week or second half of any week: try build dwellings
 	// first in-game week or second half of any week: try build dwellings
-	if (cb->getDate(Date::DAY) < 7 || cb->getDate(Date::DAY_OF_WEEK) > 3)
-		if (tryBuildAnyStructure(t, std::vector<BuildingID>(unitsSource, unitsSource + ARRAY_COUNT(unitsSource)), 8 - cb->getDate(Date::DAY_OF_WEEK)))
+	if(cb->getDate(Date::DAY) < 7 || cb->getDate(Date::DAY_OF_WEEK) > 3)
+		if(tryBuildAnyStructure(t, std::vector<BuildingID>(unitsSource, unitsSource + ARRAY_COUNT(unitsSource)), 8 - cb->getDate(Date::DAY_OF_WEEK)))
 			return;
 			return;
 
 
 	//try to upgrade dwelling
 	//try to upgrade dwelling
 	for(int i = 0; i < ARRAY_COUNT(unitsUpgrade); i++)
 	for(int i = 0; i < ARRAY_COUNT(unitsUpgrade); i++)
 	{
 	{
-		if (t->hasBuilt(unitsSource[i]) && !t->hasBuilt(unitsUpgrade[i]))
+		if(t->hasBuilt(unitsSource[i]) && !t->hasBuilt(unitsUpgrade[i]))
 		{
 		{
-			if (tryBuildStructure(t, unitsUpgrade[i]))
+			if(tryBuildStructure(t, unitsUpgrade[i]))
 				return;
 				return;
 		}
 		}
 	}
 	}
 
 
 	//remaining tasks
 	//remaining tasks
-	if (tryBuildNextStructure(t, std::vector<BuildingID>(goldSource, goldSource + ARRAY_COUNT(goldSource))))
-		return;
-	if (tryBuildNextStructure(t, std::vector<BuildingID>(spells, spells + ARRAY_COUNT(spells))))
+	if(tryBuildNextStructure(t, std::vector<BuildingID>(spells, spells + ARRAY_COUNT(spells))))
 		return;
 		return;
-	if (tryBuildAnyStructure(t, std::vector<BuildingID>(extra, extra + ARRAY_COUNT(extra))))
+	if(tryBuildAnyStructure(t, std::vector<BuildingID>(extra, extra + ARRAY_COUNT(extra))))
 		return;
 		return;
 
 
 	//at the end, try to get and build any extra buildings with nonstandard slots (for example HotA 3rd level dwelling)
 	//at the end, try to get and build any extra buildings with nonstandard slots (for example HotA 3rd level dwelling)
 	std::vector<BuildingID> extraBuildings;
 	std::vector<BuildingID> extraBuildings;
 
 
-	for (auto buildingInfo : t->town->buildings)
-		if (buildingInfo.first > 43)
+	for(auto buildingInfo : t->town->buildings)
+		if(buildingInfo.first > 43)
 			extraBuildings.push_back(buildingInfo.first);
 			extraBuildings.push_back(buildingInfo.first);
 
 
-	if (tryBuildAnyStructure(t, extraBuildings))
+	if(tryBuildAnyStructure(t, extraBuildings))
 		return;
 		return;
 }
 }
 
 
@@ -2865,7 +2875,13 @@ void VCAI::validateObject(ObjectIdRef obj)
 TResources VCAI::freeResources() const
 TResources VCAI::freeResources() const
 {
 {
 	TResources myRes = cb->getResourceAmount();
 	TResources myRes = cb->getResourceAmount();
-	myRes[Res::GOLD] -= GOLD_RESERVE;
+	auto iterator = cb->getTownsInfo();
+	if(std::none_of(iterator.begin(), iterator.end(), [](const CGTownInstance * x)->bool 
+	{ 
+		return x->builtBuildings.find(BuildingID::CAPITOL) != x->builtBuildings.end(); 
+	})
+		/*|| std::all_of(iterator.begin(), iterator.end(), [](const CGTownInstance * x) -> bool { return x->forbiddenBuildings.find(BuildingID::CAPITOL) != x->forbiddenBuildings.end(); })*/ )
+		myRes[Res::GOLD] -= GOLD_RESERVE; //what if capitol is blocked from building in all possessed towns (set in map editor)? What about reserve for city hall or something similar in that case?
 	vstd::amax(myRes[Res::GOLD], 0);
 	vstd::amax(myRes[Res::GOLD], 0);
 	return myRes;
 	return myRes;
 }
 }

+ 5 - 0
lib/mapObjects/CGHeroInstance.cpp

@@ -866,6 +866,11 @@ void CGHeroInstance::setPropertyDer( ui8 what, ui32 val )
 		setStackCount(SlotID(0), val);
 		setStackCount(SlotID(0), val);
 }
 }
 
 
+TFaction CGHeroInstance::getFaction() const
+{
+	return type->heroClass->faction;
+}
+
 double CGHeroInstance::getFightingStrength() const
 double CGHeroInstance::getFightingStrength() const
 {
 {
 	return sqrt((1.0 + 0.05*getPrimSkillLevel(PrimarySkill::ATTACK)) * (1.0 + 0.05*getPrimSkillLevel(PrimarySkill::DEFENSE)));
 	return sqrt((1.0 + 0.05*getPrimSkillLevel(PrimarySkill::ATTACK)) * (1.0 + 0.05*getPrimSkillLevel(PrimarySkill::DEFENSE)));

+ 1 - 0
lib/mapObjects/CGHeroInstance.h

@@ -147,6 +147,7 @@ public:
 	EAlignment::EAlignment getAlignment() const;
 	EAlignment::EAlignment getAlignment() const;
 	const std::string &getBiography() const;
 	const std::string &getBiography() const;
 	bool needsLastStack()const override;
 	bool needsLastStack()const override;
+	TFaction getFaction() const;
 	ui32 getTileCost(const TerrainTile &dest, const TerrainTile &from, const TurnInfo * ti) const; //move cost - applying pathfinding skill, road and terrain modifiers. NOT includes diagonal move penalty, last move levelling
 	ui32 getTileCost(const TerrainTile &dest, const TerrainTile &from, const TurnInfo * ti) const; //move cost - applying pathfinding skill, road and terrain modifiers. NOT includes diagonal move penalty, last move levelling
 	int getNativeTerrain() const;
 	int getNativeTerrain() const;
 	ui32 getLowestCreatureSpeed() const;
 	ui32 getLowestCreatureSpeed() const;