2017-07-13 11:26:03 +03:00
/*
* Fuzzy . 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
*
*/
2012-03-03 10:08:01 +00:00
# include "StdInc.h"
# include "Fuzzy.h"
2012-03-12 20:11:46 +00:00
# include <limits>
2012-09-06 10:39:48 +00:00
2014-06-05 19:52:14 +03:00
# include "../../lib/mapObjects/MapObjects.h"
2014-06-22 13:39:40 +03:00
# include "../../lib/mapObjects/CommonConstructors.h"
2012-09-06 10:39:48 +00:00
# include "../../lib/CCreatureHandler.h"
2015-12-02 21:39:53 +02:00
# include "../../lib/CPathfinder.h"
# include "../../lib/CGameStateFwd.h"
2012-09-06 10:39:48 +00:00
# include "../../lib/VCMI_Lib.h"
2013-12-20 09:43:12 +00:00
# include "../../CCallback.h"
# include "VCAI.h"
2018-07-27 02:28:12 +02:00
# include "MapObjectsEvaluator.h"
2012-03-03 10:08:01 +00:00
2012-03-04 10:43:46 +00:00
# define MIN_AI_STRENGHT (0.5f) //lower when combat AI gets smarter
2014-11-22 18:57:17 +01:00
# define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us
2012-03-04 10:43:46 +00:00
2012-05-28 19:29:32 +00:00
struct BankConfig ;
2014-12-10 12:29:51 +01:00
class CBankInfo ;
2014-10-25 15:51:53 +02:00
class Engine ;
class InputVariable ;
2012-03-04 10:43:46 +00:00
class CGTownInstance ;
2018-04-07 15:44:14 +07:00
FuzzyHelper * fh ;
2012-03-03 10:08:01 +00:00
2013-12-20 09:43:12 +00:00
extern boost : : thread_specific_ptr < CCallback > cb ;
extern boost : : thread_specific_ptr < VCAI > ai ;
2014-12-19 10:52:41 +01:00
engineBase : : engineBase ( )
{
engine . addRuleBlock ( & rules ) ;
}
void engineBase : : configure ( )
{
2017-03-25 00:01:40 +13:00
engine . configure ( " Minimum " , " Maximum " , " Minimum " , " AlgebraicSum " , " Centroid " , " General " ) ;
2016-08-13 17:44:37 +03:00
logAi - > info ( engine . toString ( ) ) ;
2014-12-19 10:52:41 +01:00
}
2018-04-07 15:44:14 +07:00
void engineBase : : addRule ( const std : : string & txt )
2014-12-19 10:52:41 +01:00
{
rules . addRule ( fl : : Rule : : parse ( txt , & engine ) ) ;
}
2012-03-04 10:43:46 +00:00
struct armyStructure
{
float walkers , shooters , flyers ;
ui32 maxSpeed ;
} ;
2018-04-07 15:44:14 +07:00
armyStructure evaluateArmyStructure ( const CArmedInstance * army )
2012-03-04 10:43:46 +00:00
{
ui64 totalStrenght = army - > getArmyStrength ( ) ;
double walkersStrenght = 0 ;
double flyersStrenght = 0 ;
double shootersStrenght = 0 ;
ui32 maxSpeed = 0 ;
2013-06-29 13:05:48 +00:00
for ( auto s : army - > Slots ( ) )
2012-03-04 10:43:46 +00:00
{
bool walker = true ;
2018-04-07 15:44:14 +07:00
if ( s . second - > type - > hasBonusOfType ( Bonus : : SHOOTER ) )
2012-03-04 10:43:46 +00:00
{
shootersStrenght + = s . second - > getPower ( ) ;
walker = false ;
}
2018-04-07 15:44:14 +07:00
if ( s . second - > type - > hasBonusOfType ( Bonus : : FLYING ) )
2012-03-04 10:43:46 +00:00
{
flyersStrenght + = s . second - > getPower ( ) ;
walker = false ;
}
2018-04-07 15:44:14 +07:00
if ( walker )
2012-03-04 10:43:46 +00:00
walkersStrenght + = s . second - > getPower ( ) ;
2015-12-29 02:14:08 +03:00
vstd : : amax ( maxSpeed , s . second - > type - > valOfBonuses ( Bonus : : STACKS_SPEED ) ) ;
2012-03-04 10:43:46 +00:00
}
armyStructure as ;
as . walkers = walkersStrenght / totalStrenght ;
as . shooters = shootersStrenght / totalStrenght ;
as . flyers = flyersStrenght / totalStrenght ;
as . maxSpeed = maxSpeed ;
2014-12-19 10:52:41 +01:00
assert ( as . walkers | | as . flyers | | as . shooters ) ;
2012-03-04 10:43:46 +00:00
return as ;
}
2018-08-04 18:58:50 +02:00
float HeroMovementGoalEngineBase : : calculateTurnDistanceInputValue ( const CGHeroInstance * h , int3 tile ) const
2018-07-27 02:21:51 +02:00
{
float turns = 0.0f ;
float distance = CPathfinderHelper : : getMovementCost ( h , tile ) ;
if ( distance )
{
if ( distance < h - > movement ) //we can move there within one turn
turns = ( fl : : scalar ) distance / h - > movement ;
else
turns = 1 + ( fl : : scalar ) ( distance - h - > movement ) / h - > maxMovePoints ( true ) ; //bool on land?
}
return turns ;
}
2018-04-07 15:44:14 +07:00
ui64 FuzzyHelper : : estimateBankDanger ( const CBank * bank )
2012-03-03 10:08:01 +00:00
{
2014-12-10 12:29:51 +01:00
//this one is not fuzzy anymore, just calculate weighted average
2014-06-22 13:39:40 +03:00
2014-12-10 12:29:51 +01:00
auto objectInfo = VLC - > objtypeh - > getHandlerFor ( bank - > ID , bank - > subID ) - > getObjectInfo ( bank - > appearance ) ;
2014-06-22 13:39:40 +03:00
2018-04-07 15:44:14 +07:00
CBankInfo * bankInfo = dynamic_cast < CBankInfo * > ( objectInfo . get ( ) ) ;
2014-12-10 12:29:51 +01:00
ui64 totalStrength = 0 ;
ui8 totalChance = 0 ;
2018-04-07 15:44:14 +07:00
for ( auto config : bankInfo - > getPossibleGuards ( ) )
2012-03-04 10:43:46 +00:00
{
2014-12-10 12:29:51 +01:00
totalStrength + = config . second . totalStrength * config . first ;
totalChance + = config . first ;
2012-03-03 10:08:01 +00:00
}
2016-11-25 19:54:28 +01:00
return totalStrength / std : : max < ui8 > ( totalChance , 1 ) ; //avoid division by zero
2012-03-03 10:08:01 +00:00
2012-03-04 10:43:46 +00:00
}
2018-08-05 19:03:25 +02:00
TacticalAdvantageEngine : : TacticalAdvantageEngine ( )
2018-08-05 15:50:56 +02:00
{
try
{
2018-08-06 19:38:39 +02:00
ourShooters = new fl : : InputVariable ( " OurShooters " ) ;
ourWalkers = new fl : : InputVariable ( " OurWalkers " ) ;
ourFlyers = new fl : : InputVariable ( " OurFlyers " ) ;
enemyShooters = new fl : : InputVariable ( " EnemyShooters " ) ;
enemyWalkers = new fl : : InputVariable ( " EnemyWalkers " ) ;
enemyFlyers = new fl : : InputVariable ( " EnemyFlyers " ) ;
//Tactical advantage calculation
std : : vector < fl : : InputVariable * > helper =
{
ourShooters , ourWalkers , ourFlyers , enemyShooters , enemyWalkers , enemyFlyers
} ;
for ( auto val : helper )
2018-08-05 15:50:56 +02:00
{
engine . addInputVariable ( val ) ;
val - > addTerm ( new fl : : Ramp ( " FEW " , 0.6 , 0.0 ) ) ;
val - > addTerm ( new fl : : Ramp ( " MANY " , 0.4 , 1 ) ) ;
val - > setRange ( 0.0 , 1.0 ) ;
}
2018-08-06 19:38:39 +02:00
ourSpeed = new fl : : InputVariable ( " OurSpeed " ) ;
enemySpeed = new fl : : InputVariable ( " EnemySpeed " ) ;
2018-08-05 15:50:56 +02:00
2018-08-06 19:38:39 +02:00
helper = { ourSpeed , enemySpeed } ;
for ( auto val : helper )
2018-08-05 15:50:56 +02:00
{
engine . addInputVariable ( val ) ;
val - > addTerm ( new fl : : Ramp ( " LOW " , 6.5 , 3 ) ) ;
val - > addTerm ( new fl : : Triangle ( " MEDIUM " , 5.5 , 10.5 ) ) ;
val - > addTerm ( new fl : : Ramp ( " HIGH " , 8.5 , 16 ) ) ;
val - > setRange ( 0 , 25 ) ;
}
2018-08-06 19:38:39 +02:00
castleWalls = new fl : : InputVariable ( " CastleWalls " ) ;
engine . addInputVariable ( castleWalls ) ;
2018-08-05 15:50:56 +02:00
{
fl : : Rectangle * none = new fl : : Rectangle ( " NONE " , CGTownInstance : : NONE , CGTownInstance : : NONE + ( CGTownInstance : : FORT - CGTownInstance : : NONE ) * 0.5f ) ;
castleWalls - > addTerm ( none ) ;
fl : : Trapezoid * medium = new fl : : Trapezoid ( " MEDIUM " , ( CGTownInstance : : FORT - CGTownInstance : : NONE ) * 0.5f , CGTownInstance : : FORT ,
CGTownInstance : : CITADEL , CGTownInstance : : CITADEL + ( CGTownInstance : : CASTLE - CGTownInstance : : CITADEL ) * 0.5f ) ;
castleWalls - > addTerm ( medium ) ;
fl : : Ramp * high = new fl : : Ramp ( " HIGH " , CGTownInstance : : CITADEL - 0.1 , CGTownInstance : : CASTLE ) ;
castleWalls - > addTerm ( high ) ;
castleWalls - > setRange ( CGTownInstance : : NONE , CGTownInstance : : CASTLE ) ;
}
2018-08-06 19:38:39 +02:00
bankPresent = new fl : : InputVariable ( " Bank " ) ;
engine . addInputVariable ( bankPresent ) ;
2018-08-05 15:50:56 +02:00
{
fl : : Rectangle * termFalse = new fl : : Rectangle ( " FALSE " , 0.0 , 0.5f ) ;
bankPresent - > addTerm ( termFalse ) ;
fl : : Rectangle * termTrue = new fl : : Rectangle ( " TRUE " , 0.5f , 1 ) ;
bankPresent - > addTerm ( termTrue ) ;
bankPresent - > setRange ( 0 , 1 ) ;
}
2018-08-06 19:38:39 +02:00
threat = new fl : : OutputVariable ( " Threat " ) ;
engine . addOutputVariable ( threat ) ;
2018-08-05 15:50:56 +02:00
threat - > addTerm ( new fl : : Ramp ( " LOW " , 1 , MIN_AI_STRENGHT ) ) ;
threat - > addTerm ( new fl : : Triangle ( " MEDIUM " , 0.8 , 1.2 ) ) ;
threat - > addTerm ( new fl : : Ramp ( " HIGH " , 1 , 1.5 ) ) ;
threat - > setRange ( MIN_AI_STRENGHT , 1.5 ) ;
addRule ( " if OurShooters is MANY and EnemySpeed is LOW then Threat is LOW " ) ;
addRule ( " if OurShooters is MANY and EnemyShooters is FEW then Threat is LOW " ) ;
addRule ( " if OurSpeed is LOW and EnemyShooters is MANY then Threat is HIGH " ) ;
addRule ( " if OurSpeed is HIGH and EnemyShooters is MANY then Threat is LOW " ) ;
addRule ( " if OurWalkers is FEW and EnemyShooters is MANY then Threat is somewhat LOW " ) ;
addRule ( " if OurShooters is MANY and EnemySpeed is HIGH then Threat is somewhat HIGH " ) ;
//just to cover all cases
addRule ( " if OurShooters is FEW and EnemySpeed is HIGH then Threat is MEDIUM " ) ;
addRule ( " if EnemySpeed is MEDIUM then Threat is MEDIUM " ) ;
addRule ( " if EnemySpeed is LOW and OurShooters is FEW then Threat is MEDIUM " ) ;
addRule ( " if Bank is TRUE and OurShooters is MANY then Threat is somewhat HIGH " ) ;
addRule ( " if Bank is TRUE and EnemyShooters is MANY then Threat is LOW " ) ;
addRule ( " if CastleWalls is HIGH and OurWalkers is MANY then Threat is very HIGH " ) ;
addRule ( " if CastleWalls is HIGH and OurFlyers is MANY and OurShooters is MANY then Threat is MEDIUM " ) ;
addRule ( " if CastleWalls is MEDIUM and OurShooters is MANY and EnemyWalkers is MANY then Threat is LOW " ) ;
}
catch ( fl : : Exception & pe )
{
logAi - > error ( " initTacticalAdvantage: %s " , pe . getWhat ( ) ) ;
}
configure ( ) ;
}
2018-08-05 19:03:25 +02:00
float TacticalAdvantageEngine : : getTacticalAdvantage ( const CArmedInstance * we , const CArmedInstance * enemy )
{
float output = 1 ;
try
{
armyStructure ourStructure = evaluateArmyStructure ( we ) ;
armyStructure enemyStructure = evaluateArmyStructure ( enemy ) ;
ourWalkers - > setValue ( ourStructure . walkers ) ;
ourShooters - > setValue ( ourStructure . shooters ) ;
ourFlyers - > setValue ( ourStructure . flyers ) ;
ourSpeed - > setValue ( ourStructure . maxSpeed ) ;
enemyWalkers - > setValue ( enemyStructure . walkers ) ;
enemyShooters - > setValue ( enemyStructure . shooters ) ;
enemyFlyers - > setValue ( enemyStructure . flyers ) ;
enemySpeed - > setValue ( enemyStructure . maxSpeed ) ;
bool bank = dynamic_cast < const CBank * > ( enemy ) ;
if ( bank )
bankPresent - > setValue ( 1 ) ;
else
bankPresent - > setValue ( 0 ) ;
const CGTownInstance * fort = dynamic_cast < const CGTownInstance * > ( enemy ) ;
if ( fort )
castleWalls - > setValue ( fort - > fortLevel ( ) ) ;
else
castleWalls - > setValue ( 0 ) ;
engine . process ( ) ;
output = threat - > getValue ( ) ;
}
catch ( fl : : Exception & fe )
{
logAi - > error ( " getTacticalAdvantage: %s " , fe . getWhat ( ) ) ;
}
if ( output < 0 | | ( output ! = output ) )
{
2018-08-06 19:38:39 +02:00
fl : : InputVariable * tab [ ] = { bankPresent , castleWalls , ourWalkers , ourShooters , ourFlyers , ourSpeed , enemyWalkers , enemyShooters , enemyFlyers , enemySpeed } ;
2018-08-05 19:03:25 +02:00
std : : string names [ ] = { " bankPresent " , " castleWalls " , " ourWalkers " , " ourShooters " , " ourFlyers " , " ourSpeed " , " enemyWalkers " , " enemyShooters " , " enemyFlyers " , " enemySpeed " } ;
std : : stringstream log ( " Warning! Fuzzy engine doesn't cover this set of parameters: " ) ;
for ( int i = 0 ; i < boost : : size ( tab ) ; i + + )
2018-08-06 19:38:39 +02:00
log < < names [ i ] < < " : " < < tab [ i ] - > getValue ( ) < < " " ;
2018-08-05 19:03:25 +02:00
logAi - > error ( log . str ( ) ) ;
assert ( false ) ;
}
return output ;
}
2015-12-29 05:43:33 +03:00
//std::shared_ptr<AbstractGoal> chooseSolution (std::vector<std::shared_ptr<AbstractGoal>> & vec)
2014-10-25 15:51:53 +02:00
2018-04-07 15:44:14 +07:00
Goals : : TSubgoal FuzzyHelper : : chooseSolution ( Goals : : TGoalVec vec )
2013-11-25 11:55:48 +00:00
{
2018-04-07 15:44:14 +07:00
if ( vec . empty ( ) ) //no possibilities found
2013-12-20 09:43:12 +00:00
return sptr ( Goals : : Invalid ( ) ) ;
2015-10-26 16:38:17 +01:00
ai - > cachedSectorMaps . clear ( ) ;
2015-08-31 08:25:33 +02:00
2014-03-31 14:26:09 +00:00
//a trick to switch between heroes less often - calculatePaths is costly
auto sortByHeroes = [ ] ( const Goals : : TSubgoal & lhs , const Goals : : TSubgoal & rhs ) - > bool
{
return lhs - > hero . h < rhs - > hero . h ;
} ;
2018-04-07 15:44:14 +07:00
boost : : sort ( vec , sortByHeroes ) ;
2014-03-31 14:26:09 +00:00
2018-04-07 15:44:14 +07:00
for ( auto g : vec )
2013-11-25 11:55:48 +00:00
{
2013-12-27 13:20:40 +00:00
setPriority ( g ) ;
2013-11-25 11:55:48 +00:00
}
2013-12-28 12:47:55 +00:00
auto compareGoals = [ ] ( const Goals : : TSubgoal & lhs , const Goals : : TSubgoal & rhs ) - > bool
2013-11-25 11:55:48 +00:00
{
2013-12-25 16:05:11 +00:00
return lhs - > priority < rhs - > priority ;
2013-11-25 11:55:48 +00:00
} ;
2018-07-19 17:36:10 +02:00
return * boost : : max_element ( vec , compareGoals ) ;
2013-12-19 20:21:21 +00:00
}
2018-04-07 15:44:14 +07:00
float FuzzyHelper : : evaluate ( Goals : : Explore & g )
2013-12-19 20:21:21 +00:00
{
2013-12-20 09:43:12 +00:00
return 1 ;
2013-12-19 20:21:21 +00:00
}
2018-04-07 15:44:14 +07:00
float FuzzyHelper : : evaluate ( Goals : : RecruitHero & g )
2013-12-19 20:21:21 +00:00
{
2018-07-26 12:06:55 +02:00
return 1 ;
2013-12-20 09:43:12 +00:00
}
2018-08-04 18:58:50 +02:00
HeroMovementGoalEngineBase : : HeroMovementGoalEngineBase ( )
2013-12-20 09:43:12 +00:00
{
2014-02-05 18:45:51 +00:00
try
{
2018-08-06 19:38:39 +02:00
strengthRatio = new fl : : InputVariable ( " strengthRatio " ) ; //hero must be strong enough to defeat guards
heroStrength = new fl : : InputVariable ( " heroStrength " ) ; //we want to use weakest possible hero
turnDistance = new fl : : InputVariable ( " turnDistance " ) ; //we want to use hero who is near
missionImportance = new fl : : InputVariable ( " lockedMissionImportance " ) ; //we may want to preempt hero with low-priority mission
value = new fl : : OutputVariable ( " Value " ) ;
2018-08-04 18:58:50 +02:00
value - > setMinimum ( 0 ) ;
value - > setMaximum ( 5 ) ;
2018-08-06 19:38:39 +02:00
std : : vector < fl : : InputVariable * > helper = { strengthRatio , heroStrength , turnDistance , missionImportance } ;
for ( auto val : helper )
2014-02-05 18:45:51 +00:00
{
2018-08-04 18:58:50 +02:00
engine . addInputVariable ( val ) ;
2014-02-05 18:45:51 +00:00
}
2018-08-06 19:38:39 +02:00
engine . addOutputVariable ( value ) ;
2014-02-05 18:45:51 +00:00
2018-08-04 18:58:50 +02:00
strengthRatio - > addTerm ( new fl : : Ramp ( " LOW " , SAFE_ATTACK_CONSTANT , 0 ) ) ;
strengthRatio - > addTerm ( new fl : : Ramp ( " HIGH " , SAFE_ATTACK_CONSTANT , SAFE_ATTACK_CONSTANT * 3 ) ) ;
strengthRatio - > setRange ( 0 , SAFE_ATTACK_CONSTANT * 3 ) ;
2014-02-05 18:45:51 +00:00
//strength compared to our main hero
2018-08-04 18:58:50 +02:00
heroStrength - > addTerm ( new fl : : Ramp ( " LOW " , 0.2 , 0 ) ) ;
heroStrength - > addTerm ( new fl : : Triangle ( " MEDIUM " , 0.2 , 0.8 ) ) ;
heroStrength - > addTerm ( new fl : : Ramp ( " HIGH " , 0.5 , 1 ) ) ;
heroStrength - > setRange ( 0.0 , 1.0 ) ;
2014-02-05 18:45:51 +00:00
2018-08-04 18:58:50 +02:00
turnDistance - > addTerm ( new fl : : Ramp ( " SMALL " , 0.5 , 0 ) ) ;
turnDistance - > addTerm ( new fl : : Triangle ( " MEDIUM " , 0.1 , 0.8 ) ) ;
turnDistance - > addTerm ( new fl : : Ramp ( " LONG " , 0.5 , 3 ) ) ;
turnDistance - > setRange ( 0.0 , 3.0 ) ;
2014-02-05 18:45:51 +00:00
2018-08-04 18:58:50 +02:00
missionImportance - > addTerm ( new fl : : Ramp ( " LOW " , 2.5 , 0 ) ) ;
missionImportance - > addTerm ( new fl : : Triangle ( " MEDIUM " , 2 , 3 ) ) ;
missionImportance - > addTerm ( new fl : : Ramp ( " HIGH " , 2.5 , 5 ) ) ;
missionImportance - > setRange ( 0.0 , 5.0 ) ;
2014-02-05 18:45:51 +00:00
2014-02-07 08:23:31 +00:00
//an issue: in 99% cases this outputs center of mass (2.5) regardless of actual input :/
2018-04-07 15:44:14 +07:00
//should be same as "mission Importance" to keep consistency
2018-08-04 18:58:50 +02:00
value - > addTerm ( new fl : : Ramp ( " LOW " , 2.5 , 0 ) ) ;
value - > addTerm ( new fl : : Triangle ( " MEDIUM " , 2 , 3 ) ) ; //can't be center of mass :/
value - > addTerm ( new fl : : Ramp ( " HIGH " , 2.5 , 5 ) ) ;
value - > setRange ( 0.0 , 5.0 ) ;
2014-02-05 18:45:51 +00:00
//use unarmed scouts if possible
2018-08-04 18:58:50 +02:00
addRule ( " if strengthRatio is HIGH and heroStrength is LOW then Value is very HIGH " ) ;
2014-02-05 18:45:51 +00:00
//we may want to use secondary hero(es) rather than main hero
2018-08-04 18:58:50 +02:00
addRule ( " if strengthRatio is HIGH and heroStrength is MEDIUM then Value is somewhat HIGH " ) ;
addRule ( " if strengthRatio is HIGH and heroStrength is HIGH then Value is somewhat LOW " ) ;
2014-02-05 18:45:51 +00:00
//don't assign targets to heroes who are too weak, but prefer targets of our main hero (in case we need to gather army)
2018-08-04 18:58:50 +02:00
addRule ( " if strengthRatio is LOW and heroStrength is LOW then Value is very LOW " ) ;
2014-02-07 08:23:31 +00:00
//attempt to arm secondary heroes is not stupid
2018-08-04 18:58:50 +02:00
addRule ( " if strengthRatio is LOW and heroStrength is MEDIUM then Value is somewhat HIGH " ) ;
addRule ( " if strengthRatio is LOW and heroStrength is HIGH then Value is LOW " ) ;
2014-10-25 15:51:53 +02:00
2014-02-05 18:45:51 +00:00
//do not cancel important goals
2018-08-04 18:58:50 +02:00
addRule ( " if lockedMissionImportance is HIGH then Value is very LOW " ) ;
addRule ( " if lockedMissionImportance is MEDIUM then Value is somewhat LOW " ) ;
addRule ( " if lockedMissionImportance is LOW then Value is HIGH " ) ;
2014-02-05 18:45:51 +00:00
//pick nearby objects if it's easy, avoid long walks
2018-08-04 18:58:50 +02:00
addRule ( " if turnDistance is SMALL then Value is HIGH " ) ;
addRule ( " if turnDistance is MEDIUM then Value is MEDIUM " ) ;
addRule ( " if turnDistance is LONG then Value is LOW " ) ;
2014-02-05 18:45:51 +00:00
}
2018-04-07 15:44:14 +07:00
catch ( fl : : Exception & fe )
2013-12-20 09:43:12 +00:00
{
2018-08-04 18:58:50 +02:00
logAi - > error ( " HeroMovementGoalEngineBase: %s " , fe . getWhat ( ) ) ;
2013-12-20 09:43:12 +00:00
}
2013-12-19 20:21:21 +00:00
}
2014-10-25 15:51:53 +02:00
2018-08-05 20:30:08 +02:00
void HeroMovementGoalEngineBase : : setSharedFuzzyVariables ( Goals : : AbstractGoal & goal )
{
float turns = calculateTurnDistanceInputValue ( goal . hero . h , goal . tile ) ;
float missionImportanceData = 0 ;
if ( vstd : : contains ( ai - > lockedHeroes , goal . hero ) )
missionImportanceData = ai - > lockedHeroes [ goal . hero ] - > priority ;
float strengthRatioData = 10.0f ; //we are much stronger than enemy
ui64 danger = evaluateDanger ( goal . tile , goal . hero . h ) ;
if ( danger )
strengthRatioData = ( fl : : scalar ) goal . hero . h - > getTotalStrength ( ) / danger ;
try
{
strengthRatio - > setValue ( strengthRatioData ) ;
heroStrength - > setValue ( ( fl : : scalar ) goal . hero - > getTotalStrength ( ) / ai - > primaryHero ( ) - > getTotalStrength ( ) ) ;
turnDistance - > setValue ( turns ) ;
missionImportance - > setValue ( missionImportanceData ) ;
}
catch ( fl : : Exception & fe )
{
logAi - > error ( " HeroMovementGoalEngineBase::setSharedFuzzyVariables: %s " , fe . getWhat ( ) ) ;
}
}
2018-04-07 15:44:14 +07:00
float FuzzyHelper : : evaluate ( Goals : : VisitTile & g )
2013-12-19 20:21:21 +00:00
{
2018-08-05 19:03:25 +02:00
return visitTileEngine . evaluate ( g ) ;
2013-12-19 20:21:21 +00:00
}
2018-08-05 20:30:08 +02:00
float FuzzyHelper : : evaluate ( Goals : : GetObj & g )
{
return getObjEngine . evaluate ( g ) ;
}
2018-04-07 15:44:14 +07:00
float FuzzyHelper : : evaluate ( Goals : : VisitHero & g )
2013-12-19 20:21:21 +00:00
{
2014-03-23 12:59:03 +00:00
auto obj = cb - > getObj ( ObjectInstanceID ( g . objid ) ) ; //we assume for now that these goals are similar
2018-04-07 15:44:14 +07:00
if ( ! obj )
2014-02-17 17:28:39 +00:00
return - 100 ; //hero died in the meantime
2013-12-20 13:01:44 +00:00
//TODO: consider direct copy (constructor?)
2013-12-27 13:20:40 +00:00
g . setpriority ( Goals : : VisitTile ( obj - > visitablePos ( ) ) . sethero ( g . hero ) . setisAbstract ( g . isAbstract ) . accept ( this ) ) ;
2016-08-13 17:44:37 +03:00
return g . priority ;
2013-12-19 20:21:21 +00:00
}
2018-04-07 15:44:14 +07:00
float FuzzyHelper : : evaluate ( Goals : : GatherArmy & g )
2014-02-07 20:09:15 +00:00
{
//the more army we need, the more important goal
//the more army we lack, the less important goal
float army = g . hero - > getArmyStrength ( ) ;
2018-04-07 15:44:14 +07:00
float ratio = g . value / std : : max ( g . value - army , 2000.0f ) ; //2000 is about the value of hero recruited from tavern
2017-06-16 21:14:24 +02:00
return 5 * ( ratio / ( ratio + 2 ) ) ; //so 50% army gives 2.5, asymptotic 5
2014-02-07 20:09:15 +00:00
}
2014-02-20 20:18:49 +00:00
2018-04-07 15:44:14 +07:00
float FuzzyHelper : : evaluate ( Goals : : ClearWayTo & g )
2014-02-20 20:18:49 +00:00
{
2018-04-07 15:44:14 +07:00
if ( ! g . hero . h )
2014-02-20 20:18:49 +00:00
throw cannotFulfillGoalException ( " ClearWayTo called without hero! " ) ;
2015-12-04 00:10:51 +02:00
int3 t = ai - > getCachedSectorMap ( g . hero ) - > firstTileToGet ( g . hero , g . tile ) ;
2014-02-20 20:18:49 +00:00
2018-04-07 15:44:14 +07:00
if ( t . valid ( ) )
2014-02-20 20:18:49 +00:00
{
2018-04-07 15:44:14 +07:00
if ( isSafeToVisit ( g . hero , t ) )
2014-02-20 20:18:49 +00:00
{
g . setpriority ( Goals : : VisitTile ( g . tile ) . sethero ( g . hero ) . setisAbstract ( g . isAbstract ) . accept ( this ) ) ;
}
else
{
g . setpriority ( Goals : : GatherArmy ( evaluateDanger ( t , g . hero . h ) * SAFE_ATTACK_CONSTANT ) .
sethero ( g . hero ) . setisAbstract ( true ) . accept ( this ) ) ;
}
return g . priority ;
}
else
return - 1 ;
2014-11-22 18:57:17 +01:00
2014-02-20 20:18:49 +00:00
}
2018-04-07 15:44:14 +07:00
float FuzzyHelper : : evaluate ( Goals : : BuildThis & g )
2013-12-19 20:21:21 +00:00
{
2018-07-26 12:06:55 +02:00
return g . priority ; //TODO
2013-12-19 20:21:21 +00:00
}
2018-04-07 15:44:14 +07:00
float FuzzyHelper : : evaluate ( Goals : : DigAtTile & g )
2013-12-19 20:21:21 +00:00
{
return 0 ;
}
2018-04-07 15:44:14 +07:00
float FuzzyHelper : : evaluate ( Goals : : CollectRes & g )
2013-12-19 20:21:21 +00:00
{
2018-07-26 12:06:55 +02:00
return g . priority ; //handled by ResourceManager
2013-12-19 20:21:21 +00:00
}
2018-04-07 15:44:14 +07:00
float FuzzyHelper : : evaluate ( Goals : : Build & g )
2013-12-19 20:21:21 +00:00
{
return 0 ;
}
2018-07-26 12:06:55 +02:00
float FuzzyHelper : : evaluate ( Goals : : BuyArmy & g )
{
return g . priority ;
}
2018-04-07 15:44:14 +07:00
float FuzzyHelper : : evaluate ( Goals : : Invalid & g )
2013-12-19 20:21:21 +00:00
{
return - 1e10 ;
}
2018-04-07 15:44:14 +07:00
float FuzzyHelper : : evaluate ( Goals : : AbstractGoal & g )
2013-12-19 20:21:21 +00:00
{
2016-08-13 17:44:37 +03:00
logAi - > warn ( " Cannot evaluate goal %s " , g . name ( ) ) ;
2013-12-26 09:53:37 +00:00
return g . priority ;
2013-12-20 13:07:58 +00:00
}
2018-07-26 12:06:55 +02:00
void FuzzyHelper : : setPriority ( Goals : : TSubgoal & g ) //calls evaluate - Visitor pattern
2013-12-27 13:20:40 +00:00
{
g - > setpriority ( g - > accept ( this ) ) ; //this enforces returned value is set
2015-11-10 02:15:27 +03:00
}
2018-07-27 02:28:12 +02:00
2018-08-05 20:30:08 +02:00
GetObjEngine : : GetObjEngine ( )
2018-08-04 18:58:50 +02:00
{
try
{
2018-08-06 19:38:39 +02:00
objectValue = new fl : : InputVariable ( " objectValue " ) ; //value of that object type known by AI
2018-08-04 18:58:50 +02:00
2018-08-06 19:38:39 +02:00
engine . addInputVariable ( objectValue ) ;
2018-08-04 18:58:50 +02:00
//objectValue ranges are based on checking RMG priorities of some objects and trying to guess sane value ranges
objectValue - > addTerm ( new fl : : Ramp ( " LOW " , 3000 , 0 ) ) ; //I have feeling that concave shape might work well instead of ramp for objectValue FL terms
objectValue - > addTerm ( new fl : : Triangle ( " MEDIUM " , 2500 , 6000 ) ) ;
objectValue - > addTerm ( new fl : : Ramp ( " HIGH " , 5000 , 20000 ) ) ;
objectValue - > setRange ( 0 , 20000 ) ; //relic artifact value is border value by design, even better things are scaled down.
addRule ( " if turnDistance is LONG and objectValue is HIGH then value is MEDIUM " ) ;
addRule ( " if turnDistance is MEDIUM and objectValue is HIGH then value is somewhat HIGH " ) ;
addRule ( " if turnDistance is SHORT and objectValue is HIGH then value is HIGH " ) ;
addRule ( " if turnDistance is LONG and objectValue is MEDIUM then value is somewhat LOW " ) ;
addRule ( " if turnDistance is MEDIUM and objectValue is MEDIUM then value is MEDIUM " ) ;
addRule ( " if turnDistance is SHORT and objectValue is MEDIUM then value is somewhat HIGH " ) ;
addRule ( " if turnDistance is LONG and objectValue is LOW then value is very LOW " ) ;
addRule ( " if turnDistance is MEDIUM and objectValue is LOW then value is LOW " ) ;
addRule ( " if turnDistance is SHORT and objectValue is LOW then value is MEDIUM " ) ;
}
catch ( fl : : Exception & fe )
{
logAi - > error ( " FindWanderTarget: %s " , fe . getWhat ( ) ) ;
}
configure ( ) ;
}
2018-08-05 20:30:08 +02:00
float GetObjEngine : : evaluate ( Goals : : AbstractGoal & goal )
{
auto g = dynamic_cast < Goals : : GetObj & > ( goal ) ;
if ( ! g . hero )
return 0 ;
auto obj = cb - > getObj ( ObjectInstanceID ( g . objid ) ) ;
boost : : optional < int > objValueKnownByAI = MapObjectsEvaluator : : getInstance ( ) . getObjectValue ( obj - > ID , obj - > subID ) ;
int objValue = 0 ;
if ( objValueKnownByAI ! = boost : : none ) //consider adding value manipulation based on object instances on map
{
objValue = std : : min ( std : : max ( objValueKnownByAI . get ( ) , 0 ) , 20000 ) ;
}
else
{
MapObjectsEvaluator : : getInstance ( ) . addObjectData ( obj - > ID , obj - > subID , 0 ) ;
logGlobal - > warn ( " AI met object type it doesn't know - ID: " + std : : to_string ( obj - > ID ) + " , subID: " + std : : to_string ( obj - > subID ) + " - adding to database with value " + std : : to_string ( objValue ) ) ;
}
setSharedFuzzyVariables ( goal ) ;
float output = - 1.0f ;
try
{
objectValue - > setValue ( objValue ) ;
engine . process ( ) ;
output = value - > getValue ( ) ;
}
catch ( fl : : Exception & fe )
{
logAi - > error ( " evaluate getWanderTargetObjectValue: %s " , fe . getWhat ( ) ) ;
}
assert ( output > = 0.0f ) ;
return output ;
}
2018-08-05 19:03:25 +02:00
VisitTileEngine : : VisitTileEngine ( ) //so far no VisitTile-specific variables that are not shared with HeroMovementGoalEngineBase
2018-08-04 18:58:50 +02:00
{
configure ( ) ;
}
2018-08-05 19:03:25 +02:00
float VisitTileEngine : : evaluate ( Goals : : AbstractGoal & goal )
{
auto g = dynamic_cast < Goals : : VisitTile & > ( goal ) ;
//we assume that hero is already set and we want to choose most suitable one for the mission
if ( ! g . hero )
return 0 ;
//assert(cb->isInTheMap(g.tile));
2018-08-05 20:30:08 +02:00
setSharedFuzzyVariables ( goal ) ;
2018-08-05 19:03:25 +02:00
try
{
engine . process ( ) ;
g . priority = value - > getValue ( ) ;
}
catch ( fl : : Exception & fe )
{
logAi - > error ( " evaluate VisitTile: %s " , fe . getWhat ( ) ) ;
}
assert ( g . priority > = 0 ) ;
return g . priority ;
}