PriorityEvaluator.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. /*
  2. * Fuzzy.cpp, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #include "StdInc.h"
  11. #include "PriorityEvaluator.h"
  12. #include <limits>
  13. #include "../../../lib/mapObjects/MapObjects.h"
  14. #include "../../../lib/mapObjects/CommonConstructors.h"
  15. #include "../../../lib/CCreatureHandler.h"
  16. #include "../../../lib/CPathfinder.h"
  17. #include "../../../lib/CGameStateFwd.h"
  18. #include "../../../lib/VCMI_Lib.h"
  19. #include "../../../CCallback.h"
  20. #include "../VCAI.h"
  21. #define MIN_AI_STRENGHT (0.5f) //lower when combat AI gets smarter
  22. #define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us
  23. struct BankConfig;
  24. class CBankInfo;
  25. class Engine;
  26. class InputVariable;
  27. class CGTownInstance;
  28. extern boost::thread_specific_ptr<CCallback> cb;
  29. extern boost::thread_specific_ptr<VCAI> ai;
  30. PriorityEvaluator::PriorityEvaluator()
  31. {
  32. initVisitTile();
  33. configure();
  34. }
  35. PriorityEvaluator::~PriorityEvaluator()
  36. {
  37. engine.removeRuleBlock(0);
  38. engine.~Engine();
  39. }
  40. void PriorityEvaluator::initVisitTile()
  41. {
  42. try
  43. {
  44. armyLossPersentageVariable = new fl::InputVariable("armyLoss"); //hero must be strong enough to defeat guards
  45. heroStrengthVariable = new fl::InputVariable("heroStrength"); //we want to use weakest possible hero
  46. turnDistanceVariable = new fl::InputVariable("turnDistance"); //we want to use hero who is near
  47. goldRewardVariable = new fl::InputVariable("goldReward"); //indicate AI that content of the file is important or it is probably bad
  48. armyRewardVariable = new fl::InputVariable("armyReward"); //indicate AI that content of the file is important or it is probably bad
  49. value = new fl::OutputVariable("Value");
  50. value->setMinimum(0);
  51. value->setMaximum(1);
  52. value->setAggregation(new fl::AlgebraicSum());
  53. value->setDefuzzifier(new fl::Centroid(100));
  54. value->setDefaultValue(0.500);
  55. rules.setConjunction(new fl::AlgebraicProduct());
  56. rules.setDisjunction(new fl::AlgebraicSum());
  57. rules.setImplication(new fl::AlgebraicProduct());
  58. rules.setActivation(new fl::General());
  59. std::vector<fl::InputVariable *> helper = {
  60. armyLossPersentageVariable,
  61. heroStrengthVariable,
  62. turnDistanceVariable,
  63. goldRewardVariable,
  64. armyRewardVariable };
  65. for(auto val : helper)
  66. {
  67. engine.addInputVariable(val);
  68. }
  69. engine.addOutputVariable(value);
  70. armyLossPersentageVariable->addTerm(new fl::Ramp("LOW", 0.200, 0.000));
  71. armyLossPersentageVariable->addTerm(new fl::Ramp("HIGH", 0.200, 0.500));
  72. armyLossPersentageVariable->setRange(0, 1);
  73. //strength compared to our main hero
  74. heroStrengthVariable->addTerm(new fl::Ramp("LOW", 0.2, 0));
  75. heroStrengthVariable->addTerm(new fl::Triangle("MEDIUM", 0.2, 0.8));
  76. heroStrengthVariable->addTerm(new fl::Ramp("HIGH", 0.5, 1));
  77. heroStrengthVariable->setRange(0.0, 1.0);
  78. turnDistanceVariable->addTerm(new fl::Ramp("SMALL", 1.000, 0.000));
  79. turnDistanceVariable->addTerm(new fl::Triangle("MEDIUM", 0.000, 1.000, 2.000));
  80. turnDistanceVariable->addTerm(new fl::Ramp("LONG", 1.000, 3.000));
  81. turnDistanceVariable->setRange(0, 3);
  82. goldRewardVariable->addTerm(new fl::Ramp("LOW", 2000.000, 0.000));
  83. goldRewardVariable->addTerm(new fl::Triangle("MEDIUM", 0.000, 2000.000, 3500.000));
  84. goldRewardVariable->addTerm(new fl::Ramp("HIGH", 2000.000, 5000.000));
  85. goldRewardVariable->setRange(0.0, 5000.0);
  86. armyRewardVariable->addTerm(new fl::Ramp("LOW", 0.300, 0.000));
  87. armyRewardVariable->addTerm(new fl::Triangle("MEDIUM", 0.100, 0.400, 0.800));
  88. armyRewardVariable->addTerm(new fl::Ramp("HIGH", 0.400, 1.000));
  89. armyRewardVariable->setRange(0.0, 1.0);
  90. value->addTerm(new fl::Ramp("LOWEST", 0.150, 0.000));
  91. value->addTerm(new fl::Triangle("LOW", 0.100, 0.100, 0.250, 0.500));
  92. value->addTerm(new fl::Triangle("BITLOW", 0.200, 0.200, 0.350, 0.250));
  93. value->addTerm(new fl::Triangle("MEDIUM", 0.300, 0.500, 0.700, 0.050));
  94. value->addTerm(new fl::Triangle("BITHIGH", 0.650, 0.800, 0.800, 0.250));
  95. value->addTerm(new fl::Triangle("HIGH", 0.750, 0.900, 0.900, 0.500));
  96. value->addTerm(new fl::Ramp("HIGHEST", 0.850, 1.000));
  97. value->setRange(0.0, 1.0);
  98. //we may want to use secondary hero(es) rather than main hero
  99. //do not cancel important goals
  100. //addRule("if lockedMissionImportance is HIGH then Value is very LOW");
  101. //addRule("if lockedMissionImportance is MEDIUM then Value is somewhat LOW");
  102. //addRule("if lockedMissionImportance is LOW then Value is HIGH");
  103. //pick nearby objects if it's easy, avoid long walks
  104. /*addRule("if turnDistance is SMALL then Value is somewhat HIGH");
  105. addRule("if turnDistance is MEDIUM then Value is MEDIUM");
  106. addRule("if turnDistance is LONG then Value is LOW");*/
  107. //some goals are more rewarding by definition f.e. capturing town is more important than collecting resource - experimental
  108. addRule("if turnDistance is MEDIUM then Value is MEDIUM");
  109. addRule("if turnDistance is SMALL then Value is BITHIGH");
  110. addRule("if turnDistance is LONG then Value is BITLOW");
  111. addRule("if turnDistance is very LONG then Value is LOW");
  112. addRule("if goldReward is LOW then Value is MEDIUM");
  113. addRule("if goldReward is MEDIUM and armyLoss is LOW then Value is BITHIGH");
  114. addRule("if goldReward is HIGH and armyLoss is LOW then Value is HIGH");
  115. addRule("if armyReward is LOW then Value is MEDIUM");
  116. addRule("if armyReward is MEDIUM and armyLoss is LOW then Value is BITHIGH");
  117. addRule("if armyReward is HIGH and armyLoss is LOW then Value is HIGHEST with 0.5");
  118. addRule("if armyReward is HIGH and goldReward is HIGH and armyLoss is LOW then Value is HIGHEST");
  119. addRule("if armyReward is HIGH and goldReward is MEDIUM and armyLoss is LOW then Value is HIGHEST with 0.8");
  120. addRule("if armyReward is MEDIUM and goldReward is HIGH and armyLoss is LOW then Value is HIGHEST with 0.5");
  121. addRule("if armyReward is MEDIUM and goldReward is MEDIUM and armyLoss is LOW then Value is HIGH");
  122. addRule("if armyReward is HIGH and turnDistance is SMALL and armyLoss is LOW then Value is HIGHEST");
  123. addRule("if goldReward is HIGH and turnDistance is SMALL and armyLoss is LOW then Value is HIGHEST");
  124. addRule("if armyReward is MEDIUM and turnDistance is SMALL and armyLoss is LOW then Value is HIGH");
  125. addRule("if goldReward is MEDIUM and turnDistance is SMALL and armyLoss is LOW then Value is BITHIGH");
  126. addRule("if goldReward is LOW and armyReward is LOW and turnDistance is not SMALL then Value is LOWEST");
  127. addRule("if armyLoss is HIGH then Value is LOWEST");
  128. addRule("if armyLoss is LOW then Value is MEDIUM");
  129. addRule("if armyReward is LOW and turnDistance is LONG then Value is LOWEST");
  130. }
  131. catch(fl::Exception & fe)
  132. {
  133. logAi->error("visitTile: %s", fe.getWhat());
  134. }
  135. }
  136. int32_t estimateTownIncome(const CGObjectInstance * target, const CGHeroInstance * hero)
  137. {
  138. auto relations = cb->getPlayerRelations(hero->tempOwner, target->tempOwner);
  139. if(relations != PlayerRelations::ENEMIES)
  140. return 0; // if we already own it, no additional reward will be received by just visiting it
  141. auto town = cb->getTown(target->id);
  142. auto isNeutral = target->tempOwner == PlayerColor::NEUTRAL;
  143. auto isProbablyDeveloped = !isNeutral && town->hasFort();
  144. return isProbablyDeveloped ? 1500 : 500;
  145. }
  146. TResources getCreatureBankResources(const CGObjectInstance * target, const CGHeroInstance * hero)
  147. {
  148. auto objectInfo = VLC->objtypeh->getHandlerFor(target->ID, target->subID)->getObjectInfo(target->appearance);
  149. CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
  150. auto resources = bankInfo->getPossibleResourcesReward();
  151. return resources;
  152. }
  153. uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero)
  154. {
  155. auto objectInfo = VLC->objtypeh->getHandlerFor(target->ID, target->subID)->getObjectInfo(target->appearance);
  156. CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
  157. auto creatures = bankInfo->getPossibleCreaturesReward();
  158. uint64_t result = 0;
  159. for(auto c : creatures)
  160. {
  161. result += c.type->AIValue * c.count;
  162. }
  163. return result;
  164. }
  165. uint64_t getDwellingScore(const CGObjectInstance * target)
  166. {
  167. auto dwelling = dynamic_cast<const CGDwelling *>(target);
  168. uint64_t score = 0;
  169. for(auto & creLevel : dwelling->creatures)
  170. {
  171. if(creLevel.first && creLevel.second.size())
  172. {
  173. auto creature = creLevel.second.back().toCreature();
  174. if(cb->getResourceAmount().canAfford(creature->cost * creLevel.first))
  175. {
  176. score += creature->AIValue * creLevel.first;
  177. }
  178. }
  179. }
  180. return score;
  181. }
  182. uint64_t getArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero)
  183. {
  184. if(!target)
  185. return 0;
  186. const int dailyIncomeMultiplier = 5;
  187. switch(target->ID)
  188. {
  189. case Obj::TOWN:
  190. return target->tempOwner == PlayerColor::NEUTRAL ? 5000 : 1000;
  191. case Obj::CREATURE_BANK:
  192. return getCreatureBankArmyReward(target, hero);
  193. case Obj::CREATURE_GENERATOR1:
  194. return getDwellingScore(target) * dailyIncomeMultiplier;
  195. case Obj::CRYPT:
  196. case Obj::SHIPWRECK:
  197. case Obj::SHIPWRECK_SURVIVOR:
  198. return 1500;
  199. case Obj::ARTIFACT:
  200. return dynamic_cast<const CGArtifact *>(target)->storedArtifact-> artType->getArtClassSerial() == CArtifact::ART_MAJOR ? 3000 : 1500;
  201. case Obj::DRAGON_UTOPIA:
  202. return 10000;
  203. default:
  204. return 0;
  205. }
  206. }
  207. /// Gets aproximated reward in gold. Daily income is multiplied by 5
  208. int32_t getGoldReward(const CGObjectInstance * target, const CGHeroInstance * hero)
  209. {
  210. if(!target)
  211. return 0;
  212. const int dailyIncomeMultiplier = 5;
  213. auto isGold = target->subID == Res::GOLD; // TODO: other resorces could be sold but need to evaluate market power
  214. switch(target->ID)
  215. {
  216. case Obj::RESOURCE:
  217. return isGold ? 800 : 100;
  218. case Obj::TREASURE_CHEST:
  219. return 1500;
  220. case Obj::WATER_WHEEL:
  221. return 1000;
  222. case Obj::TOWN:
  223. return dailyIncomeMultiplier * estimateTownIncome(target, hero);
  224. case Obj::MINE:
  225. case Obj::ABANDONED_MINE:
  226. return dailyIncomeMultiplier * (isGold ? 1000 : 75);
  227. case Obj::MYSTICAL_GARDEN:
  228. case Obj::WINDMILL:
  229. return 100;
  230. case Obj::CAMPFIRE:
  231. return 900;
  232. case Obj::CREATURE_BANK:
  233. return getCreatureBankResources(target, hero)[Res::GOLD];
  234. case Obj::CRYPT:
  235. case Obj::DERELICT_SHIP:
  236. return 3000;
  237. case Obj::DRAGON_UTOPIA:
  238. return 10000;
  239. case Obj::SEA_CHEST:
  240. return 1500;
  241. default:
  242. return 0;
  243. }
  244. }
  245. /// distance
  246. /// nearest hero?
  247. /// gold income
  248. /// army income
  249. /// hero strength - hero skills
  250. /// danger
  251. /// importance
  252. float PriorityEvaluator::evaluate(Goals::TSubgoal task)
  253. {
  254. auto heroPtr = task->hero;
  255. if(!heroPtr.validAndSet())
  256. return 2;
  257. int objId = task->objid;
  258. if(task->parent)
  259. objId = task->parent->objid;
  260. const CGObjectInstance * target = cb->getObj((ObjectInstanceID)objId, false);
  261. auto hero = heroPtr.get();
  262. auto armyTotal = task->evaluationContext.heroStrength;
  263. double armyLossPersentage = task->evaluationContext.armyLoss / (double)armyTotal;
  264. int32_t goldReward = getGoldReward(target, hero);
  265. uint64_t armyReward = getArmyReward(target, hero);
  266. double result = 0;
  267. try
  268. {
  269. armyLossPersentageVariable->setValue(armyLossPersentage);
  270. heroStrengthVariable->setValue((fl::scalar)hero->getTotalStrength() / ai->primaryHero()->getTotalStrength());
  271. turnDistanceVariable->setValue(task->evaluationContext.movementCost);
  272. goldRewardVariable->setValue(goldReward);
  273. armyRewardVariable->setValue(armyReward / 10000.0);
  274. engine.process();
  275. //engine.process(VISIT_TILE); //TODO: Process only Visit_Tile
  276. result = value->getValue() / task->evaluationContext.closestWayRatio;
  277. }
  278. catch(fl::Exception & fe)
  279. {
  280. logAi->error("evaluate VisitTile: %s", fe.getWhat());
  281. }
  282. assert(result >= 0);
  283. #ifdef VCMI_TRACE_PATHFINDER
  284. logAi->trace("Evaluated %s, loss: %f, turns: %f, gold: %d, army gain: %d, result %f",
  285. task->name(),
  286. armyLossPersentage,
  287. task->evaluationContext.movementCost,
  288. goldReward,
  289. armyReward,
  290. result);
  291. #endif
  292. return result;
  293. }