Package horizons :: Package ai :: Package aiplayer :: Package behavior :: Module behaviorcomponents
[hide private]
[frames] | no frames]

Source Code for Module horizons.ai.aiplayer.behavior.behaviorcomponents

  1  # ################################################### 
  2  # Copyright (C) 2008-2017 The Unknown Horizons Team 
  3  # team@unknown-horizons.org 
  4  # This file is part of Unknown Horizons. 
  5  # 
  6  # Unknown Horizons is free software; you can redistribute it and/or modify 
  7  # it under the terms of the GNU General Public License as published by 
  8  # the Free Software Foundation; either version 2 of the License, or 
  9  # (at your option) any later version. 
 10  # 
 11  # This program is distributed in the hope that it will be useful, 
 12  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 13  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 14  # GNU General Public License for more details. 
 15  # 
 16  # You should have received a copy of the GNU General Public License 
 17  # along with this program; if not, write to the 
 18  # Free Software Foundation, Inc., 
 19  # 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
 20  # ################################################### 
 21   
 22  import logging 
 23  from collections import defaultdict 
 24   
 25  from horizons.ai.aiplayer.behavior.diplomacysettings import DiplomacySettings 
 26  from horizons.ai.aiplayer.behavior.movecallbacks import BehaviorMoveCallback 
 27  from horizons.ai.aiplayer.combat.combatmanager import CombatManager 
 28  from horizons.ai.aiplayer.combat.unitmanager import UnitManager 
 29  from horizons.ai.aiplayer.strategy.mission.chaseshipsandattack import ChaseShipsAndAttack 
 30  from horizons.ai.aiplayer.strategy.mission.pirateroutine import PirateRoutine 
 31  from horizons.ai.aiplayer.strategy.mission.surpriseattack import SurpriseAttack 
 32  from horizons.command.diplomacy import AddEnemyPair 
 33  from horizons.component.namedcomponent import NamedComponent 
 34  from horizons.constants import BUILDINGS 
 35  from horizons.ext.enum import Enum 
 36  from horizons.util.python.callback import Callback 
 37  from horizons.util.shapes import Circle 
 38  from horizons.world.units.unitexeptions import MoveNotPossible 
39 40 41 -class BehaviorComponent:
42 """ 43 This is an abstract BehaviorComponent - a building block for AI Behavior. 44 """ 45 log = logging.getLogger('ai.aiplayer.behavior.behaviorcomponents') 46 default_certainty = 1.0 47 minimal_fleet_size = 1 48
49 - def __init__(self, owner):
50 self.owner = owner 51 self.combat_manager = owner.combat_manager 52 self.unit_manager = owner.unit_manager 53 self.world = owner.world 54 self.session = owner.session 55 56 # Witchery below is a way to have certainty() always return the same certainty if it's not defined per behavior. 57 self._certainty = defaultdict(lambda: (lambda **env: self.default_certainty))
58
59 - def certainty(self, action_name, **environment):
60 certainty = self._certainty[action_name](**environment) 61 assert certainty is not None, "Certainty function returned None " \ 62 "instead of a float. Certainty in {0!s} for {1!s}". \ 63 format(self.__class__.__name__, action_name) 64 return certainty
65 66 # common certainties used by various behaviors
67 - def _certainty_has_boat_builder(self, **environment):
68 if self.owner.count_buildings(BUILDINGS.BOAT_BUILDER): 69 return self.default_certainty 70 else: 71 return 0.0
72
73 - def _certainty_has_fleet(self, **environment):
74 idle_ships = environment['idle_ships'] 75 if idle_ships: 76 return self.default_certainty 77 else: 78 return 0.0
79
80 # Components below are roughly divided into "Aggressive, Normal, Cautious" etc. 81 # (division below is not related to the way dictionaries in BehaviorManager are named (offensive, idle, defensive)) 82 83 84 -class BehaviorDoNothing(BehaviorComponent):
85 """ 86 Behavior that does nothing. Used mainly for idle actions (we don't want to scout too often). 87 """ 88
89 - def __init__(self, owner):
90 super().__init__(owner)
91
92 - def no_one_in_sight(self, **environment):
93 pass
94
95 96 -class BehaviorPirateRoutine(BehaviorComponent):
97 """ 98 Idle behavior for Pirate player. It has to be specialized for Pirate since general AI does not have home_point. 99 Responsible for pirate ships routine when no one is around. States change in a loop: 100 idle -> moving_random -> going_home -> idle 101 """ 102 103 sail_home_chance = 0.3 # sail_home_chance to sail home, 1-sail_home_chance to sail randomly 104 pirate_caught_ship_radius = 5 105 pirate_home_radius = 2 106
107 - def __init__(self, owner):
108 super().__init__(owner)
109
110 - def no_one_in_sight(self, **environment):
111 """ 112 Idle action, sail randomly when no ship was spotted nearby. 113 """ 114 ship_group = environment['ship_group'] 115 for ship in ship_group: 116 rand_val = self.session.random.random() 117 if self.owner.ships[ship] == self.owner.shipStates.idle: 118 if rand_val < self.sail_home_chance: 119 self._sail_home(ship) 120 else: 121 self._sail_random(ship) 122 123 self.log.debug('BehaviorPirateRoutine: Ship:{0!s} no_one_in_sight'. 124 format(ship.get_component(NamedComponent).name))
125
126 - def trading_ships_in_sight(self, **environment):
127 ship_group = environment['ship_group'] 128 for ship in ship_group: 129 self._chase_closest_ship(ship) 130 self.log.debug('BehaviorPirateRoutine: Ship:{0!s} trading_ships_in_sight'. 131 format(ship.get_component(NamedComponent).name))
132
133 - def _arrived(self, ship):
134 """ 135 Callback function executed once ship arrives at the destination after certain action. 136 Practically only changes ship state to idle. 137 """ 138 owner = ship.owner 139 self.log.debug('Player {0!s}: Ship {1!s}: arrived at destination after "{2!s}"'. 140 format(owner.name, ship.get_component(NamedComponent).name, owner.ships[ship])) 141 owner.ships[ship] = owner.shipStates.idle
142
143 - def _chase_closest_ship(self, pirate_ship):
144 owner = pirate_ship.owner 145 ship = owner.get_nearest_player_ship(pirate_ship) 146 if ship: 147 owner.ships[pirate_ship] = owner.shipStates.chasing_ship 148 149 # if ship was caught 150 if ship.position.distance(pirate_ship.position) <= self.pirate_caught_ship_radius: 151 self.log.debug('Pirate {0!s}: Ship {1!s}({2!s}) caught {3!s}'. 152 format(owner.worldid, pirate_ship.get_component(NamedComponent).name, 153 owner.ships[pirate_ship], ship)) 154 self._sail_home(pirate_ship) 155 else: 156 try: 157 pirate_ship.move(Circle(ship.position, self.pirate_caught_ship_radius - 1), Callback(self._sail_home, pirate_ship)) 158 owner.ships[pirate_ship] = owner.shipStates.chasing_ship 159 self.log.debug('Pirate {0!s}: Ship {1!s}({2!s}) chasing {3!s}'. 160 format(owner.worldid, pirate_ship.get_component(NamedComponent).name, 161 owner.ships[pirate_ship], ship.get_component(NamedComponent).name)) 162 except MoveNotPossible: 163 self.log.debug('Pirate {0!s}: Ship {1!s}({2!s}) unable to chase the closest ship {3!s}'. 164 format(owner.worldid, pirate_ship.get_component(NamedComponent).name, 165 owner.ships[pirate_ship], ship.get_component(NamedComponent).name)) 166 owner.ships[pirate_ship] = owner.shipStates.idle
167
168 - def _sail_home(self, pirate_ship):
169 owner = pirate_ship.owner 170 try: 171 pirate_ship.move(Circle(owner.home_point, self.pirate_home_radius), Callback(self._arrived, pirate_ship)) 172 owner.ships[pirate_ship] = owner.shipStates.going_home 173 self.log.debug('Pirate {0!s}: Ship {1!s}({2!s}): sailing home at {3!s}'. 174 format(owner.worldid, pirate_ship.get_component(NamedComponent).name, 175 owner.ships[pirate_ship], owner.home_point)) 176 except MoveNotPossible: 177 owner.ships[pirate_ship] = owner.shipStates.idle 178 self.log.debug('Pirate {0!s}: Ship {1!s}: unable to move home at {2!s}'. 179 format(owner.worldid, pirate_ship.get_component(NamedComponent).name, 180 owner.home_point))
181
182 - def _sail_random(self, pirate_ship):
183 184 owner = pirate_ship.owner 185 session = owner.session 186 point = session.world.get_random_possible_ship_position() 187 try: 188 pirate_ship.move(point, Callback(self._arrived, pirate_ship)) 189 owner.ships[pirate_ship] = owner.shipStates.moving_random 190 self.log.debug('Pirate {0!s}: Ship {1!s}({2!s}): moving random at {3!s}'. 191 format(owner.worldid, pirate_ship.get_component(NamedComponent).name, 192 owner.ships[pirate_ship], point)) 193 except MoveNotPossible: 194 owner.ships[pirate_ship] = owner.shipStates.idle 195 self.log.debug('Pirate {0!s}: Ship {1!s}: unable to move random at {2!s}'. 196 format(owner.worldid, pirate_ship.get_component(NamedComponent).name, point))
197
198 199 # Common certainty functions for offensive actions 200 -def certainty_power_balance_exp(**environment):
201 """ 202 Return power_balance^2, altering the exponent will impact the weight certainty has. 203 """ 204 return BehaviorComponent.default_certainty * (environment['power_balance'] ** 2)
205
206 207 -def certainty_power_balance_inverse(**environment):
208 """Return power_balance reciprocal, 209 """ 210 return BehaviorComponent.default_certainty * (1. / environment['power_balance'])
211
212 213 -class BehaviorRegular(BehaviorComponent):
214 """A well-balanced way to respond to situations in game. 215 """ 216 power_balance_threshold = 1.0 217
218 - def __init__(self, owner):
219 super().__init__(owner) 220 self._certainty['pirate_ships_in_sight'] = certainty_power_balance_exp 221 self._certainty['fighting_ships_in_sight'] = certainty_power_balance_exp 222 self._certainty['player_shares_island'] = self._certainty_player_shares_island 223 self._certainty['hostile_player'] = self._certainty_hostile_player 224 self._certainty['debug'] = self._certainty_ship_amount
225
226 - def pirate_ships_in_sight(self, **environment):
227 """ 228 Attacks pirates only if they are enemies already and the power balance is advantageous. 229 """ 230 pirates = environment['enemies'] 231 ship_group = environment['ship_group'] 232 power_balance = environment['power_balance'] 233 234 # It's enough to check if first pirate is hostile, since there is only one pirate player. 235 if self.session.world.diplomacy.are_enemies(self.owner, pirates[0].owner): 236 # Let each ship attack it's closest enemy to maximize dps (in a way) 237 ship_pairs = UnitManager.get_closest_ships_for_each(ship_group, pirates) 238 for ship, pirate in ship_pairs: 239 ship.attack(pirates[0]) 240 BehaviorComponent.log.info('%s: Attacked pirate ship', self.__class__.__name__) 241 else: 242 BehaviorComponent.log.info('%s: Pirate ship was not hostile', self.__class__.__name__)
243
244 - def fighting_ships_in_sight(self, **environment):
245 """ 246 Attacks frigates only if they are enemies already and the power balance is advantageous. 247 """ 248 enemies = environment['enemies'] 249 ship_group = environment['ship_group'] 250 power_balance = environment['power_balance'] 251 252 if power_balance > self.power_balance_threshold: 253 BehaviorComponent.log.info('%s: Enemy group is too strong', self.__class__.__name__) 254 return 255 256 if self.session.world.diplomacy.are_enemies(self.owner, enemies[0].owner): 257 ship_pairs = UnitManager.get_closest_ships_for_each(ship_group, enemies) 258 for ship, enemy_ship in ship_pairs: 259 ship.attack(enemy_ship) 260 261 BehaviorComponent.log.info('%s: Attacked enemy ship', self.__class__.__name__) 262 else: 263 BehaviorComponent.log.info('%s: Enemy ship was not hostile', self.__class__.__name__)
264
265 - def working_ships_in_sight(self, **environment):
266 """ 267 Attacks working ships only if they are hostile. 268 """ 269 enemies = environment['enemies'] 270 ship_group = environment['ship_group'] 271 272 if self.session.world.diplomacy.are_enemies(self.owner, enemies[0].owner): 273 for ship in ship_group: 274 ship.attack(enemies[0]) 275 BehaviorComponent.log.info('%s: Attacked enemy ship', self.__class__.__name__) 276 else: 277 BehaviorComponent.log.info('%s: Enemy worker was not hostile', self.__class__.__name__)
278
279 - def _certainty_player_shares_island(self, **environment):
280 """ 281 Dummy certainty that checks for a fleets size only. 282 """ 283 idle_ships = environment['idle_ships'] 284 285 if len(idle_ships) < self.minimal_fleet_size: 286 return 0.0 287 288 return self.default_certainty
289
290 - def _certainty_ship_amount(self, **environment):
291 idle_ships = environment['idle_ships'] 292 293 if len(idle_ships) < self.minimal_fleet_size: 294 return 0.0 295 else: 296 return self.default_certainty
297
298 - def _certainty_hostile_player(self, **environment):
299 enemy_player = environment['player'] 300 idle_ships = environment['idle_ships'] 301 302 enemy_ships = self.unit_manager.get_player_ships(enemy_player) 303 304 if not enemy_ships or len(idle_ships) < self.minimal_fleet_size: 305 return 0.0 306 307 return self.default_certainty
308
309 - def player_shares_island(self, **environment):
310 """ 311 Response to player that shares an island with AI player. 312 Regular AI should simply attack given player. 313 """ 314 enemy_player = environment['player'] 315 idle_ships = environment['idle_ships'] 316 317 if not enemy_player.settlements: 318 return None 319 320 target_point = self.unit_manager.get_warehouse_area(enemy_player.settlements[0], 13) 321 322 return_point = idle_ships[0].position.copy() 323 mission = SurpriseAttack.create(self.owner.strategy_manager.report_success, 324 self.owner.strategy_manager.report_failure, idle_ships, target_point, return_point, enemy_player) 325 return mission
326
327 - def hostile_player(self, **environment):
328 """ 329 Arrage an attack for hostile ships. 330 """ 331 enemy_player = environment['player'] 332 idle_ships = environment['idle_ships'] 333 enemy_ships = self.unit_manager.get_player_ships(enemy_player) 334 335 # TODO: pick target ship better 336 target_ship = enemy_ships[0] 337 mission = ChaseShipsAndAttack.create(self.owner.strategy_manager.report_success, 338 self.owner.strategy_manager.report_failure, idle_ships, target_ship) 339 340 return mission
341
342 - def neutral_player(self, **environment):
343 """Not concerned about neutral players. 344 """ 345 return None
346
347 348 -class BehaviorAggressive(BehaviorComponent):
349 350 power_balance_threshold = 0.8 # allow to attack targets that are slightly stronger 351
352 - def __init__(self, owner):
353 super().__init__(owner) 354 self._certainty['neutral_player'] = self._certainty_neutral_player 355 self._certainty['fighting_ships_in_sight'] = self._certainty_fighting_ships_in_sight
356
357 - def _certainty_fighting_ships_in_sight(self, **environment):
358 return self.default_certainty
359
360 - def fighting_ships_in_sight(self, **environment):
361 """ 362 Attacks frigates only if they are enemies already and the power balance is advantageous. 363 """ 364 enemies = environment['enemies'] 365 ship_group = environment['ship_group'] 366 power_balance = environment['power_balance'] 367 368 if power_balance < self.power_balance_threshold: 369 BehaviorComponent.log.info('%s: Enemy group is too strong', self.__class__.__name__) 370 return 371 372 # attack ship with the lowest HP 373 target_ship = UnitManager.get_lowest_hp_ship(enemies) 374 375 if self.session.world.diplomacy.are_enemies(self.owner, target_ship.owner): 376 for ship in ship_group: 377 ship.attack(target_ship) 378 BehaviorComponent.log.info('%s: Attacked enemy ship', self.__class__.__name__) 379 else: 380 BehaviorComponent.log.info('%s: Enemy ship was not hostile', self.__class__.__name__)
381
382 - def _certainty_neutral_player(self, **environment):
383 idle_ships = environment['idle_ships'] 384 385 if len(idle_ships) >= self.minimal_fleet_size: 386 return self.default_certainty 387 elif self.owner.count_buildings(BUILDINGS.BOAT_BUILDER): 388 return self.default_certainty 389 else: 390 return 0.0
391
392 - def neutral_player(self, **environment):
393 """ 394 Start war with neutral player 395 -make a SurpriseAttack if possible. 396 -break diplomacy otherwise. 397 """ 398 idle_ships = environment['idle_ships'] 399 enemy_player = environment['player'] 400 401 # Nothing to do when AI or enemy don't have a settlement yet 402 if not enemy_player.settlements or not self.owner.settlements: 403 return None 404 405 # Send a surprise attack if there are ships available, otherwise simply declare war 406 if idle_ships: 407 target_point = self.unit_manager.get_warehouse_area(enemy_player.settlements[0]) 408 return_point = self.unit_manager.get_warehouse_area(self.owner.settlements[0], 15) 409 mission = SurpriseAttack.create(self.owner.strategy_manager.report_success, 410 self.owner.strategy_manager.report_failure, idle_ships, target_point, return_point, enemy_player) 411 return mission 412 else: 413 AddEnemyPair(self.owner, enemy_player).execute(self.session)
414
415 416 -class BehaviorCautious(BehaviorComponent):
417
418 - def __init__(self, owner):
419 super().__init__(owner)
420
421 - def neutral_player(self, **environment):
422 """ 423 Not concerned about neutral players. 424 """ 425 return None
426
427 428 -class BehaviorSmart(BehaviorComponent):
429
430 - def fighting_ships_in_sight(self, **environment):
431 """ 432 Attacks frigates, and keeps distance based on power balance. 433 """ 434 enemies = environment['enemies'] 435 ship_group = environment['ship_group'] 436 power_balance = environment['power_balance'] 437 438 if power_balance >= 1.0: 439 range_function = CombatManager.close_range 440 else: 441 range_function = CombatManager.fallback_range 442 443 ship_pairs = UnitManager.get_closest_ships_for_each(ship_group, enemies) 444 for ship, enemy_ship in ship_pairs: 445 if self.session.world.diplomacy.are_enemies(ship.owner, enemy_ship.owner): 446 BehaviorMoveCallback.maintain_distance_and_attack(ship, enemy_ship, range_function(ship)) 447 BehaviorComponent.log.info('%s: Attack: %s -> %s', self.__class__.__name__, 448 ship.get_component(NamedComponent).name, enemy_ship.get_component(NamedComponent).name) 449 else: 450 BehaviorComponent.log.info('%s: Enemy ship %s was not hostile', self.__class__.__name__, 451 ship.get_component(NamedComponent).name)
452
453 - def working_ships_in_sight(self, **environment):
454 """ 455 Attacks working ships only if they are hostile. 456 """ 457 enemies = environment['enemies'] 458 ship_group = environment['ship_group'] 459 460 if self.session.world.diplomacy.are_enemies(self.owner, enemies[0].owner): 461 # working ships won't respond with fire, each ship should attack the closest one, and chase them if necessary. 462 ship_pairs = UnitManager.get_closest_ships_for_each(ship_group, enemies) 463 for ship, enemy_ship in ship_pairs: 464 range_function = CombatManager.close_range 465 BehaviorMoveCallback.maintain_distance_and_attack(ship, enemy_ship, range_function(ship)) 466 BehaviorComponent.log.info('%s: Attacked enemy ship', self.__class__.__name__) 467 else: 468 BehaviorComponent.log.info('%s: Enemy worker was not hostile', self.__class__.__name__)
469
470 471 # Behaviors calculate single value against each of the players (you can think of it as of respect, or "relationship_score" values towards other player) 472 # Each AI values different traits in others. Based on that value AI can break diplomacy with an ally, declare a war, or 473 # act the other way around: form an alliance 474 -class BehaviorDiplomatic(BehaviorComponent):
475 """ 476 Behaviors that handle diplomacy. 477 """ 478 479 # value to which each function is related, so even when relationship_score is at the peek somewhere (e.g. it's value is 1.0) 480 # probability to actually choose given action is peek/upper_boundary (0.2 in case of upper_boundary = 5.0) 481 upper_boundary = DiplomacySettings.upper_boundary 482 483 # possible actions behavior can take 484 actions = Enum('wait', 'war', 'peace', 'neutral') 485
486 - def calculate_relationship_score(self, balance, weights):
487 """ 488 Calculate total relationship_score based on balances and their weights. 489 Count only balances that have weight defined (this way "default" weight is 0) 490 """ 491 return sum((getattr(balance, key) * value for key, value in weights.items()))
492 493 @classmethod
494 - def _move_f(cls, f, v_x, v_y):
495 """ 496 Return function f moved by vector (v_x, v_y) 497 """ 498 return lambda x: f(x - v_x) + v_y
499
500 - def handle_diplomacy(self, parameters, **environment):
501 """ 502 Main function responsible for handling diplomacy. 503 """ 504 player = environment['player'] 505 balance = self.owner.strategy_manager.calculate_player_balance(player) 506 relationship_score = self.calculate_relationship_score(balance, self.weights) 507 action = self._get_action(relationship_score, **parameters) 508 self.log.debug("{0!s} vs {1!s} | Dipomacy: balance:{2!s}, relationship_score:{3!s}, action:{4!s}". 509 format(self.owner.name, player.name, balance, relationship_score, action)) 510 self._perform_action(action, **environment)
511
512 - def _perform_action(self, action, **environment):
513 """ 514 Execute action from actions Enum. 515 """ 516 player = environment['player'] 517 518 # ideally this shouldn't automatically change diplomacy for both players (i.e. add_pair) but do it for one side only. 519 520 if action == self.actions.war: 521 self.session.world.diplomacy.add_enemy_pair(self.owner, player) 522 elif action == self.actions.peace: 523 self.session.world.diplomacy.add_ally_pair(self.owner, player) 524 elif action == self.actions.neutral: 525 self.session.world.diplomacy.add_neutral_pair(self.owner, player)
526 527 @classmethod
528 - def _get_quadratic_function(cls, mid, root, peek=1.0):
529 """ 530 Functions for border distributions such as enemy or ally (left or right parabola). 531 @param mid: value on axis X that is to be center of the parabola 532 @type mid: float 533 @param root: value on axis X which is a crossing point of axis OX and the function itself 534 @type root: float 535 @param peek: value on axis Y which is a peek of a function 536 @type peek: float 537 @return: quadratic function 538 @rtype: lambda(x) 539 """ 540 541 # base function is upside-down parabola, stretched in X in order to have roots at exactly 'root' value. 542 # (-1. / (abs(mid - root) ** 2)) part is for stretching the parabola in X axis and flipping it upside down, we have to use 543 # abs(mid - root) because it's later moved by mid 544 # Note: Multiply by 1./abs(mid-root) to scale function in X (e.g. if mid is 1.0 and root is 1.5 -> make original x^2 function 2 times narrower 545 base = lambda x: (-1. / (abs(mid - root) ** 2)) * (x ** 2) 546 547 # we move the function so it looks like "distribution", i.e. move it far left (or right), and assume the peek is 1.0 548 moved = cls._move_f(base, mid, 1.0) 549 550 # in case of negative values of f(x) we want to have 0.0 instead 551 # we multiply by peek here in order to scale function in Y 552 final_function = lambda x: max(0.0, moved(x) * peek) 553 554 return final_function
555 556 @classmethod
557 - def get_enemy_function(cls, root, peek=1.0):
558 return cls._get_quadratic_function(-10.0, root, peek)
559 560 @classmethod
561 - def get_ally_function(cls, root, peek=1.0):
562 return cls._get_quadratic_function(10.0, root, peek)
563 564 @classmethod
565 - def get_neutral_function(cls, mid, root, peek=1.0):
566 return cls._get_quadratic_function(mid, root, peek)
567
568 - def _choose_random_from_tuple(self, tuple):
569 """ 570 Choose random action from tuple of (name, value) 571 """ 572 total_probability = sum((item[1] for item in tuple)) 573 random_value = self.session.random.random() * total_probability 574 counter = 0.0 575 for item in tuple: 576 if item[1] + counter >= random_value: 577 return item[0] 578 else: 579 counter += item[1]
580
581 - def _get_action(self, relationship_score, **parameters):
582 possible_actions = [] 583 if 'enemy' in parameters: 584 enemy_params = parameters['enemy'] 585 possible_actions.append((self.actions.war, self.get_enemy_function(**enemy_params)(relationship_score), )) 586 587 if 'ally' in parameters: 588 ally_params = parameters['ally'] 589 possible_actions.append((self.actions.peace, self.get_ally_function(**ally_params)(relationship_score), )) 590 591 if 'neutral' in parameters: 592 neutral_params = parameters['neutral'] 593 possible_actions.append((self.actions.neutral, self.get_neutral_function(**neutral_params)(relationship_score), )) 594 595 max_probability = max((item[1] for item in possible_actions)) 596 random_value = self.session.random.random() * self.upper_boundary 597 if random_value < max_probability: #do something 598 return self._choose_random_from_tuple(possible_actions) 599 else: 600 return self.actions.wait
601
602 - def hostile_player(self, **environment):
603 """ 604 Calculate balance, and change diplomacy towards a player to neutral or ally. 605 This has a very small chance though, since BehaviorEvil enjoys to be in a war. 606 """ 607 608 # Parameters are crucial in determining how AI should behave: 609 # 'ally' and 'enemy' parameters are tuples of 1 or 2 values that set width or width and height of the parabola. 610 # By default parabola peek is fixed at 1.0, but could be changed (by providing second parameter) 611 # to manipulate the chance with which given actions is called 612 # 'neutral' parameter is a tuple up to three values, first one determining where the center of the parabola is 613 614 self.handle_diplomacy(self.parameters_hostile, **environment)
615
616 - def neutral_player(self, **environment):
617 self.handle_diplomacy(self.parameters_neutral, **environment)
618
619 - def allied_player(self, **environment):
620 self.handle_diplomacy(self.parameters_allied, **environment)
621
622 623 -class BehaviorEvil(BehaviorDiplomatic):
624 """ 625 Diplomatic behavior. 626 Evil AI likes players that are: 627 - stronger 628 - bigger (in terms of terrain) 629 - wealthier 630 Neutral towards: 631 - poorer 632 Dislikes: 633 - weaker 634 - smaller 635 """ 636
637 - def __init__(self, owner):
638 super().__init__(owner) 639 self._certainty['hostile_player'] = self._certainty_has_fleet 640 self._certainty['neutral_player'] = self._certainty_has_boat_builder
641 642 weights = DiplomacySettings.Evil.weights 643 parameters_hostile = DiplomacySettings.Evil.parameters_hostile 644 parameters_neutral = DiplomacySettings.Evil.parameters_neutral 645 parameters_allied = DiplomacySettings.Evil.parameters_allied
646
647 648 -class BehaviorGood(BehaviorDiplomatic):
649 """ 650 Diplomatic behavior. 651 Good AI likes players that are: 652 - weaker 653 - smaller 654 Neutral towards: 655 - wealth 656 Dislikes: 657 - 658 """ 659 660 weights = DiplomacySettings.Good.weights 661 parameters_hostile = DiplomacySettings.Good.parameters_hostile 662 parameters_neutral = DiplomacySettings.Good.parameters_neutral 663 parameters_allied = DiplomacySettings.Good.parameters_allied
664
665 666 -class BehaviorNeutral(BehaviorDiplomatic):
667 """ 668 Diplomatic behavior. 669 Neutral AI likes players that are: 670 - wealthier 671 Neutral towards: 672 - strength 673 - size (favor bigger though) 674 Dislikes: 675 - 676 """ 677 678 weights = DiplomacySettings.Neutral.weights 679 parameters_hostile = DiplomacySettings.Neutral.parameters_hostile 680 parameters_neutral = DiplomacySettings.Neutral.parameters_neutral 681 parameters_allied = DiplomacySettings.Neutral.parameters_allied
682
683 684 -class BehaviorDebug(BehaviorComponent):
685
686 - def __init__(self, owner):
687 super().__init__(owner)
688
689 - def debug(self, **environment):
690 """ 691 For debugging purposes. 692 """ 693 694 return None
695
696 697 -class BehaviorRegularPirate(BehaviorComponent):
698 699 power_balance_threshold = 1.0 700
701 - def __init__(self, owner):
702 super().__init__(owner) 703 self._certainty['fighting_ships_in_sight'] = certainty_power_balance_exp 704 self._certainty['pirate_routine'] = self._certainty_pirate_routine
705
706 - def fighting_ships_in_sight(self, **environment):
707 """ 708 Attacks frigates only if they are enemies already and the power balance is advantageous. 709 """ 710 enemies = environment['enemies'] 711 ship_group = environment['ship_group'] 712 power_balance = environment['power_balance'] 713 714 if power_balance < self.power_balance_threshold: 715 BehaviorComponent.log.info('%s: Enemy ship was too strong, did not attack', self.__class__.__name__) 716 return 717 718 if not self.session.world.diplomacy.are_enemies(self.owner, enemies[0].owner): 719 BehaviorComponent.log.info('%s: Enemy ship was not hostile', self.__class__.__name__) 720 return 721 722 for ship in ship_group: 723 ship.attack(enemies[0]) 724 BehaviorComponent.log.info('%s: Attacked enemy ship', self.__class__.__name__)
725
726 - def _certainty_pirate_routine(self, **environment):
727 idle_ships = environment['idle_ships'] 728 if len(idle_ships) >= self.minimal_fleet_size: 729 return self.default_certainty 730 else: 731 return 0.0
732
733 - def pirate_routine(self, **environment):
734 """ 735 Strategy that spawns pirate's idle-sailing routine. 736 """ 737 idle_ships = environment['idle_ships'] 738 739 # Use a one-ship group: 740 idle_ships = idle_ships[:1] 741 742 mission = PirateRoutine.create(self.owner.strategy_manager.report_success, self.owner.strategy_manager.report_failure, idle_ships) 743 BehaviorComponent.log.info('BehaviorRegularPirate: pirate_routine request') 744 return mission
745
746 747 -class BehaviorAggressivePirate(BehaviorComponent):
748
749 - def __init__(self, owner):
750 super().__init__(owner) 751 self._certainty['fighting_ships_in_sight'] = certainty_power_balance_exp
752
753 - def fighting_ships_in_sight(self, **environment):
754 """ 755 Attacks frigates only if they are enemies. Does not care about power balance. 756 """ 757 758 enemies = environment['enemies'] 759 ship_group = environment['ship_group'] 760 761 if not self.session.world.diplomacy.are_enemies(self.owner, enemies[0].owner): 762 BehaviorComponent.log.info('%s: Enemy ship was not hostile', self.__class__.__name__) 763 return 764 765 target_ship = UnitManager.get_lowest_hp_ship(enemies) 766 for ship in ship_group: 767 ship.attack(target_ship) 768 BehaviorComponent.log.info('%s: Attacked enemy ship', self.__class__.__name__)
769
770 771 -class BehaviorBreakDiplomacy(BehaviorComponent):
772 """ 773 Temporary action for breaking diplomacy with other players. 774 """ 775
776 - def __init__(self, owner):
777 super().__init__(owner)
778
779 - def fighting_ships_in_sight(self, **environment):
780 enemies = environment['enemies'] 781 ship_group = environment['ship_group'] 782 783 if not self.session.world.diplomacy.are_enemies(self.owner, enemies[0].owner): 784 AddEnemyPair(self.owner, enemies[0].owner).execute(self.session) 785 BehaviorComponent.log.info('Player:{0!s} broke diplomacy with {1!s}' 786 .format(self.owner.name, enemies[0].owner.name))
787
788 789 -class BehaviorCoward(BehaviorComponent):
790
791 - def __init__(self, owner):
792 super().__init__(owner) 793 # Certainty here is a hyperbolic function from power_balance 794 # (higher power_balance -> lesser chance of doing nothing) 795 self._certainty['pirate_ships_in_sight'] = certainty_power_balance_inverse
796
797 - def pirate_ships_in_sight(self, **environment):
798 """Dummy action, do nothing really. 799 """ 800 BehaviorComponent.log.info('Pirates give me chills man.')
801
802 803 -def certainty_are_enemies(**environment):
804 """ 805 returns 0.0 if two players are enemies already, default certainty otherwise. 806 """ 807 enemies = environment['enemies'] 808 ship_group = environment['ship_group'] 809 810 player = ship_group[0].owner 811 enemy_player = enemies[0].owner 812 813 return 0.0 if player.session.world.diplomacy.are_enemies(player, enemy_player) else BehaviorComponent.default_certainty
814
815 816 -class BehaviorPirateHater(BehaviorComponent):
817
818 - def __init__(self, owner):
819 super().__init__(owner) 820 self._certainty['pirate_ships_in_sight'] = certainty_are_enemies
821
822 - def pirate_ships_in_sight(self, **environment):
823 """Breaks diplomacy and attacks pirates. 824 """ 825 enemies = environment['enemies'] 826 ship_group = environment['ship_group'] 827 power_balance = environment['power_balance'] 828 829 if not self.session.world.diplomacy.are_enemies(self.owner, enemies[0].owner): 830 AddEnemyPair(self.owner, enemies[0].owner).execute(self.session) 831 BehaviorComponent.log.info('I feel urgent need to wipe out them pirates.')
832