File: FuzzyEngines.cpp

package info (click to toggle)
vcmi 1.6.5%2Bdfsg-2
  • links: PTS, VCS
  • area: contrib
  • in suites: forky, sid, trixie
  • size: 32,060 kB
  • sloc: cpp: 238,971; python: 265; sh: 224; xml: 157; ansic: 78; objc: 61; makefile: 49
file content (239 lines) | stat: -rw-r--r-- 7,951 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
/*
* FuzzyEngines.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
*
*/
#include "../StdInc.h"
#include "FuzzyEngines.h"
#include "../Goals/Goals.h"

#include "../../../lib/mapObjects/MapObjects.h"
#include "../AIGateway.h"

namespace NKAI
{

constexpr float MIN_AI_STRENGTH = 0.5f; //lower when combat AI gets smarter

engineBase::engineBase()
{
	rules = new fl::RuleBlock();
	engine.addRuleBlock(rules);
}

void engineBase::configure()
{
	engine.configure("Minimum", "Maximum", "Minimum", "AlgebraicSum", "Centroid", "Proportional");
	logAi->trace(engine.toString());
}

void engineBase::addRule(const std::string & txt)
{
	rules->addRule(fl::Rule::parse(txt, &engine));
}

struct armyStructure
{
	float walkers;
	float shooters;
	float flyers;
	ui32 maxSpeed;
};

armyStructure evaluateArmyStructure(const CArmedInstance * army)
{
	ui64 totalStrength = army->getArmyStrength();
	double walkersStrength = 0;
	double flyersStrength = 0;
	double shootersStrength = 0;
	ui32 maxSpeed = 0;

	static const CSelector selectorSHOOTER = Selector::type()(BonusType::SHOOTER);
	static const std::string keySHOOTER = "type_"+std::to_string((int32_t)BonusType::SHOOTER);

	static const CSelector selectorFLYING = Selector::type()(BonusType::FLYING);
	static const std::string keyFLYING = "type_"+std::to_string((int32_t)BonusType::FLYING);

	static const CSelector selectorSTACKS_SPEED = Selector::type()(BonusType::STACKS_SPEED);
	static const std::string keySTACKS_SPEED = "type_"+std::to_string((int32_t)BonusType::STACKS_SPEED);

	for(auto s : army->Slots())
	{
		bool walker = true;
		auto bearer = s.second->getType()->getBonusBearer();
		if(bearer->hasBonus(selectorSHOOTER, keySHOOTER))
		{
			shootersStrength += s.second->getPower();
			walker = false;
		}
		if(bearer->hasBonus(selectorFLYING, keyFLYING))
		{
			flyersStrength += s.second->getPower();
			walker = false;
		}
		if(walker)
			walkersStrength += s.second->getPower();

		vstd::amax(maxSpeed, bearer->valOfBonuses(selectorSTACKS_SPEED, keySTACKS_SPEED));
	}
	armyStructure as;
	as.walkers = static_cast<float>(walkersStrength / totalStrength);
	as.shooters = static_cast<float>(shootersStrength / totalStrength);
	as.flyers = static_cast<float>(flyersStrength / totalStrength);
	as.maxSpeed = maxSpeed;
	assert(as.walkers || as.flyers || as.shooters);
	return as;
}

TacticalAdvantageEngine::TacticalAdvantageEngine()
{
	try
	{
		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)
		{
			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);
		}

		ourSpeed = new fl::InputVariable("OurSpeed");
		enemySpeed = new fl::InputVariable("EnemySpeed");

		helper = { ourSpeed, enemySpeed };

		for(auto val : helper)
		{
			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);
		}

		castleWalls = new fl::InputVariable("CastleWalls");
		engine.addInputVariable(castleWalls);
		{
			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);
		}


		bankPresent = new fl::InputVariable("Bank");
		engine.addInputVariable(bankPresent);
		{
			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);
		}

		threat = new fl::OutputVariable("Threat");
		engine.addOutputVariable(threat);
		threat->addTerm(new fl::Ramp("LOW", 1, MIN_AI_STRENGTH));
		threat->addTerm(new fl::Triangle("MEDIUM", 0.8, 1.2));
		threat->addTerm(new fl::Ramp("HIGH", 1, 1.5));
		threat->setRange(MIN_AI_STRENGTH, 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 LOW");
		addRule("if OurShooters is MANY and EnemySpeed is HIGH then Threat is 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 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 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();
}

float TacticalAdvantageEngine::getTacticalAdvantage(const CArmedInstance * we, const CArmedInstance * enemy)
{
	float output = 1;
	/*try //TODO: rework this engine, it tends to produce nonsense output
	{
		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);

		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))
	{
		fl::InputVariable * tab[] = { bankPresent, castleWalls, ourWalkers, ourShooters, ourFlyers, ourSpeed, enemyWalkers, enemyShooters, enemyFlyers, enemySpeed };
		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++)
			log << names[i] << ": " << tab[i]->getValue() << " ";
		logAi->error(log.str());
		assert(false);
	}*/

	return output;
}

}