Module chillow.service.ai.search_tree_node
Expand source code
from dataclasses import dataclass
from typing import Type, Any, List, Tuple, Optional
from chillow.exceptions import InvalidPlayerMoveException
from chillow.model.action import Action
from chillow.model.game import Game
from chillow.model.player import Player
from chillow.service.game_service import GameService
@dataclass
class SearchTreeRoot(object):
"""A node of a search tree where no action of the viewed player is performed but by all others."""
_game: Game
def calculate_action(self, player: Player, player_ids_to_watch: List[int], combinations: List[Tuple[Any]], # noqa: C901
depth: int, turn_counter: int, root: bool, first_actions: List[Action], max_speed: int = 10,
randomize: bool = False) \
-> Optional[Action]:
"""Checks for an action that lets the player survive for the next rounds based on the parameters.
Args:
player: The viewed player
player_ids_to_watch: The ID of the player's which should be considered in this calculation.
combinations: The possible combinations for all actions.
depth: The amount of rounds to be calculated in the future.
turn_counter: The turn number.
root: Indicating whether this is the initial call to the starting node.
first_actions:
The actions that can be performed in the first simulated round.
If empty, all actions are possible.
max_speed: The maximum acceptable speed for the player.
randomize: Indicates whether the actions should be calculated in random order in the tree.
Returns:
An action that lets the player survive for the next rounds based on the parameters.
Raises:
AssertionError:
Depth is smaller than 1 or different amount of players to be watched than players in one combination.
"""
assert len(player_ids_to_watch) == len(combinations[0])
assert depth >= 1
if depth == 1:
for action in SearchTreeRoot.__get_actions(root, first_actions, randomize):
child = self.__create_child(player, action, turn_counter, max_speed)
if child is not None and child._game.get_player_by_id(player.id).active:
if SearchTreeRoot.__try_combinations_for_child(child, player, player_ids_to_watch, combinations,
turn_counter):
return child.get_action()
return None
for action in SearchTreeRoot.__get_actions(root, first_actions, randomize):
child = self.__create_child(player, action, turn_counter, max_speed)
if child is not None and child._game.get_player_by_id(player.id).active:
surviving_subtree = True
for combination in combinations:
node = SearchTreeRoot.__try_combination(child._game, player_ids_to_watch, combination, turn_counter)
if node._game.get_player_by_id(player.id).active:
node_action = node.calculate_action(player, player_ids_to_watch, combinations, depth - 1,
turn_counter + 1, False, first_actions, max_speed,
randomize)
if node_action is None:
surviving_subtree = False
break
else:
surviving_subtree = False
break
if surviving_subtree:
return child.get_action()
return None
@staticmethod
def __get_actions(root: bool, first_actions: List[Action], randomize: bool) -> List[Action]:
if root and first_actions is not None and len(first_actions) >= 1:
return first_actions
return Action.get_actions(randomize)
def __create_child(self, player: Player, action: Action, turn_counter: int, max_speed: int):
if player.speed == max_speed and action == Action.speed_up:
return
modified_game = self._game.copy()
game_service = GameService(modified_game)
game_service.turn.turn_ctr = turn_counter
SearchTreeRoot.__perform_simulation(game_service, action, modified_game.get_player_by_id(player.id))
game_service.check_and_set_died_players()
return SearchTreeNode(modified_game.copy(), action)
@staticmethod
def __try_combinations_for_child(child: Type['SearchTreeRoot'],
player: Player,
player_ids_to_watch: List[int],
combinations: List[Tuple[Action]],
turn_counter: int) -> bool:
for combination in combinations:
node = SearchTreeRoot.__try_combination(child._game, player_ids_to_watch, combination, turn_counter)
if not node._game.get_player_by_id(player.id).active:
return False
return True
@staticmethod
def __try_combination(game: Game, player_ids_to_watch: List[int], combination: Tuple[Action],
turn_counter: int):
modified_game = game.copy()
game_service = GameService(modified_game)
game_service.turn.turn_ctr = turn_counter
players = modified_game.get_players_by_ids(player_ids_to_watch)
for j in range(len(combination)):
action = combination[j]
player = players[j]
SearchTreeRoot.__perform_simulation(game_service, action, player)
game_service.check_and_set_died_players()
return SearchTreeRoot(modified_game.copy())
@staticmethod
def __perform_simulation(game_service: GameService, action: Action, player: Player):
if player.active:
try:
game_service.visited_cells_by_player[player.id] = \
game_service.get_and_visit_cells(player, action)
except InvalidPlayerMoveException:
game_service.set_player_inactive(player)
def get_action(self):
return None
@dataclass
class SearchTreeNode(SearchTreeRoot):
"""A node of a search tree where only an action of the viewed player is performed."""
__action: Action
def get_action(self):
return self.__action
Classes
class SearchTreeNode (_game: Game, _SearchTreeNode__action: Action)
-
A node of a search tree where only an action of the viewed player is performed.
Expand source code
@dataclass class SearchTreeNode(SearchTreeRoot): """A node of a search tree where only an action of the viewed player is performed.""" __action: Action def get_action(self): return self.__action
Ancestors
Methods
def get_action(self)
-
Expand source code
def get_action(self): return self.__action
Inherited members
class SearchTreeRoot (_game: Game)
-
A node of a search tree where no action of the viewed player is performed but by all others.
Expand source code
@dataclass class SearchTreeRoot(object): """A node of a search tree where no action of the viewed player is performed but by all others.""" _game: Game def calculate_action(self, player: Player, player_ids_to_watch: List[int], combinations: List[Tuple[Any]], # noqa: C901 depth: int, turn_counter: int, root: bool, first_actions: List[Action], max_speed: int = 10, randomize: bool = False) \ -> Optional[Action]: """Checks for an action that lets the player survive for the next rounds based on the parameters. Args: player: The viewed player player_ids_to_watch: The ID of the player's which should be considered in this calculation. combinations: The possible combinations for all actions. depth: The amount of rounds to be calculated in the future. turn_counter: The turn number. root: Indicating whether this is the initial call to the starting node. first_actions: The actions that can be performed in the first simulated round. If empty, all actions are possible. max_speed: The maximum acceptable speed for the player. randomize: Indicates whether the actions should be calculated in random order in the tree. Returns: An action that lets the player survive for the next rounds based on the parameters. Raises: AssertionError: Depth is smaller than 1 or different amount of players to be watched than players in one combination. """ assert len(player_ids_to_watch) == len(combinations[0]) assert depth >= 1 if depth == 1: for action in SearchTreeRoot.__get_actions(root, first_actions, randomize): child = self.__create_child(player, action, turn_counter, max_speed) if child is not None and child._game.get_player_by_id(player.id).active: if SearchTreeRoot.__try_combinations_for_child(child, player, player_ids_to_watch, combinations, turn_counter): return child.get_action() return None for action in SearchTreeRoot.__get_actions(root, first_actions, randomize): child = self.__create_child(player, action, turn_counter, max_speed) if child is not None and child._game.get_player_by_id(player.id).active: surviving_subtree = True for combination in combinations: node = SearchTreeRoot.__try_combination(child._game, player_ids_to_watch, combination, turn_counter) if node._game.get_player_by_id(player.id).active: node_action = node.calculate_action(player, player_ids_to_watch, combinations, depth - 1, turn_counter + 1, False, first_actions, max_speed, randomize) if node_action is None: surviving_subtree = False break else: surviving_subtree = False break if surviving_subtree: return child.get_action() return None @staticmethod def __get_actions(root: bool, first_actions: List[Action], randomize: bool) -> List[Action]: if root and first_actions is not None and len(first_actions) >= 1: return first_actions return Action.get_actions(randomize) def __create_child(self, player: Player, action: Action, turn_counter: int, max_speed: int): if player.speed == max_speed and action == Action.speed_up: return modified_game = self._game.copy() game_service = GameService(modified_game) game_service.turn.turn_ctr = turn_counter SearchTreeRoot.__perform_simulation(game_service, action, modified_game.get_player_by_id(player.id)) game_service.check_and_set_died_players() return SearchTreeNode(modified_game.copy(), action) @staticmethod def __try_combinations_for_child(child: Type['SearchTreeRoot'], player: Player, player_ids_to_watch: List[int], combinations: List[Tuple[Action]], turn_counter: int) -> bool: for combination in combinations: node = SearchTreeRoot.__try_combination(child._game, player_ids_to_watch, combination, turn_counter) if not node._game.get_player_by_id(player.id).active: return False return True @staticmethod def __try_combination(game: Game, player_ids_to_watch: List[int], combination: Tuple[Action], turn_counter: int): modified_game = game.copy() game_service = GameService(modified_game) game_service.turn.turn_ctr = turn_counter players = modified_game.get_players_by_ids(player_ids_to_watch) for j in range(len(combination)): action = combination[j] player = players[j] SearchTreeRoot.__perform_simulation(game_service, action, player) game_service.check_and_set_died_players() return SearchTreeRoot(modified_game.copy()) @staticmethod def __perform_simulation(game_service: GameService, action: Action, player: Player): if player.active: try: game_service.visited_cells_by_player[player.id] = \ game_service.get_and_visit_cells(player, action) except InvalidPlayerMoveException: game_service.set_player_inactive(player) def get_action(self): return None
Subclasses
Methods
def calculate_action(self, player: Player, player_ids_to_watch: List[int], combinations: List[Tuple[Any]], depth: int, turn_counter: int, root: bool, first_actions: List[Action], max_speed: int = 10, randomize: bool = False) ‑> Optional[Action]
-
Checks for an action that lets the player survive for the next rounds based on the parameters.
Args
player
- The viewed player
player_ids_to_watch
- The ID of the player's which should be considered in this calculation.
combinations
- The possible combinations for all actions.
depth
- The amount of rounds to be calculated in the future.
turn_counter
- The turn number.
root
- Indicating whether this is the initial call to the starting node.
- first_actions:
- The actions that can be performed in the first simulated round.
- If empty, all actions are possible.
max_speed
- The maximum acceptable speed for the player.
randomize
- Indicates whether the actions should be calculated in random order in the tree.
Returns
An action that lets the player survive for the next rounds based on the parameters.
Raises
AssertionError: Depth is smaller than 1 or different amount of players to be watched than players in one combination.
Expand source code
def calculate_action(self, player: Player, player_ids_to_watch: List[int], combinations: List[Tuple[Any]], # noqa: C901 depth: int, turn_counter: int, root: bool, first_actions: List[Action], max_speed: int = 10, randomize: bool = False) \ -> Optional[Action]: """Checks for an action that lets the player survive for the next rounds based on the parameters. Args: player: The viewed player player_ids_to_watch: The ID of the player's which should be considered in this calculation. combinations: The possible combinations for all actions. depth: The amount of rounds to be calculated in the future. turn_counter: The turn number. root: Indicating whether this is the initial call to the starting node. first_actions: The actions that can be performed in the first simulated round. If empty, all actions are possible. max_speed: The maximum acceptable speed for the player. randomize: Indicates whether the actions should be calculated in random order in the tree. Returns: An action that lets the player survive for the next rounds based on the parameters. Raises: AssertionError: Depth is smaller than 1 or different amount of players to be watched than players in one combination. """ assert len(player_ids_to_watch) == len(combinations[0]) assert depth >= 1 if depth == 1: for action in SearchTreeRoot.__get_actions(root, first_actions, randomize): child = self.__create_child(player, action, turn_counter, max_speed) if child is not None and child._game.get_player_by_id(player.id).active: if SearchTreeRoot.__try_combinations_for_child(child, player, player_ids_to_watch, combinations, turn_counter): return child.get_action() return None for action in SearchTreeRoot.__get_actions(root, first_actions, randomize): child = self.__create_child(player, action, turn_counter, max_speed) if child is not None and child._game.get_player_by_id(player.id).active: surviving_subtree = True for combination in combinations: node = SearchTreeRoot.__try_combination(child._game, player_ids_to_watch, combination, turn_counter) if node._game.get_player_by_id(player.id).active: node_action = node.calculate_action(player, player_ids_to_watch, combinations, depth - 1, turn_counter + 1, False, first_actions, max_speed, randomize) if node_action is None: surviving_subtree = False break else: surviving_subtree = False break if surviving_subtree: return child.get_action() return None
def get_action(self)
-
Expand source code
def get_action(self): return None