PriorityEvaluator.cpp 12 KB

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