1 /*
2 * Copyright (C) 2009-2020 by the Widelands Development Team
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 */
19
20 #include "ai/ai_help_structs.h"
21
22 #include <algorithm>
23
24 #include "base/macros.h"
25 #include "base/time_string.h"
26 #include "logic/ai_dna_handler.h"
27 #include "logic/map.h"
28 #include "logic/player.h"
29
30 namespace Widelands {
31
32 constexpr int kNoAiTrainingMutation = 200;
33 constexpr int kUpperDefaultMutationLimit = 150;
34 constexpr int kLowerDefaultMutationLimit = 75;
35
36 // CheckStepRoadAI
CheckStepRoadAI(Player * const pl,uint8_t const mc,bool const oe)37 CheckStepRoadAI::CheckStepRoadAI(Player* const pl, uint8_t const mc, bool const oe)
38 : player(pl), movecaps(mc), open_end(oe) {
39 }
40
allowed(const Map & map,FCoords start,FCoords end,int32_t,CheckStep::StepId const id) const41 bool CheckStepRoadAI::allowed(
42 const Map& map, FCoords start, FCoords end, int32_t, CheckStep::StepId const id) const {
43 const uint8_t endcaps = player->get_buildcaps(end);
44
45 // we should not cross fields with road or flags (or any other immovable)
46 if ((map.get_immovable(start)) && !(id == CheckStep::stepFirst)) {
47 return false;
48 }
49
50 // Calculate cost and passability
51 if (!(endcaps & movecaps)) {
52 return false;
53 }
54
55 // Check for blocking immovables
56 if (BaseImmovable const* const imm = map.get_immovable(end)) {
57 if (imm->get_size() >= BaseImmovable::SMALL) {
58 if (id != CheckStep::stepLast && !open_end) {
59 return false;
60 }
61 if (dynamic_cast<Flag const*>(imm)) {
62 return true;
63 }
64 if (!dynamic_cast<Road const*>(imm) || !(endcaps & BUILDCAPS_FLAG)) {
65 return false;
66 }
67 }
68 }
69 return true;
70 }
71
reachable_dest(const Map & map,const FCoords & dest) const72 bool CheckStepRoadAI::reachable_dest(const Map& map, const FCoords& dest) const {
73 NodeCaps const caps = dest.field->nodecaps();
74
75 if (!(caps & movecaps)) {
76 if (!((movecaps & MOVECAPS_SWIM) && (caps & MOVECAPS_WALK))) {
77 return false;
78 }
79 if (!map.can_reach_by_water(dest)) {
80 return false;
81 }
82 }
83
84 return true;
85 }
86
87 // CheckStepOwnTerritory
CheckStepOwnTerritory(Player * const pl,uint8_t const mc,bool const oe)88 CheckStepOwnTerritory::CheckStepOwnTerritory(Player* const pl, uint8_t const mc, bool const oe)
89 : player(pl), movecaps(mc), open_end(oe) {
90 }
91
92 // Defines when movement is allowed:
93 // 1. startfield is walkable (or it is the first step)
94 // And endfield either:
95 // 2a. is walkable
96 // 2b. has our PlayerImmovable (building or flag)
allowed(const Map & map,FCoords start,FCoords end,int32_t,CheckStep::StepId const id) const97 bool CheckStepOwnTerritory::allowed(
98 const Map& map, FCoords start, FCoords end, int32_t, CheckStep::StepId const id) const {
99 const uint8_t endcaps = player->get_buildcaps(end);
100 const uint8_t startcaps = player->get_buildcaps(start);
101
102 // We should not cross fields with road or flags (or any other immovable)
103 // Or rather we can step on it, but not go on from such field
104 if ((map.get_immovable(start)) && !(id == CheckStep::stepFirst)) {
105 return false;
106 }
107
108 // Start field must be walkable
109 if (!(startcaps & movecaps)) {
110 return false;
111 }
112
113 // Endfield can not be water
114 if (endcaps & MOVECAPS_SWIM) {
115 return false;
116 }
117
118 return true;
119 }
120
121 // We accept either walkable territory or field with own immovable
reachable_dest(const Map & map,const FCoords & dest) const122 bool CheckStepOwnTerritory::reachable_dest(const Map& map, const FCoords& dest) const {
123 const uint8_t endcaps = player->get_buildcaps(dest);
124 if (BaseImmovable const* const imm = map.get_immovable(dest)) {
125 if (imm->descr().type() >= MapObjectType::FLAG) {
126 return true;
127 } else {
128 return false;
129 }
130 }
131 if (endcaps & MOVECAPS_WALK) {
132 return true;
133 }
134 return false;
135 }
136
137 // We are looking for fields we can walk on
138 // and owned by hostile player.
FindNodeEnemy(Player * p,Game & g)139 FindNodeEnemy::FindNodeEnemy(Player* p, Game& g) : player(p), game(g) {
140 }
141
accept(const EditorGameBase &,const FCoords & fc) const142 bool FindNodeEnemy::accept(const EditorGameBase&, const FCoords& fc) const {
143 return (fc.field->nodecaps() & MOVECAPS_WALK) && fc.field->get_owned_by() != 0 &&
144 player->is_hostile(*game.get_player(fc.field->get_owned_by()));
145 }
146
147 // We are looking for buildings owned by hostile player
148 // (sometimes there is a enemy's teritorry without buildings, and
149 // this confuses the AI)
FindNodeEnemiesBuilding(Player * p,Game & g)150 FindNodeEnemiesBuilding::FindNodeEnemiesBuilding(Player* p, Game& g) : player(p), game(g) {
151 }
152
accept(const EditorGameBase &,const FCoords & fc) const153 bool FindNodeEnemiesBuilding::accept(const EditorGameBase&, const FCoords& fc) const {
154 return (fc.field->get_immovable()) && fc.field->get_owned_by() != 0 &&
155 player->is_hostile(*game.get_player(fc.field->get_owned_by()));
156 }
157
158 // When looking for unowned terrain to acquire, we are actually
159 // only interested in fields we can walk on.
160 // Fields should either be completely unowned or owned by an opposing player
FindEnemyNodeWalkable(Player * p,Game & g)161 FindEnemyNodeWalkable::FindEnemyNodeWalkable(Player* p, Game& g) : player(p), game(g) {
162 }
163
accept(const EditorGameBase &,const FCoords & fc) const164 bool FindEnemyNodeWalkable::accept(const EditorGameBase&, const FCoords& fc) const {
165 return ((fc.field->nodecaps() & MOVECAPS_WALK) && (fc.field->get_owned_by() > 0) &&
166 player->is_hostile(*game.get_player(fc.field->get_owned_by())));
167 }
168
169 // Sometimes we need to know how many nodes our allies owns
FindNodeAllyOwned(Player * p,Game & g,PlayerNumber n)170 FindNodeAllyOwned::FindNodeAllyOwned(Player* p, Game& g, PlayerNumber n)
171 : player(p), game(g), player_number(n) {
172 }
173
accept(const EditorGameBase &,const FCoords & fc) const174 bool FindNodeAllyOwned::accept(const EditorGameBase&, const FCoords& fc) const {
175 return (fc.field->nodecaps() & MOVECAPS_WALK) && (fc.field->get_owned_by() != 0) &&
176 (fc.field->get_owned_by() != player_number) &&
177 !player->is_hostile(*game.get_player(fc.field->get_owned_by()));
178 }
179
180 // When looking for unowned terrain to acquire, we must
181 // pay speciall attention to fields where mines can be built.
182 // Fields should be completely unowned
FindNodeUnownedMineable(Player * p,Game & g,int32_t t)183 FindNodeUnownedMineable::FindNodeUnownedMineable(Player* p, Game& g, int32_t t)
184 : player(p), game(g), ore_type(t) {
185 }
186
accept(const EditorGameBase &,const FCoords & fc) const187 bool FindNodeUnownedMineable::accept(const EditorGameBase&, const FCoords& fc) const {
188 if (ore_type == INVALID_INDEX) {
189 return (fc.field->nodecaps() & BUILDCAPS_MINE) && (fc.field->get_owned_by() == neutral());
190 }
191 return (fc.field->nodecaps() & BUILDCAPS_MINE) && (fc.field->get_owned_by() == neutral()) &&
192 fc.field->get_resources() == ore_type;
193 }
194
FindNodeUnownedBuildable(Player * p,Game & g)195 FindNodeUnownedBuildable::FindNodeUnownedBuildable(Player* p, Game& g) : player(p), game(g) {
196 }
197
accept(const EditorGameBase &,const FCoords & fc) const198 bool FindNodeUnownedBuildable::accept(const EditorGameBase&, const FCoords& fc) const {
199 return ((fc.field->nodecaps() & BUILDCAPS_SIZEMASK) ||
200 (fc.field->nodecaps() & BUILDCAPS_MINE)) &&
201 (fc.field->get_owned_by() == neutral());
202 }
203
204 // Unowned but walkable fields nearby
FindNodeUnownedWalkable(Player * p,Game & g)205 FindNodeUnownedWalkable::FindNodeUnownedWalkable(Player* p, Game& g) : player(p), game(g) {
206 }
207
accept(const EditorGameBase &,const FCoords & fc) const208 bool FindNodeUnownedWalkable::accept(const EditorGameBase&, const FCoords& fc) const {
209 return (fc.field->nodecaps() & MOVECAPS_WALK) && (fc.field->get_owned_by() == neutral());
210 }
211
212 // Looking only for mines-capable fields nearby
213 // of specific type
FindNodeMineable(Game & g,DescriptionIndex r)214 FindNodeMineable::FindNodeMineable(Game& g, DescriptionIndex r) : game(g), res(r) {
215 }
216
accept(const EditorGameBase &,const FCoords & fc) const217 bool FindNodeMineable::accept(const EditorGameBase&, const FCoords& fc) const {
218
219 return (fc.field->nodecaps() & BUILDCAPS_MINE) && (fc.field->get_resources() == res);
220 }
221
222 // Fishers and fishbreeders must be built near water
FindNodeWater(const World & world)223 FindNodeWater::FindNodeWater(const World& world) : world_(world) {
224 }
225
accept(const EditorGameBase & egbase,const FCoords & coord) const226 bool FindNodeWater::accept(const EditorGameBase& egbase, const FCoords& coord) const {
227 return (world_.terrain_descr(coord.field->terrain_d()).get_is() &
228 TerrainDescription::Is::kWater) ||
229 (world_.terrain_descr(egbase.map().get_neighbour(coord, WALK_W).field->terrain_r())
230 .get_is() &
231 TerrainDescription::Is::kWater) ||
232 (world_.terrain_descr(egbase.map().get_neighbour(coord, WALK_NW).field->terrain_r())
233 .get_is() &
234 TerrainDescription::Is::kWater);
235 }
236
accept(const EditorGameBase &,const FCoords & coord) const237 bool FindNodeOpenWater::accept(const EditorGameBase&, const FCoords& coord) const {
238 return !(coord.field->nodecaps() & MOVECAPS_WALK) && (coord.field->nodecaps() & MOVECAPS_SWIM);
239 }
240
241 // FindNodeWithFlagOrRoad
accept(const EditorGameBase &,FCoords fc) const242 bool FindNodeWithFlagOrRoad::accept(const EditorGameBase&, FCoords fc) const {
243 if (upcast(PlayerImmovable const, pimm, fc.field->get_immovable())) {
244 return (dynamic_cast<Flag const*>(pimm) ||
245 (dynamic_cast<Road const*>(pimm) && (fc.field->nodecaps() & BUILDCAPS_FLAG)));
246 }
247 return false;
248 }
249
NearFlag(const Flag * f,int32_t const c)250 NearFlag::NearFlag(const Flag* f, int32_t const c) : flag(f), current_road_distance(c) {
251 to_be_checked = true;
252 }
253
NearFlag()254 NearFlag::NearFlag() {
255 flag = nullptr;
256 current_road_distance = 0;
257 to_be_checked = true;
258 }
259
EventTimeQueue()260 EventTimeQueue::EventTimeQueue() {
261 }
262
push(const uint32_t production_time,const uint32_t additional_id)263 void EventTimeQueue::push(const uint32_t production_time, const uint32_t additional_id) {
264 queue.push_front(std::make_pair(production_time, additional_id));
265 }
266
267 // Return count of entries in log (deque), if id is provided, it counts corresponding
268 // members. id here can be index of building, f.e. it count how many soldiers were
269 // trained in particular type of training site
count(const uint32_t current_time,const uint32_t additional_id)270 uint32_t EventTimeQueue::count(const uint32_t current_time, const uint32_t additional_id) {
271 strip_old(current_time);
272 if (additional_id == std::numeric_limits<uint32_t>::max()) {
273 return queue.size();
274 } else {
275 uint32_t cnt = 0;
276 for (auto item : queue) {
277 if (item.second == additional_id) {
278 ++cnt;
279 }
280 }
281 return cnt;
282 }
283 }
284
strip_old(const uint32_t current_time)285 void EventTimeQueue::strip_old(const uint32_t current_time) {
286 while (!queue.empty() && queue.back().first < current_time - duration_) {
287 queue.pop_back();
288 }
289 }
290
BuildableField(const Widelands::FCoords & fc)291 BuildableField::BuildableField(const Widelands::FCoords& fc)
292 : coords(fc),
293 field_info_expiration(20000),
294 preferred(false),
295 enemy_nearby(0),
296 enemy_accessible_(false),
297 enemy_wh_nearby(false),
298 unowned_land_nearby(0),
299 enemy_owned_land_nearby(0U),
300 unowned_buildable_spots_nearby(0U),
301 unowned_portspace_vicinity_nearby(0U),
302 nearest_buildable_spot_nearby(0U),
303 near_border(false),
304 unowned_mines_spots_nearby(0),
305 unowned_iron_mines_nearby(false),
306 trees_nearby(0),
307 bushes_nearby(0),
308 // explanation of starting values
309 // this is done to save some work for AI (CPU utilization)
310 // base rules are:
311 // count of rocks can only decrease, so amount of rocks
312 // is recalculated only when previous count is positive
313 // count of water fields are stable, so if the current count is
314 // non-negative, water is not recalculated
315 rocks_nearby(1),
316 water_nearby(-1),
317 open_water_nearby(-1),
318 distant_water(0),
319 fish_nearby(-1),
320 critters_nearby(-1),
321 ground_water(1),
322 space_consumers_nearby(0),
323 rangers_nearby(0),
324 area_military_capacity(0),
325 military_loneliness(1000),
326 military_in_constr_nearby(0),
327 own_military_presence(0),
328 enemy_military_presence(0),
329 enemy_military_sites(0),
330 ally_military_presence(0),
331 military_stationed(0),
332 unconnected_nearby(false),
333 military_unstationed(0),
334 own_non_military_nearby(0),
335 defense_msite_allowed(false),
336 is_portspace(Widelands::ExtendedBool::kUnset),
337 port_nearby(false),
338 portspace_nearby(Widelands::ExtendedBool::kUnset),
339 max_buildcap_nearby(0),
340 last_resources_check_time(0),
341 military_score_(0),
342 inland(false),
343 local_soldier_capacity(0),
344 is_militarysite(false) {
345 }
346
MineableField(const Widelands::FCoords & fc)347 MineableField::MineableField(const Widelands::FCoords& fc)
348 : coords(fc),
349 field_info_expiration(20000),
350 preferred(false),
351 mines_nearby(0),
352 same_mine_fields_nearby(0) {
353 }
354
EconomyObserver(Widelands::Economy & e)355 EconomyObserver::EconomyObserver(Widelands::Economy& e) : economy(e) {
356 dismantle_grace_time = std::numeric_limits<uint32_t>::max();
357 fields_block_last_time = 0;
358 }
359
total_count() const360 int32_t BuildingObserver::total_count() const {
361 return cnt_built + cnt_under_construction;
362 }
363
is(BuildingAttribute attribute) const364 bool BuildingObserver::is(BuildingAttribute attribute) const {
365 return is_what.count(attribute) == 1;
366 }
367
set_is(const BuildingAttribute attribute)368 void BuildingObserver::set_is(const BuildingAttribute attribute) {
369 is_what.insert(attribute);
370 }
371
unset_is(const BuildingAttribute attribute)372 void BuildingObserver::unset_is(const BuildingAttribute attribute) {
373 is_what.erase(attribute);
374 assert(!is(attribute));
375 }
376
has_collected_map_resource() const377 bool BuildingObserver::has_collected_map_resource() const {
378 return collected_map_resource != INVALID_INDEX;
379 }
set_collected_map_resource(const Widelands::TribeDescr & tribe,const std::string & ware_name)380 void BuildingObserver::set_collected_map_resource(const Widelands::TribeDescr& tribe,
381 const std::string& ware_name) {
382 if (!ware_name.empty()) {
383 collected_map_resource = tribe.safe_ware_index(ware_name);
384 } else {
385 collected_map_resource = Widelands::INVALID_INDEX;
386 }
387 }
get_collected_map_resource() const388 DescriptionIndex BuildingObserver::get_collected_map_resource() const {
389 if (has_collected_map_resource()) {
390 return collected_map_resource;
391 } else {
392 throw wexception("Building '%s' needs to define the AI hint \"collects_ware_from_map\"",
393 desc->name().c_str());
394 }
395 }
396
aimode_limit_status() const397 Widelands::AiModeBuildings BuildingObserver::aimode_limit_status() const {
398 if (total_count() > cnt_limit_by_aimode) {
399 return Widelands::AiModeBuildings::kLimitExceeded;
400 } else if (total_count() == cnt_limit_by_aimode) {
401 return Widelands::AiModeBuildings::kOnLimit;
402 } else {
403 return Widelands::AiModeBuildings::kAnotherAllowed;
404 }
405 }
buildable(Widelands::Player & p)406 bool BuildingObserver::buildable(Widelands::Player& p) {
407 return is(BuildingAttribute::kBuildable) && p.is_building_type_allowed(id);
408 }
409
410 // as all mines have 3 levels, AI does not know total count of mines per mined material
411 // so this observer will be used for this
MineTypesObserver()412 MineTypesObserver::MineTypesObserver()
413 : in_construction(0), finished(0), is_critical(false), unoccupied(0) {
414 }
415
416 // Reset counter for all field types
zero()417 void MineFieldsObserver::zero() {
418 for (auto& material : stat) {
419 material.second = 0;
420 }
421 }
422
423 // Increase counter by one for specific ore/minefield type
add(const Widelands::DescriptionIndex idx)424 void MineFieldsObserver::add(const Widelands::DescriptionIndex idx) {
425 ++stat[idx];
426 }
427
428 // Add ore into critical_ores
add_critical_ore(const Widelands::DescriptionIndex idx)429 void MineFieldsObserver::add_critical_ore(const Widelands::DescriptionIndex idx) {
430 critical_ores.insert(idx);
431 }
432
433 // Does the player has at least one mineable field with positive amount for each critical ore?
has_critical_ore_fields()434 bool MineFieldsObserver::has_critical_ore_fields() {
435 for (auto ore : critical_ores) {
436 if (get(ore) == 0) {
437 return false;
438 }
439 }
440 return true;
441 }
442
443 // Returns count of fields with desired ore
get(const Widelands::DescriptionIndex idx)444 uint16_t MineFieldsObserver::get(const Widelands::DescriptionIndex idx) {
445 if (stat.count(idx) == 0) {
446 return 0;
447 }
448 return stat[idx];
449 }
450
451 // Count of types of mineable fields, up to 4 currently
count_types()452 uint8_t MineFieldsObserver::count_types() {
453 uint16_t count = 0;
454 for (auto material : stat) {
455 if (material.second > 0) {
456 ++count;
457 }
458 }
459 return count;
460 }
461
ExpansionType()462 ExpansionType::ExpansionType() {
463 type = ExpansionMode::kResources;
464 }
465
set_expantion_type(const ExpansionMode etype)466 void ExpansionType::set_expantion_type(const ExpansionMode etype) {
467 type = etype;
468 }
469
470 // Initialization of neuron. Neuron is defined by curve (type) and weight [-kWeightRange,
471 // kWeightRange]
472 // third argument is just id
Neuron(int8_t w,uint8_t f,uint16_t i)473 Neuron::Neuron(int8_t w, uint8_t f, uint16_t i) : weight(w), type(f), id(i) {
474 assert(type < neuron_curves.size());
475 assert(weight >= -kNeuronWeightLimit && weight <= kNeuronWeightLimit);
476 recalculate();
477 }
478
479 // Weight, or rather value in range [-kWeightRange, kWeightRange]. Automatically adjusts the weight
480 // to the range in case of
481 // overflow.
set_weight(int8_t w)482 void Neuron::set_weight(int8_t w) {
483 weight = Neuron::clip_weight_to_range(w);
484 }
485
486 // Neuron stores calculated values in an array of size 21.
487 // This has to be recalculated when the weight or curve type change
recalculate()488 void Neuron::recalculate() {
489 assert(neuron_curves.size() > type);
490 for (uint8_t i = 0; i <= kNeuronMaxPosition; ++i) {
491 results[i] = weight * neuron_curves[type][i] / kNeuronWeightLimit;
492 }
493 }
494
495 // The Following two functions return Neuron values on position
get_result(const size_t pos)496 int8_t Neuron::get_result(const size_t pos) {
497 assert(pos <= kNeuronMaxPosition);
498 return results[pos];
499 }
500
501 // get value corresponding to input in range 0-20, if you are out of range
502 // the input will be cropped
get_result_safe(int32_t pos,const bool absolute)503 int8_t Neuron::get_result_safe(int32_t pos, const bool absolute) {
504 // pos has to be normalized into range 0 - 20(kNeuronMaxPosition)
505 pos = std::max(0, std::min(static_cast<int>(kNeuronMaxPosition), pos));
506
507 assert(pos <= static_cast<int32_t>(kNeuronMaxPosition));
508 assert(pos >= 0);
509 assert(results[pos] >= -kNeuronWeightLimit && results[pos] <= kNeuronWeightLimit);
510
511 if (absolute) {
512 return std::abs(results[pos]);
513 }
514 return results[pos];
515 }
516
517 // Setting the type of curve
set_type(uint8_t new_type)518 void Neuron::set_type(uint8_t new_type) {
519 assert(new_type < neuron_curves.size());
520 type = new_type;
521 }
522
523 // FNeuron is basically a single uint32_t integer, and the AI can query every bit of that uint32_t
FNeuron(uint32_t c,uint16_t i)524 FNeuron::FNeuron(uint32_t c, uint16_t i) : core(c), id(i) {
525 }
526
527 // Returning a result depending on combinations of 5 bools
528 // Bools are completely anonymous, but can present any yes/no inputs, e.g. imagine the AI that is
529 // to figure out if it should attack from a militarysite. The inputs can be:
530 // bool1 - are we stronger than the enemy?
531 // bool2 - do we have a basic economy established?
532 // bool3 - do we have local predominance?
533 // bool4 - has our strength grown during the last 60 minutes?
534 // bool5 - are there mines in the vicinity?
535 // These five bools can create 32 combinations = yes/no answers.
536 // In fact this can be perceived as a complicated if..then structure, but one that can
537 // adjust automatically as a part of training.
538 // Or rather it is a 5-dimensional table with 2 columns in every dimension :)
539 // In fact this concept if very demanding for training so we don't use it much
get_result(const bool bool1,const bool bool2,const bool bool3,const bool bool4,const bool bool5)540 bool FNeuron::get_result(
541 const bool bool1, const bool bool2, const bool bool3, const bool bool4, const bool bool5) {
542 return core.test(bool1 * 16 + bool2 * 8 + bool3 * 4 + bool4 * 2 + bool5);
543 }
544
545 // Returning bool on a position
get_position(const uint8_t pos)546 bool FNeuron::get_position(const uint8_t pos) {
547 assert(pos < kFNeuronBitSize);
548 return core.test(pos);
549 }
550
551 // Returning numerical value of FNeuron. Used for saving and priting into log
get_int()552 uint32_t FNeuron::get_int() {
553 return core.to_ulong();
554 }
555
556 // This is basically a mutation of FNeuron
flip_bit(const uint8_t pos)557 void FNeuron::flip_bit(const uint8_t pos) {
558 assert(pos < kFNeuronBitSize);
559 core.flip(pos);
560 }
561
562 // Shifting the value in range -kWeightRange to kWeightRange, if zero_align is true, it is now
563 // allowed to shift
564 // from negative to positive and vice versa, 0 must be used.
shift_weight_value(const int8_t old_value,const bool aggressive)565 int8_t ManagementData::shift_weight_value(const int8_t old_value, const bool aggressive) {
566
567 int16_t halfVArRange = 50;
568 if (aggressive) {
569 halfVArRange = 200;
570 }
571
572 const int16_t upper_limit = std::min<int16_t>(old_value + halfVArRange, kNeuronWeightLimit);
573 const int16_t bottom_limit = std::max<int16_t>(old_value - halfVArRange, -kNeuronWeightLimit);
574 int16_t new_value = bottom_limit + std::rand() % (upper_limit - bottom_limit + 1);
575
576 if (!aggressive && ((old_value > 0 && new_value < 0) || (old_value < 0 && new_value > 0))) {
577 new_value = 0;
578 }
579
580 new_value = Neuron::clip_weight_to_range(new_value);
581 return static_cast<int8_t>(new_value);
582 }
583
584 // Used to score performance of AI
585 // Should be disabled for "production"
review(const uint32_t gametime,PlayerNumber pn,const uint32_t land,const uint32_t max_e_land,const uint32_t old_land,const uint16_t attackers,const int16_t trained_soldiers,const uint16_t strength,const uint32_t existing_ps,const uint32_t first_iron_mine_time)586 void ManagementData::review(const uint32_t gametime,
587 PlayerNumber pn,
588 const uint32_t land,
589 const uint32_t max_e_land,
590 const uint32_t old_land,
591 const uint16_t attackers,
592 const int16_t trained_soldiers,
593 const uint16_t strength,
594 const uint32_t existing_ps,
595 const uint32_t first_iron_mine_time) {
596
597 // bonuses (1000 or nothing)
598 const uint16_t territory_bonus = (land > old_land || land > max_e_land) ? 1000 : 0;
599 const uint16_t iron_mine_bonus = (first_iron_mine_time < 2 * 60 * 60 * 1000) ? 1000 : 0;
600 const uint16_t attack_bonus = (attackers > 0) ? 1000 : 0;
601 const uint16_t training_bonus = (trained_soldiers > 0) ? 1000 : 0;
602
603 // scores (numbers dependant on performance)
604 const uint16_t land_score = land / kCurrentLandDivider;
605 const uint16_t strength_score = std::min<uint16_t>(strength, 100) * kStrengthMultiplier;
606 const uint16_t attack_score = std::min<uint16_t>(attackers, 40) * 50;
607 const uint32_t ps_sites_score = kPSitesRatioMultiplier * std::pow(existing_ps, 3) / 1000 / 1000;
608
609 score = territory_bonus + iron_mine_bonus + attack_bonus + training_bonus + land_score +
610 strength_score + ps_sites_score + attack_score;
611
612 log(" %2d %s: reviewing AI mngm. data, sc: %5d Pr.p: %d (Bonuses:Te:%s I:%s A:%s Tr:%s, "
613 "Scores:Land:%5d Str:%4d PS:%4d, Att:%4d\n",
614 pn, gamestring_with_leading_zeros(gametime), score, primary_parent,
615 (territory_bonus) ? "Y" : "N", (iron_mine_bonus) ? "Y" : "N", (attack_bonus) ? "Y" : "N",
616 (training_bonus) ? "Y" : "N", land_score, strength_score, ps_sites_score, attack_score);
617
618 if (score < -10000 || score > 30000) {
619 log("%2d %s: reviewing AI mngm. data, score too extreme: %4d\n", pn,
620 gamestring_with_leading_zeros(gametime), score);
621 }
622 assert(score > -10000 && score < 100000);
623 }
624
625 // Here we generate new AI DNA (no mutation yet) and push them into persistent data
626 // this can cause inconsistency between local and persistent
new_dna_for_persistent(const uint8_t pn,const Widelands::AiType type)627 void ManagementData::new_dna_for_persistent(const uint8_t pn, const Widelands::AiType type) {
628
629 ai_type = type;
630
631 log("%2d: DNA initialization... \n", pn);
632
633 primary_parent = std::rand() % 4;
634 const uint8_t parent2 = std::rand() % 4;
635
636 std::vector<int16_t> AI_military_numbers_P1(
637 Widelands::Player::AiPersistentState::kMagicNumbersSize);
638 std::vector<int8_t> input_weights_P1(Widelands::Player::AiPersistentState::kNeuronPoolSize);
639 std::vector<int8_t> input_func_P1(Widelands::Player::AiPersistentState::kNeuronPoolSize);
640 std::vector<uint32_t> f_neurons_P1(Widelands::Player::AiPersistentState::kFNeuronPoolSize);
641 ai_dna_handler.fetch_dna(
642 AI_military_numbers_P1, input_weights_P1, input_func_P1, f_neurons_P1, primary_parent + 1);
643
644 std::vector<int16_t> AI_military_numbers_P2(
645 Widelands::Player::AiPersistentState::kMagicNumbersSize);
646 std::vector<int8_t> input_weights_P2(Widelands::Player::AiPersistentState::kNeuronPoolSize);
647 std::vector<int8_t> input_func_P2(Widelands::Player::AiPersistentState::kNeuronPoolSize);
648 std::vector<uint32_t> f_neurons_P2(Widelands::Player::AiPersistentState::kFNeuronPoolSize);
649 ai_dna_handler.fetch_dna(
650 AI_military_numbers_P2, input_weights_P2, input_func_P2, f_neurons_P2, parent2 + 1);
651
652 log(" ... Primary parent: %d, secondary parent: %d\n", primary_parent, parent2);
653
654 // First setting of military numbers, they go directly to persistent data
655 for (uint16_t i = 0; i < Widelands::Player::AiPersistentState::kMagicNumbersSize; ++i) {
656 // Child inherits DNA with probability 1/kSecondParentProbability from main parent
657 DnaParent dna_donor = ((std::rand() % kSecondParentProbability) > 0) ? DnaParent::kPrimary :
658 DnaParent::kSecondary;
659 if (i == kMutationRatePosition) { // Overwriting
660 dna_donor = DnaParent::kPrimary;
661 }
662
663 switch (dna_donor) {
664 case DnaParent::kPrimary:
665 set_military_number_at(i, AI_military_numbers_P1[i]);
666 break;
667 case DnaParent::kSecondary:
668 set_military_number_at(i, AI_military_numbers_P2[i]);
669 break;
670 }
671 }
672
673 persistent_data->neuron_weights.clear();
674 persistent_data->neuron_functs.clear();
675 persistent_data->f_neurons.clear();
676
677 for (uint16_t i = 0; i < Widelands::Player::AiPersistentState::kNeuronPoolSize; ++i) {
678 const DnaParent dna_donor = ((std::rand() % kSecondParentProbability) > 0) ?
679 DnaParent::kPrimary :
680 DnaParent::kSecondary;
681
682 switch (dna_donor) {
683 case DnaParent::kPrimary:
684 persistent_data->neuron_weights.push_back(input_weights_P1[i]);
685 persistent_data->neuron_functs.push_back(input_func_P1[i]);
686 break;
687 case DnaParent::kSecondary:
688 persistent_data->neuron_weights.push_back(input_weights_P2[i]);
689 persistent_data->neuron_functs.push_back(input_func_P2[i]);
690 break;
691 }
692 }
693
694 for (uint16_t i = 0; i < Widelands::Player::AiPersistentState::kFNeuronPoolSize; ++i) {
695 const DnaParent dna_donor = ((std::rand() % kSecondParentProbability) > 0) ?
696 DnaParent::kPrimary :
697 DnaParent::kSecondary;
698 switch (dna_donor) {
699 case DnaParent::kPrimary:
700 persistent_data->f_neurons.push_back(f_neurons_P1[i]);
701 break;
702 case DnaParent::kSecondary:
703 persistent_data->f_neurons.push_back(f_neurons_P2[i]);
704 break;
705 }
706 }
707
708 assert(persistent_data->magic_numbers.size() ==
709 Widelands::Player::AiPersistentState::kMagicNumbersSize);
710 }
711 // Decides if mutation takes place and how intensive it will be
do_mutate(const uint8_t is_preferred,const int16_t mutation_probability)712 MutatingIntensity ManagementData::do_mutate(const uint8_t is_preferred,
713 const int16_t mutation_probability) {
714 if (is_preferred > 0) {
715 return MutatingIntensity::kAgressive;
716 }
717 if (std::rand() % mutation_probability == 0) {
718 return MutatingIntensity::kNormal;
719 }
720 return MutatingIntensity::kNo;
721 }
722
723 // Mutating, but all done on persistent data
mutate(const uint8_t pn)724 void ManagementData::mutate(const uint8_t pn) {
725
726 // Below numbers are used to dictate intensity of mutation
727 // Probability that a value will be mutated = 1 / probability
728 // (lesser number means higher probability and higher mutation)
729 int16_t probability =
730 shift_weight_value(get_military_number_at(kMutationRatePosition), false) + 101;
731 // Some of mutation will be agressive - over full range of values, the number below
732 // say how many (aproximately) they will be
733 uint16_t preferred_numbers_count = 0;
734 // This is used to store status whether wild card was or was not used
735 bool wild_card = false;
736
737 // When doing training mutation probability is to be bigger than not in training mode
738 if (ai_training_mode_) {
739 if (probability > kUpperDefaultMutationLimit) {
740 probability = kUpperDefaultMutationLimit;
741 }
742 if (probability < kLowerDefaultMutationLimit) {
743 probability = kLowerDefaultMutationLimit;
744 }
745 preferred_numbers_count = 1;
746 } else {
747 probability = kNoAiTrainingMutation;
748 }
749
750 set_military_number_at(kMutationRatePosition, probability - 101);
751 // decreasing probability (or rather increasing probability of mutation) if weaker player
752 if (ai_type == Widelands::AiType::kWeak) {
753 probability /= 15;
754 preferred_numbers_count = 25;
755 } else if (ai_type == Widelands::AiType::kVeryWeak) {
756 probability /= 40;
757 preferred_numbers_count = 50;
758 }
759
760 // Wildcard for ai trainingmode
761 if (ai_training_mode_ && std::rand() % 8 == 0 && ai_type == Widelands::AiType::kNormal) {
762 probability /= 3;
763 preferred_numbers_count = 5;
764 wild_card = true;
765 }
766
767 assert(probability > 0 && probability <= 201);
768
769 log("%2d: mutating DNA with probability 1 / %3d, preffered numbers target %d%s:\n", pn,
770 probability, preferred_numbers_count, (wild_card) ? ", wild card" : "");
771
772 if (probability < 201) {
773
774 // Modifying pool of Military numbers
775 {
776 // Preferred numbers are ones that will be mutated agressively in full range
777 // [-kWeightRange, kWeightRange]
778 std::set<int32_t> preferred_numbers;
779 for (int i = 0; i < preferred_numbers_count; i++) {
780 preferred_numbers.insert(std::rand() % pref_number_probability);
781 }
782
783 for (uint16_t i = 0; i < Widelands::Player::AiPersistentState::kMagicNumbersSize; ++i) {
784 if (i == kMutationRatePosition) { // mutated above
785 continue;
786 }
787
788 const MutatingIntensity mutating_intensity =
789 do_mutate(preferred_numbers.count(i) > 0, probability);
790
791 if (mutating_intensity != MutatingIntensity::kNo) {
792 const int16_t old_value = get_military_number_at(i);
793 const int16_t new_value = shift_weight_value(
794 get_military_number_at(i), mutating_intensity == MutatingIntensity::kAgressive);
795 set_military_number_at(i, new_value);
796 log(" Magic number %3d: value changed: %4d -> %4d %s\n", i, old_value,
797 new_value,
798 (mutating_intensity == MutatingIntensity::kAgressive) ? "aggressive" : "");
799 }
800 }
801 }
802
803 // Modifying pool of neurons
804 {
805 // Neurons to be mutated more agressively
806 std::set<int32_t> preferred_neurons;
807 for (int i = 0; i < preferred_numbers_count; i++) {
808 preferred_neurons.insert(std::rand() % pref_number_probability);
809 }
810 for (auto& item : neuron_pool) {
811
812 const MutatingIntensity mutating_intensity =
813 do_mutate(preferred_neurons.count(item.get_id()) > 0, probability);
814
815 if (mutating_intensity != MutatingIntensity::kNo) {
816 const int16_t old_value = item.get_weight();
817 if (std::rand() % 4 == 0) {
818 assert(!neuron_curves.empty());
819 item.set_type(std::rand() % neuron_curves.size());
820 persistent_data->neuron_functs[item.get_id()] = item.get_type();
821 } else {
822 int16_t new_value = shift_weight_value(
823 item.get_weight(), mutating_intensity == MutatingIntensity::kAgressive);
824 item.set_weight(new_value);
825 persistent_data->neuron_weights[item.get_id()] = item.get_weight();
826 }
827 log(" Neuron %2d: weight: %4d -> %4d, new curve: %d %s\n", item.get_id(),
828 old_value, item.get_weight(), item.get_type(),
829 (mutating_intensity == MutatingIntensity::kAgressive) ? "aggressive" : "");
830
831 item.recalculate();
832 }
833 }
834 }
835
836 // Modifying pool of f-neurons
837 {
838 // FNeurons to be mutated more agressively
839 std::set<int32_t> preferred_f_neurons;
840 // preferred_numbers_count is multiplied by 3 because FNeuron store more than
841 // one value
842 for (int i = 0; i < 3 * preferred_numbers_count; i++) {
843 preferred_f_neurons.insert(std::rand() % pref_number_probability);
844 }
845
846 for (auto& item : f_neuron_pool) {
847 uint8_t changed_bits = 0;
848 // is this a preferred neuron
849 if (preferred_f_neurons.count(item.get_id()) > 0) {
850 for (uint8_t i = 0; i < kFNeuronBitSize; ++i) {
851 if (std::rand() % 5 == 0) {
852 item.flip_bit(i);
853 ++changed_bits;
854 }
855 }
856 } else { // normal mutation
857 for (uint8_t i = 0; i < kFNeuronBitSize; ++i) {
858 if (std::rand() % (probability * 3) == 0) {
859 item.flip_bit(i);
860 ++changed_bits;
861 }
862 }
863 }
864
865 if (changed_bits) {
866 persistent_data->f_neurons[item.get_id()] = item.get_int();
867 log(" F-Neuron %2d: new value: %13ul, changed bits: %2d %s\n", item.get_id(),
868 item.get_int(), changed_bits,
869 (preferred_f_neurons.count(item.get_id()) > 0) ? "aggressive" : "");
870 }
871 }
872 }
873 }
874
875 test_consistency();
876 }
877
878 // Now we copy persistent to local
copy_persistent_to_local()879 void ManagementData::copy_persistent_to_local() {
880
881 assert(persistent_data->neuron_weights.size() ==
882 Widelands::Player::AiPersistentState::kNeuronPoolSize);
883 assert(persistent_data->neuron_functs.size() ==
884 Widelands::Player::AiPersistentState::kNeuronPoolSize);
885 neuron_pool.clear();
886 for (uint32_t i = 0; i < Widelands::Player::AiPersistentState::kNeuronPoolSize; ++i) {
887 neuron_pool.push_back(
888 Neuron(persistent_data->neuron_weights[i], persistent_data->neuron_functs[i], i));
889 }
890
891 assert(persistent_data->f_neurons.size() ==
892 Widelands::Player::AiPersistentState::kFNeuronPoolSize);
893 f_neuron_pool.clear();
894 for (uint32_t i = 0; i < Widelands::Player::AiPersistentState::kFNeuronPoolSize; ++i) {
895 f_neuron_pool.push_back(FNeuron(persistent_data->f_neurons[i], i));
896 }
897
898 assert(persistent_data->magic_numbers.size() ==
899 Widelands::Player::AiPersistentState::kMagicNumbersSize);
900
901 test_consistency();
902 log(" ... DNA initialized\n");
903 }
904
test_consistency(bool itemized)905 void ManagementData::test_consistency(bool itemized) {
906 assert(persistent_data->magic_numbers.size() ==
907 Widelands::Player::AiPersistentState::kMagicNumbersSize);
908 assert(persistent_data->neuron_weights.size() ==
909 Widelands::Player::AiPersistentState::kNeuronPoolSize);
910 assert(persistent_data->neuron_functs.size() ==
911 Widelands::Player::AiPersistentState::kNeuronPoolSize);
912 assert(neuron_pool.size() == Widelands::Player::AiPersistentState::kNeuronPoolSize);
913 assert(f_neuron_pool.size() == Widelands::Player::AiPersistentState::kFNeuronPoolSize);
914
915 if (itemized) {
916 // comparing contents of neuron and fneuron pools
917 for (uint16_t i = 0; i < Widelands::Player::AiPersistentState::kNeuronPoolSize; ++i) {
918 assert(persistent_data->neuron_weights[i] == neuron_pool[i].get_weight());
919 assert(persistent_data->neuron_functs[i] == neuron_pool[i].get_type());
920 assert(neuron_pool[i].get_id() == i);
921 }
922 for (uint16_t i = 0; i < Widelands::Player::AiPersistentState::kFNeuronPoolSize; ++i) {
923 assert(persistent_data->f_neurons[i] == f_neuron_pool[i].get_int());
924 assert(f_neuron_pool[i].get_id() == i);
925 }
926 }
927
928 return;
929 }
930
dump_data(const PlayerNumber pn)931 void ManagementData::dump_data(const PlayerNumber pn) {
932 ai_dna_handler.dump_output(persistent_data, pn);
933 }
934
935 // Querying military number at a possition
get_military_number_at(uint8_t pos)936 int16_t ManagementData::get_military_number_at(uint8_t pos) {
937 assert(pos < Widelands::Player::AiPersistentState::kMagicNumbersSize);
938 return persistent_data->magic_numbers.at(pos);
939 }
940
941 // Setting military number (persistent numbers are used also for local use)
set_military_number_at(const uint8_t pos,int16_t value)942 void ManagementData::set_military_number_at(const uint8_t pos, int16_t value) {
943 assert(pos < Widelands::Player::AiPersistentState::kMagicNumbersSize);
944 assert(persistent_data->magic_numbers.size() ==
945 Widelands::Player::AiPersistentState::kMagicNumbersSize);
946 persistent_data->magic_numbers.at(pos) = Neuron::clip_weight_to_range(value);
947 }
948
total_count() const949 uint16_t MineTypesObserver::total_count() const {
950 return in_construction + finished;
951 }
952
953 // this is used to count militarysites by their size
MilitarySiteSizeObserver()954 MilitarySiteSizeObserver::MilitarySiteSizeObserver() : in_construction(0), finished(0) {
955 }
956
957 // this represents a scheduler task
SchedulerTask(const uint32_t time,const Widelands::SchedulerTaskId t,const uint8_t p,const char * d)958 SchedulerTask::SchedulerTask(const uint32_t time,
959 const Widelands::SchedulerTaskId t,
960 const uint8_t p,
961 const char* d)
962 : due_time(time), id(t), priority(p), descr(d) {
963 }
964
operator <(const SchedulerTask & other) const965 bool SchedulerTask::operator<(const SchedulerTask& other) const {
966 return priority > other.priority;
967 }
968
969 // List of blocked fields with block time, with some accompanying functions
add(Widelands::Coords coords,uint32_t till)970 void BlockedFields::add(Widelands::Coords coords, uint32_t till) {
971 const uint32_t hash = coords.hash();
972 if (blocked_fields_.count(hash) == 0) {
973 blocked_fields_.insert(std::pair<uint32_t, uint32_t>(hash, till));
974 } else if (blocked_fields_[hash] < till) {
975 blocked_fields_[hash] = till;
976 }
977 // The third possibility is that a field has been already blocked for longer time than 'till'
978 }
979
count()980 uint32_t BlockedFields::count() {
981 return blocked_fields_.size();
982 }
983
remove_expired(uint32_t gametime)984 void BlockedFields::remove_expired(uint32_t gametime) {
985 std::vector<uint32_t> fields_to_remove;
986 for (auto field : blocked_fields_) {
987 if (field.second < gametime) {
988 fields_to_remove.push_back(field.first);
989 }
990 }
991 while (!fields_to_remove.empty()) {
992 blocked_fields_.erase(fields_to_remove.back());
993 fields_to_remove.pop_back();
994 }
995 }
996
is_blocked(Coords coords)997 bool BlockedFields::is_blocked(Coords coords) {
998 return (blocked_fields_.count(coords.hash()) != 0);
999 }
1000
PlayersStrengths()1001 PlayersStrengths::PlayersStrengths() : update_time(0) {
1002 }
1003
1004 // Constructor to be used
PlayerStat(Widelands::TeamNumber tc,uint32_t pp,uint32_t op,uint32_t o60p,uint32_t cs,uint32_t land,uint32_t oland,uint32_t o60l)1005 PlayersStrengths::PlayerStat::PlayerStat(Widelands::TeamNumber tc,
1006 uint32_t pp,
1007 uint32_t op,
1008 uint32_t o60p,
1009 uint32_t cs,
1010 uint32_t land,
1011 uint32_t oland,
1012 uint32_t o60l)
1013 : team_number(tc),
1014 players_power(pp),
1015 old_players_power(op),
1016 old60_players_power(o60p),
1017 players_casualities(cs),
1018 players_land(land),
1019 old_players_land(oland),
1020 old60_players_land(o60l) {
1021 last_time_seen = kNever;
1022 }
1023
1024 // Inserting/updating data
1025 // We keep information for
1026 // - player strength / power
1027 // - player casualties
1028 // - player land
1029 // We store actual values, but for some of them we store also
1030 // - old = 15 mins ago
1031 // - old60 = 60 mins ago
1032 // e.g. players_power / old_players_power / old60_players_power
1033 // we recieve also player and team numbers to figure out if we are enemies, or in the team
add(Widelands::PlayerNumber pn,Widelands::PlayerNumber opn,Widelands::TeamNumber mytn,Widelands::TeamNumber pltn,uint32_t pp,uint32_t op,uint32_t o60p,uint32_t cs,uint32_t land,uint32_t oland,uint32_t o60l)1034 void PlayersStrengths::add(Widelands::PlayerNumber pn,
1035 Widelands::PlayerNumber opn,
1036 Widelands::TeamNumber mytn,
1037 Widelands::TeamNumber pltn,
1038 uint32_t pp,
1039 uint32_t op,
1040 uint32_t o60p,
1041 uint32_t cs,
1042 uint32_t land,
1043 uint32_t oland,
1044 uint32_t o60l) {
1045 if (all_stats.count(opn) == 0) {
1046 this_player_number = pn;
1047 this_player_team = mytn;
1048 all_stats.insert(std::make_pair(opn, PlayerStat(pltn, pp, op, o60p, cs, land, oland, o60l)));
1049 } else {
1050 all_stats[opn].players_power = pp;
1051 all_stats[opn].old_players_power = op;
1052 all_stats[opn].old60_players_power = o60p;
1053 all_stats[opn].players_casualities = cs;
1054 all_stats[opn].players_land = land;
1055 all_stats[opn].old_players_land = oland;
1056 all_stats[opn].old60_players_land = oland;
1057 assert(this_player_number == pn);
1058 if (this_player_team != mytn) {
1059 log("%2d: Team changed %d -> %d\n", pn, this_player_team, mytn);
1060 this_player_team = mytn;
1061 }
1062 if (all_stats[opn].team_number != pltn) {
1063 log("%2d: Team changed for player %d: %d -> %d\n", pn, opn, all_stats[opn].team_number,
1064 pltn);
1065 all_stats[opn].team_number = pltn;
1066 }
1067 }
1068 }
1069
1070 // Very tiny possibility that player that has a statistics info here
1071 // does not exist anymore
remove_stat(const Widelands::PlayerNumber pn)1072 void PlayersStrengths::remove_stat(const Widelands::PlayerNumber pn) {
1073 if (all_stats.count(pn) > 0) {
1074 log("%d: AI: Erasing statistics for player %d\n", this_player_number, pn);
1075 all_stats.erase(pn);
1076 }
1077 }
1078
1079 // After statistics for team members are updated, this calculation is needed
recalculate_team_power()1080 void PlayersStrengths::recalculate_team_power() {
1081 team_powers.clear();
1082 for (auto& item : all_stats) {
1083 if (item.second.team_number > 0) { // is a member of a team
1084 if (team_powers.count(item.second.team_number) > 0) {
1085 team_powers[item.second.team_number] += item.second.players_power;
1086 } else {
1087 team_powers[item.second.team_number] = item.second.players_power;
1088 }
1089 }
1090 }
1091 }
1092
1093 // This just goes over information about all enemies and where they were seen the last time
any_enemy_seen_lately(const uint32_t gametime)1094 bool PlayersStrengths::any_enemy_seen_lately(const uint32_t gametime) {
1095 for (auto& item : all_stats) {
1096 if (get_is_enemy(item.first) && player_seen_lately(item.first, gametime)) {
1097 return true;
1098 }
1099 }
1100 return false;
1101 }
1102
1103 // Returns count of nearby enemies
enemies_seen_lately_count(const uint32_t gametime)1104 uint8_t PlayersStrengths::enemies_seen_lately_count(const uint32_t gametime) {
1105 uint8_t count = 0;
1106 for (auto& item : all_stats) {
1107 if (get_is_enemy(item.first) && player_seen_lately(item.first, gametime)) {
1108 ++count;
1109 }
1110 }
1111 return count;
1112 }
1113
1114 // When we see enemy, we use this to store the time
set_last_time_seen(const uint32_t seentime,Widelands::PlayerNumber pn)1115 void PlayersStrengths::set_last_time_seen(const uint32_t seentime, Widelands::PlayerNumber pn) {
1116 if (all_stats.count(pn) == 0) {
1117 return;
1118 }
1119 all_stats[pn].last_time_seen = seentime;
1120 }
1121
get_is_enemy(Widelands::PlayerNumber other_player_number)1122 bool PlayersStrengths::get_is_enemy(Widelands::PlayerNumber other_player_number) {
1123 // So this is me
1124 if (other_player_number == this_player_number) {
1125 return false;
1126 }
1127 // If we do not belong to any team, all others are our enemies
1128 if (this_player_team == 0) {
1129 return true;
1130 }
1131 if (all_stats.count(other_player_number) == 0) {
1132 // Should happen only rarely so we print a warning here
1133 log("%d: WARNING: player has no statistics yet for player %d\n", this_player_number,
1134 other_player_number);
1135 return false;
1136 }
1137 // finally we compare my team number of the other player team number
1138 return all_stats[other_player_number].team_number != this_player_team;
1139 }
1140
1141 // Was the player seen less then 2 minutes ago
player_seen_lately(Widelands::PlayerNumber pn,const uint32_t gametime)1142 bool PlayersStrengths::player_seen_lately(Widelands::PlayerNumber pn, const uint32_t gametime) {
1143 if (all_stats.count(pn) == 0) {
1144 // Should happen only rarely so we print a warning here
1145 log("%d: WARNING: player has no statistics yet\n", this_player_number);
1146 return false;
1147 }
1148 if (all_stats[pn].last_time_seen == kNever) {
1149 return false;
1150 }
1151 if (all_stats[pn].last_time_seen + (2U * 60U * 1000U) > gametime) {
1152 return true;
1153 }
1154 return false;
1155 }
1156
1157 // This is the strength of a player
get_player_power(Widelands::PlayerNumber pn)1158 uint32_t PlayersStrengths::get_player_power(Widelands::PlayerNumber pn) {
1159 if (all_stats.count(pn) > 0) {
1160 return all_stats[pn].players_power;
1161 }
1162 return 0;
1163 }
1164
1165 // This is the land size owned by player
get_player_land(Widelands::PlayerNumber pn)1166 uint32_t PlayersStrengths::get_player_land(Widelands::PlayerNumber pn) {
1167 if (all_stats.count(pn) > 0) {
1168 return all_stats[pn].players_land;
1169 }
1170 return 0;
1171 }
1172
1173 // Calculates the strength of the enemies seen within the last 2 minutes
get_visible_enemies_power(const uint32_t gametime)1174 uint32_t PlayersStrengths::get_visible_enemies_power(const uint32_t gametime) {
1175 uint32_t pw = 0;
1176 for (auto& item : all_stats) {
1177 if (get_is_enemy(item.first) && player_seen_lately(item.first, gametime)) {
1178 pw += item.second.players_power;
1179 }
1180 }
1181 return pw;
1182 }
1183
get_enemies_average_power()1184 uint32_t PlayersStrengths::get_enemies_average_power() {
1185 uint32_t sum = 0;
1186 uint8_t count = 0;
1187 for (auto& item : all_stats) {
1188 if (get_is_enemy(item.first)) {
1189 sum += item.second.players_power;
1190 ++count;
1191 }
1192 }
1193 if (count > 0) {
1194 return sum / count;
1195 }
1196 return 0;
1197 }
1198
get_enemies_average_land()1199 uint32_t PlayersStrengths::get_enemies_average_land() {
1200 uint32_t sum = 0;
1201 uint8_t count = 0;
1202 for (auto& item : all_stats) {
1203 if (get_is_enemy(item.first)) {
1204 sum += item.second.players_land;
1205 ++count;
1206 }
1207 }
1208 if (count > 0) {
1209 return sum / count;
1210 }
1211 return 0;
1212 }
1213
1214 // Strength of stronger player
get_enemies_max_power()1215 uint32_t PlayersStrengths::get_enemies_max_power() {
1216 uint32_t power = 0;
1217 for (auto& item : all_stats) {
1218 if (get_is_enemy(item.first)) {
1219 power = std::max<uint32_t>(power, item.second.players_power);
1220 }
1221 }
1222 return power;
1223 }
1224
get_enemies_max_land()1225 uint32_t PlayersStrengths::get_enemies_max_land() {
1226 uint32_t land = 0;
1227 for (auto& item : all_stats) {
1228 if (get_is_enemy(item.first)) {
1229 land = std::max<uint32_t>(land, item.second.players_land);
1230 }
1231 }
1232 return land;
1233 }
1234
get_old_player_power(Widelands::PlayerNumber pn)1235 uint32_t PlayersStrengths::get_old_player_power(Widelands::PlayerNumber pn) {
1236 if (all_stats.count(pn) > 0) {
1237 return all_stats[pn].old_players_power;
1238 }
1239 return 0;
1240 }
1241
get_old60_player_power(Widelands::PlayerNumber pn)1242 uint32_t PlayersStrengths::get_old60_player_power(Widelands::PlayerNumber pn) {
1243 if (all_stats.count(pn) > 0) {
1244 return all_stats[pn].old60_players_power;
1245 }
1246 return 0;
1247 }
1248
get_old_player_land(Widelands::PlayerNumber pn)1249 uint32_t PlayersStrengths::get_old_player_land(Widelands::PlayerNumber pn) {
1250 if (all_stats.count(pn) == 0) {
1251 log(" %d: Players statistics are still empty\n", pn);
1252 return 0;
1253 }
1254 return all_stats[pn].old_players_land;
1255 }
1256
get_old60_player_land(Widelands::PlayerNumber pn)1257 uint32_t PlayersStrengths::get_old60_player_land(Widelands::PlayerNumber pn) {
1258 if (all_stats.count(pn) == 0) {
1259 log(" %d: Players statistics are still empty\n", pn);
1260 return 0;
1261 }
1262 return all_stats[pn].old60_players_land;
1263 }
1264
get_old_visible_enemies_power(const uint32_t gametime)1265 uint32_t PlayersStrengths::get_old_visible_enemies_power(const uint32_t gametime) {
1266 uint32_t pw = 0;
1267 for (auto& item : all_stats) {
1268 if (get_is_enemy(item.first) && player_seen_lately(item.first, gametime)) {
1269 pw += item.second.old_players_power;
1270 }
1271 }
1272 return pw;
1273 }
1274
1275 // This is strength of player plus third of strength of other members of his team
get_modified_player_power(Widelands::PlayerNumber pn)1276 uint32_t PlayersStrengths::get_modified_player_power(Widelands::PlayerNumber pn) {
1277 uint32_t result = 0;
1278 Widelands::TeamNumber team = 0;
1279 if (all_stats.count(pn) > 0) {
1280 result = all_stats[pn].players_power;
1281 team = all_stats[pn].team_number;
1282 }
1283 if (team > 0 && team_powers.count(team) > 0) {
1284 result = result + (team_powers[team] - result) / 3;
1285 }
1286 return result;
1287 }
1288
1289 // Are the player in the same team
players_in_same_team(Widelands::PlayerNumber pl1,Widelands::PlayerNumber pl2)1290 bool PlayersStrengths::players_in_same_team(Widelands::PlayerNumber pl1,
1291 Widelands::PlayerNumber pl2) {
1292 assert(all_stats.count(pl1) > 0);
1293 assert(all_stats.count(pl2) > 0);
1294 if (pl1 == pl2) {
1295 return false;
1296 } else if (all_stats[pl1].team_number > 0 &&
1297 all_stats[pl1].team_number == all_stats[pl2].team_number) {
1298 // team number 0 = no team
1299 return true;
1300 } else {
1301 return false;
1302 }
1303 }
1304
strong_enough(Widelands::PlayerNumber pl)1305 bool PlayersStrengths::strong_enough(Widelands::PlayerNumber pl) {
1306 if (all_stats.count(pl) == 0) {
1307 return false;
1308 }
1309 uint32_t my_strength = all_stats[pl].players_power;
1310 uint32_t strongest_opponent_strength = 0;
1311 for (auto item : all_stats) {
1312 if (!players_in_same_team(item.first, pl) && pl != item.first) {
1313 if (get_modified_player_power(item.first) > strongest_opponent_strength) {
1314 strongest_opponent_strength = get_modified_player_power(item.first);
1315 }
1316 }
1317 }
1318 return my_strength > strongest_opponent_strength + 50;
1319 }
1320
1321 // Update_time is used to prevent too frequent updates of statistics
set_update_time(const uint32_t gametime)1322 void PlayersStrengths::set_update_time(const uint32_t gametime) {
1323 update_time = gametime;
1324 }
1325
get_update_time()1326 uint32_t PlayersStrengths::get_update_time() {
1327 return update_time;
1328 }
1329
FlagInfo(const uint32_t gametime,const uint16_t dist,const uint32_t wh)1330 FlagWarehouseDistances::FlagInfo::FlagInfo(const uint32_t gametime,
1331 const uint16_t dist,
1332 const uint32_t wh) {
1333 expiry_time = gametime + kFlagDistanceExpirationPeriod;
1334 soft_expiry_time = gametime + kFlagDistanceExpirationPeriod / 2;
1335 distance = dist;
1336 nearest_warehouse = wh;
1337 new_road_prohibited_till = 0;
1338 }
FlagInfo()1339 FlagWarehouseDistances::FlagInfo::FlagInfo() {
1340 expiry_time = 0;
1341 distance = 1000;
1342 new_road_prohibited_till = 0;
1343 }
1344
1345 // We are updating the distance info, but not all the time.
1346 // Always if after soft expiration period, but
1347 // if below expiration period only when the new value is lower than current one
1348 // In both cases new expiration times are calculated
update(const uint32_t gametime,const uint16_t new_distance,const uint32_t nearest_wh)1349 bool FlagWarehouseDistances::FlagInfo::update(const uint32_t gametime,
1350 const uint16_t new_distance,
1351 const uint32_t nearest_wh) {
1352 const uint32_t new_expiry_time = gametime + kFlagDistanceExpirationPeriod;
1353
1354 if (gametime > soft_expiry_time) {
1355 distance = new_distance;
1356 expiry_time = new_expiry_time;
1357 soft_expiry_time = gametime + kFlagDistanceExpirationPeriod / 2;
1358 nearest_warehouse = nearest_wh;
1359 return true;
1360 } else if (new_distance < distance ||
1361 (new_distance == distance && expiry_time < new_expiry_time)) {
1362 distance = new_distance;
1363 expiry_time = new_expiry_time;
1364 nearest_warehouse = nearest_wh;
1365 return true;
1366 }
1367 return false;
1368 }
1369
get(const uint32_t gametime,uint32_t * nw) const1370 uint16_t FlagWarehouseDistances::FlagInfo::get(const uint32_t gametime, uint32_t* nw) const {
1371 *nw = nearest_warehouse;
1372 if (gametime <= expiry_time) {
1373 return distance;
1374 }
1375 return kFarButReachable;
1376 }
1377
set_road_built(const uint32_t gametime)1378 void FlagWarehouseDistances::FlagInfo::set_road_built(const uint32_t gametime) {
1379 // Prohibiting for next 60 seconds
1380 new_road_prohibited_till = gametime + 60 * 1000;
1381 }
1382
is_road_prohibited(const uint32_t gametime) const1383 bool FlagWarehouseDistances::FlagInfo::is_road_prohibited(const uint32_t gametime) const {
1384 return new_road_prohibited_till > gametime;
1385 }
1386
set_distance(const uint32_t flag_coords,const uint16_t distance,uint32_t const gametime,uint32_t const nearest_warehouse)1387 bool FlagWarehouseDistances::set_distance(const uint32_t flag_coords,
1388 const uint16_t distance,
1389 uint32_t const gametime,
1390 uint32_t const nearest_warehouse) {
1391 if (flags_map.count(flag_coords) == 0) {
1392 flags_map[flag_coords] =
1393 FlagWarehouseDistances::FlagInfo(gametime, distance, nearest_warehouse);
1394 return true;
1395 }
1396 return flags_map[flag_coords].update(gametime, distance, nearest_warehouse);
1397 }
1398
count() const1399 uint16_t FlagWarehouseDistances::count() const {
1400 return flags_map.size();
1401 }
1402
1403 int16_t
get_distance(const uint32_t flag_coords,uint32_t gametime,uint32_t * nw)1404 FlagWarehouseDistances::get_distance(const uint32_t flag_coords, uint32_t gametime, uint32_t* nw) {
1405 if (flags_map.count(flag_coords) == 0) {
1406 *nw = 0;
1407 return kFarButReachable; // this is to discourage to build second road from brand new flag...
1408 } else {
1409 return flags_map[flag_coords].get(gametime, nw);
1410 }
1411 }
1412
set_road_built(const uint32_t coords_hash,const uint32_t gametime)1413 void FlagWarehouseDistances::set_road_built(const uint32_t coords_hash, const uint32_t gametime) {
1414 if (flags_map.count(coords_hash) == 1) {
1415 flags_map[coords_hash].set_road_built(gametime);
1416 }
1417 }
1418
is_road_prohibited(const uint32_t coords_hash,const uint32_t gametime)1419 bool FlagWarehouseDistances::is_road_prohibited(const uint32_t coords_hash,
1420 const uint32_t gametime) {
1421 if (flags_map.count(coords_hash) == 1) {
1422 return flags_map[coords_hash].is_road_prohibited(gametime);
1423 }
1424 return false;
1425 }
1426
remove_old_flag(uint32_t gametime)1427 bool FlagWarehouseDistances::remove_old_flag(uint32_t gametime) {
1428 for (std::map<uint32_t, FlagWarehouseDistances::FlagInfo>::iterator it = flags_map.begin();
1429 it != flags_map.end(); it++) {
1430 if (it->second.expiry_time + kOldFlagRemoveTime < gametime) {
1431 it = flags_map.erase(it);
1432 return true;
1433 }
1434 }
1435 return false;
1436 }
1437
1438 // Returns pointer to winning road, provided that the treshold is exceeded
get_winner(const int16_t threshold)1439 FlagCandidates::Candidate* FlagCandidates::get_winner(const int16_t threshold) {
1440 if (flags_.empty()) {
1441 return nullptr;
1442 }
1443 sort();
1444 if (flags_[0].score() < threshold) {
1445 return nullptr;
1446 }
1447 if (!flags_[0].is_buildable()) {
1448 return nullptr;
1449 }
1450 return &flags_[0];
1451 }
1452
Candidate(const uint32_t c_hash,bool different_eco,uint16_t start_f_dist,uint16_t act_dist_to_wh,uint16_t air_dst)1453 FlagCandidates::Candidate::Candidate(const uint32_t c_hash,
1454 bool different_eco,
1455 uint16_t start_f_dist,
1456 uint16_t act_dist_to_wh,
1457 uint16_t air_dst) {
1458 coords_hash = c_hash;
1459 different_economy = different_eco;
1460 start_flag_dist_to_wh = start_f_dist;
1461 flag_to_flag_road_distance = 0;
1462 possible_road_distance = kFarButReachable;
1463 cand_flag_distance_to_wh = act_dist_to_wh;
1464 air_distance = air_dst;
1465 }
1466
score() const1467 int16_t FlagCandidates::Candidate::score() const {
1468 return different_economy * 2000 + (start_flag_dist_to_wh - cand_flag_distance_to_wh) +
1469 (flag_to_flag_road_distance - 2 * possible_road_distance);
1470 }
1471
set_road_possible(const uint32_t coords_hash,const uint16_t steps)1472 bool FlagCandidates::set_road_possible(const uint32_t coords_hash, const uint16_t steps) {
1473 for (auto& item : flags_) {
1474 if (item.coords_hash == coords_hash) {
1475 item.possible_road_distance = steps;
1476 return true;
1477 }
1478 }
1479 return false;
1480 }
1481
set_cur_road_distance(const uint32_t coords,uint16_t dist)1482 bool FlagCandidates::set_cur_road_distance(const uint32_t coords, uint16_t dist) {
1483 for (auto& item : flags_) {
1484 if (item.coords_hash == coords) {
1485 item.flag_to_flag_road_distance = dist;
1486 return true;
1487 }
1488 }
1489 return false;
1490 }
sort()1491 void FlagCandidates::sort() {
1492 std::sort(flags_.begin(), flags_.end());
1493 }
1494
sort_by_air_distance()1495 void FlagCandidates::sort_by_air_distance() {
1496 std::sort(flags_.begin(), flags_.end(),
1497 [](const FlagCandidates::Candidate& lf, const FlagCandidates::Candidate& rf) {
1498 return lf.air_distance < rf.air_distance;
1499 });
1500 }
1501
add_flag(const uint32_t coords,const bool different_economy,const uint16_t act_dist_to_wh,const uint16_t air_distance)1502 void FlagCandidates::add_flag(const uint32_t coords,
1503 const bool different_economy,
1504 const uint16_t act_dist_to_wh,
1505 const uint16_t air_distance) {
1506 flags_.push_back(
1507 Candidate(coords, different_economy, start_flag_dist_to_wh, act_dist_to_wh, air_distance));
1508 }
1509
has_candidate(const uint32_t coords_hash) const1510 bool FlagCandidates::has_candidate(const uint32_t coords_hash) const {
1511 for (auto& item : flags_) {
1512 if (item.coords_hash == coords_hash) {
1513 return true;
1514 }
1515 }
1516 return false;
1517 }
1518
1519 } // namespace Widelands
1520