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