Module chillow.service.ai

Expand source code
from chillow.service.ai.not_killing_itself_ai import NotKillingItselfAI, AIOptions
from chillow.service.ai.pathfinding_ai import PathfindingAI
from chillow.service.ai.pathfinding_search_tree_ai import PathfindingSearchTreeAI
from chillow.service.ai.random_ai import RandomAI, RandomWaitingAI
from chillow.service.ai.search_tree_ai import SearchTreeAI
from chillow.service.ai.search_tree_pathfinding_ai import SearchTreePathfindingAI

__all__ = ['NotKillingItselfAI', 'AIOptions', 'PathfindingAI', 'PathfindingSearchTreeAI', 'RandomAI',
           'RandomWaitingAI', 'SearchTreeAI', 'SearchTreePathfindingAI']

Sub-modules

chillow.service.ai.artificial_intelligence
chillow.service.ai.not_killing_itself_ai
chillow.service.ai.pathfinding_ai
chillow.service.ai.pathfinding_search_tree_ai
chillow.service.ai.random_ai
chillow.service.ai.search_tree_ai
chillow.service.ai.search_tree_node
chillow.service.ai.search_tree_pathfinding_ai

Classes

class AIOptions (value, names=None, *, module=None, qualname=None, type=None, start=1)

Enumeration that holds possible options for the AIs.

Expand source code
class AIOptions(Enum):
    """Enumeration that holds possible options for the AIs."""
    max_distance = range(1)

Ancestors

  • enum.Enum

Class variables

var max_distance
class NotKillingItselfAI (player: Player, options: List[AIOptions], max_speed: int, max_worse_distance: int, depth: int)

AI implementation to choose an action that simply does not kill the player for the next rounds.

It does not consider the opponent's player actions.

Attributes

player
The player associated with this AI.

Creates a new object of the NotKillingItselfAI.

Args

player
The player assigned to the AI.
options
List of possible options to change the behavior of the AI.
max_speed
The maximum speed the AI can reach.
max_worse_distance
A tolerance, whereby more than just the best action is calculated. Actions which are
worse, but within this tolerance, are also considered.
depth
Number of player actions that are looked into the future.
Expand source code
class NotKillingItselfAI(ArtificialIntelligence):
    """AI implementation to choose an action that simply does not kill the player for the next rounds.

    It does not consider the opponent's player actions.

    Attributes:
        player: The player associated with this AI.
    """

    def __init__(self, player: Player, options: List[AIOptions], max_speed: int, max_worse_distance: int,
                 depth: int):
        """Creates a new object of the NotKillingItselfAI.

        Args:
            player: The player assigned to the AI.
            options: List of possible options to change the behavior of the AI.
            max_speed: The maximum speed the AI can reach.
            max_worse_distance: A tolerance, whereby more than just the best action is calculated. Actions which are
            worse, but within this tolerance, are also considered.
            depth: Number of player actions that are looked into the future.
        """
        super().__init__(player, max_speed)
        self.__options = options
        self.__max_worse_distance = max_worse_distance

        assert depth > 0, "depth must be greater than 0"
        self.__depth = depth

    def get_information(self) -> str:
        """See base class."""
        return (super().get_information() + ", max_worse_distance=" + str(self.__max_worse_distance)
                + ", depth=" + str(self.__depth))

    def create_next_action(self, game: Game, return_value: Value):
        """See base class."""
        self._turn_ctr += 1

        game_service = GameService(game)
        game_service.turn.turn_ctr = self._turn_ctr

        surviving_actions = self.find_surviving_actions_with_best_depth(game_service)
        if AIOptions.max_distance in self.__options:
            max_distance_actions = self.calc_action_with_max_distance_to_visited_cells(game_service, surviving_actions)
            action = choice(max_distance_actions) if max_distance_actions is not None and len(
                max_distance_actions) > 0 else Action.change_nothing
        else:
            action = choice(surviving_actions) if surviving_actions is not None and len(
                surviving_actions) > 0 else Action.change_nothing

        return_value.value = action.get_index()

    def calc_action_with_max_distance_to_visited_cells(self, game_service: GameService,
                                                       actions: List[Action]) -> List[Action]:
        """Calculates a list of actions that have the property to have as many free cells as possible in front of them
        while running straight after the action has been executed.

        Args:
            game_service: The game service used for simulation of actions.
            actions: The actions to be checked

        Returns:
            List of best actions with the property having as many free cells as possible in front of the player.
        """
        max_straight_distance = 0
        best_actions: Dict[Action, int] = {}
        for action in actions:
            gs_copy = copy.deepcopy(game_service)
            try:
                player = gs_copy.game.get_player_by_id(self.player.id)
                gs_copy.visited_cells_by_player[player.id] = gs_copy.get_and_visit_cells(player, action)

                straight_distance = 0
                horizontal_multiplier, vertical_multiplier = GameService.get_horizontal_and_vertical_multiplier(player)

                for i in range(max(gs_copy.game.height, gs_copy.game.width)):
                    x = player.x + (i + 1) * horizontal_multiplier
                    y = player.y + (i + 1) * vertical_multiplier
                    if x in range(gs_copy.game.width) and y in range(gs_copy.game.height) and (
                            gs_copy.game.cells[y][x].players is None or len(gs_copy.game.cells[y][x].players) == 0):
                        straight_distance += 1
                    else:
                        break

                if len(best_actions) == 0 or straight_distance > max_straight_distance:
                    max_straight_distance = straight_distance
                    best_actions[action] = straight_distance
                    updated_best_actions: Dict[Action, int] = {}
                    for (act, dist) in best_actions.items():  # new max_straight_distance. Remove worth options
                        if dist >= max_straight_distance - self.__max_worse_distance:
                            updated_best_actions[act] = dist
                    best_actions = updated_best_actions
                elif straight_distance >= max_straight_distance - self.__max_worse_distance:  # still good option
                    best_actions[action] = straight_distance
            except InvalidPlayerMoveException as ex:
                logging.warning(ex)
                continue

        return list(best_actions.keys())

    def find_surviving_actions(self, game_service: GameService, depth: int) -> List[Action]:
        """Finds all actions that will let the player survive for the next rounds.

        Args:
            game_service: The game service used for simulation of actions.
            depth: The number of rounds the player should survive at least.

        Returns:
            Actions that will not kill the player in the next rounds.
        """
        result: List[Action] = []
        for action in Action:
            gs_copy = pickle.loads(pickle.dumps(game_service))
            try:
                player = gs_copy.game.get_player_by_id(self.player.id)
                if player.speed == self._max_speed and action == Action.speed_up:
                    continue
                gs_copy.visited_cells_by_player[player.id] = gs_copy.get_and_visit_cells(player, action)
            except InvalidPlayerMoveException:
                continue
            gs_copy.check_and_set_died_players()
            if player.active:
                interim_result = []
                if depth > 1:
                    # recursive call to look further into the future
                    interim_result = self.find_surviving_actions(gs_copy, depth - 1)

                if len(interim_result) > 0 or depth == 1:
                    result += [action]

        return result

    def find_surviving_actions_with_best_depth(self, game_service: GameService) -> List[Action]:
        """Finds all actions that won't kill the player in the next rounds.
        The number of pre-calculated player moves is reduced until surviving actions are found.

        Args:
            game_service: The game service used for simulation of actions.

        Returns:
            Actions that will not kill the player in the next rounds.
        """
        result: List[Action] = []
        for current_depth in reversed(range(1, self.__depth + 1)):
            result = self.find_surviving_actions(game_service, current_depth)
            if len(result) > 0:
                break

        return result

Ancestors

Subclasses

Methods

def calc_action_with_max_distance_to_visited_cells(self, game_service: GameService, actions: List[Action]) ‑> List[Action]

Calculates a list of actions that have the property to have as many free cells as possible in front of them while running straight after the action has been executed.

Args

game_service
The game service used for simulation of actions.
actions
The actions to be checked

Returns

List of best actions with the property having as many free cells as possible in front of the player.

Expand source code
def calc_action_with_max_distance_to_visited_cells(self, game_service: GameService,
                                                   actions: List[Action]) -> List[Action]:
    """Calculates a list of actions that have the property to have as many free cells as possible in front of them
    while running straight after the action has been executed.

    Args:
        game_service: The game service used for simulation of actions.
        actions: The actions to be checked

    Returns:
        List of best actions with the property having as many free cells as possible in front of the player.
    """
    max_straight_distance = 0
    best_actions: Dict[Action, int] = {}
    for action in actions:
        gs_copy = copy.deepcopy(game_service)
        try:
            player = gs_copy.game.get_player_by_id(self.player.id)
            gs_copy.visited_cells_by_player[player.id] = gs_copy.get_and_visit_cells(player, action)

            straight_distance = 0
            horizontal_multiplier, vertical_multiplier = GameService.get_horizontal_and_vertical_multiplier(player)

            for i in range(max(gs_copy.game.height, gs_copy.game.width)):
                x = player.x + (i + 1) * horizontal_multiplier
                y = player.y + (i + 1) * vertical_multiplier
                if x in range(gs_copy.game.width) and y in range(gs_copy.game.height) and (
                        gs_copy.game.cells[y][x].players is None or len(gs_copy.game.cells[y][x].players) == 0):
                    straight_distance += 1
                else:
                    break

            if len(best_actions) == 0 or straight_distance > max_straight_distance:
                max_straight_distance = straight_distance
                best_actions[action] = straight_distance
                updated_best_actions: Dict[Action, int] = {}
                for (act, dist) in best_actions.items():  # new max_straight_distance. Remove worth options
                    if dist >= max_straight_distance - self.__max_worse_distance:
                        updated_best_actions[act] = dist
                best_actions = updated_best_actions
            elif straight_distance >= max_straight_distance - self.__max_worse_distance:  # still good option
                best_actions[action] = straight_distance
        except InvalidPlayerMoveException as ex:
            logging.warning(ex)
            continue

    return list(best_actions.keys())
def create_next_action(self, game: Game, return_value: >)

See base class.

Expand source code
def create_next_action(self, game: Game, return_value: Value):
    """See base class."""
    self._turn_ctr += 1

    game_service = GameService(game)
    game_service.turn.turn_ctr = self._turn_ctr

    surviving_actions = self.find_surviving_actions_with_best_depth(game_service)
    if AIOptions.max_distance in self.__options:
        max_distance_actions = self.calc_action_with_max_distance_to_visited_cells(game_service, surviving_actions)
        action = choice(max_distance_actions) if max_distance_actions is not None and len(
            max_distance_actions) > 0 else Action.change_nothing
    else:
        action = choice(surviving_actions) if surviving_actions is not None and len(
            surviving_actions) > 0 else Action.change_nothing

    return_value.value = action.get_index()
def find_surviving_actions(self, game_service: GameService, depth: int) ‑> List[Action]

Finds all actions that will let the player survive for the next rounds.

Args

game_service
The game service used for simulation of actions.
depth
The number of rounds the player should survive at least.

Returns

Actions that will not kill the player in the next rounds.

Expand source code
def find_surviving_actions(self, game_service: GameService, depth: int) -> List[Action]:
    """Finds all actions that will let the player survive for the next rounds.

    Args:
        game_service: The game service used for simulation of actions.
        depth: The number of rounds the player should survive at least.

    Returns:
        Actions that will not kill the player in the next rounds.
    """
    result: List[Action] = []
    for action in Action:
        gs_copy = pickle.loads(pickle.dumps(game_service))
        try:
            player = gs_copy.game.get_player_by_id(self.player.id)
            if player.speed == self._max_speed and action == Action.speed_up:
                continue
            gs_copy.visited_cells_by_player[player.id] = gs_copy.get_and_visit_cells(player, action)
        except InvalidPlayerMoveException:
            continue
        gs_copy.check_and_set_died_players()
        if player.active:
            interim_result = []
            if depth > 1:
                # recursive call to look further into the future
                interim_result = self.find_surviving_actions(gs_copy, depth - 1)

            if len(interim_result) > 0 or depth == 1:
                result += [action]

    return result
def find_surviving_actions_with_best_depth(self, game_service: GameService) ‑> List[Action]

Finds all actions that won't kill the player in the next rounds. The number of pre-calculated player moves is reduced until surviving actions are found.

Args

game_service
The game service used for simulation of actions.

Returns

Actions that will not kill the player in the next rounds.

Expand source code
def find_surviving_actions_with_best_depth(self, game_service: GameService) -> List[Action]:
    """Finds all actions that won't kill the player in the next rounds.
    The number of pre-calculated player moves is reduced until surviving actions are found.

    Args:
        game_service: The game service used for simulation of actions.

    Returns:
        Actions that will not kill the player in the next rounds.
    """
    result: List[Action] = []
    for current_depth in reversed(range(1, self.__depth + 1)):
        result = self.find_surviving_actions(game_service, current_depth)
        if len(result) > 0:
            break

    return result
def get_information(self) ‑> str

See base class.

Expand source code
def get_information(self) -> str:
    """See base class."""
    return (super().get_information() + ", max_worse_distance=" + str(self.__max_worse_distance)
            + ", depth=" + str(self.__depth))
class PathfindingAI (player: Player, max_speed: int, count_paths_to_check: int)

AI implementation that chooses actions which will allow it to survive a certain number of moves.

It does not consider enemy actions. Furthermore, the AI avoids running into too small areas or dead ends.

Attributes

player
The player associated with this AI.

Creates a new object of the PathfindingAI.

Args

player
The player assigned to the AI.
max_speed
The maximum speed the AI can reach.
count_paths_to_check
The number of paths used to avoid dead ends.
Expand source code
class PathfindingAI(NotKillingItselfAI):
    """AI implementation that chooses actions which will allow it to survive a certain number of moves.

    It does not consider enemy actions. Furthermore, the AI avoids running into too small areas or dead ends.

    Attributes:
        player: The player associated with this AI.
    """

    def __init__(self, player: Player, max_speed: int, count_paths_to_check: int):
        """Creates a new object of the PathfindingAI.

        Args:
            player: The player assigned to the AI.
            max_speed: The maximum speed the AI can reach.
            count_paths_to_check: The number of paths used to avoid dead ends.
        """
        super().__init__(player, [], max_speed, 0, 3)
        self.__count_paths_to_check = count_paths_to_check

    def get_information(self) -> str:
        """See base class."""
        return "max_speed=" + str(self._max_speed) \
               + ", count_paths_to_check=" + str(self.__count_paths_to_check)

    def create_next_action(self, game: Game, return_value: Value):
        """See base class."""
        self._turn_ctr += 1
        actions = self.create_next_actions_ranked(game)
        action = actions[0][0] if actions is not None and len(actions) > 0 else Action.get_random_action()
        return_value.value = action.get_index()

    def create_next_actions_ranked(self, game: Game) -> Optional[List[Tuple[Action, int]]]:
        """Calculates all actions with the number of reachable paths, with which the AI won't lose in the next turn.

        Args:
            game: The game object in which the AI is located and which contains the current status of the game.

        Returns:
            A list with actions and the corresponding number of accessible paths.
        """
        game_service = GameService(game)
        game_service.turn.turn_ctr = self._turn_ctr

        surviving_actions = self.find_surviving_actions_with_best_depth(game_service)

        return self.find_actions_by_best_path_connection(surviving_actions, game)

    def find_actions_by_best_path_connection(self, actions: List[Action], game: Game) -> Optional[
            List[Tuple[Action, int]]]:
        """ Calculates for the passed actions how many paths are still accessible after the execution of the action.

        For this purpose, points are randomly generated on the playing field and an algorithm for finding paths is
        used to check whether the point can be reached.

        Args:
            actions: List of actions to check.
            game: The game that contains the current state of the game.

        Returns:
            List of actions with the accessible paths.
        """
        if actions is None or len(actions) == 0:
            return None
        # shuffle the actions, so that different actions are chosen if they have the same quality and the AI is not so
        # easily predictable.
        shuffle(actions)
        actions_with_possible_paths: List[Tuple[Action, int]] = []
        free_cells_for_pathfinding = self.get_random_free_cells_from_playground(game)

        path_finder = BestFirst(diagonal_movement=DiagonalMovement.never)

        for action in actions:
            game_copy = game.copy()
            game_service = GameService(game_copy)
            try:
                player = game_service.game.get_player_by_id(self.player.id)
                game_service.visited_cells_by_player[player.id] = game_service.get_and_visit_cells(player, action)
            except InvalidPlayerMoveException:
                continue

            matrix = game_copy.translate_cell_matrix_to_pathfinding_matrix()
            current_possible_paths = 0
            length_free_cells = len(free_cells_for_pathfinding)
            for i in range(length_free_cells):
                grid = Grid(matrix=matrix)
                start = grid.node(player.x, player.y)
                end = grid.node(free_cells_for_pathfinding[i][0], free_cells_for_pathfinding[i][1])
                path, _ = path_finder.find_path(start, end, grid)
                if len(path) > 0:  # a path exists
                    current_possible_paths += 1

            actions_with_possible_paths.append((action, current_possible_paths))
        # Action with most accessible paths at index 0
        actions_with_possible_paths.sort(key=operator.itemgetter(1), reverse=True)
        return actions_with_possible_paths

    def get_random_free_cells_from_playground(self, game: Game) -> List[Tuple[int, int]]:
        """Calculates up to count_paths_to_check many points of all free fields on the playing field.

        Args:
            game: The game that contains the current state of the game.

        Returns:
            List of coordinates with x- and y-value.
        """
        free_cells: List[(int, int)] = []
        for x in range(game.width):
            for y in range(game.height):
                if game.cells[y][x].players is None or len(game.cells[y][x].players) == 0:
                    free_cells.append((x, y))
        shuffle(free_cells)  # shuffle the coordinates to get a random distribution
        return free_cells[:min(self.__count_paths_to_check, len(free_cells))]

    def _get_count_paths_to_check(self) -> int:
        return self.__count_paths_to_check

Ancestors

Subclasses

Methods

def create_next_actions_ranked(self, game: Game) ‑> Optional[List[Tuple[Action, int]]]

Calculates all actions with the number of reachable paths, with which the AI won't lose in the next turn.

Args

game
The game object in which the AI is located and which contains the current status of the game.

Returns

A list with actions and the corresponding number of accessible paths.

Expand source code
def create_next_actions_ranked(self, game: Game) -> Optional[List[Tuple[Action, int]]]:
    """Calculates all actions with the number of reachable paths, with which the AI won't lose in the next turn.

    Args:
        game: The game object in which the AI is located and which contains the current status of the game.

    Returns:
        A list with actions and the corresponding number of accessible paths.
    """
    game_service = GameService(game)
    game_service.turn.turn_ctr = self._turn_ctr

    surviving_actions = self.find_surviving_actions_with_best_depth(game_service)

    return self.find_actions_by_best_path_connection(surviving_actions, game)
def find_actions_by_best_path_connection(self, actions: List[Action], game: Game) ‑> Optional[List[Tuple[Action, int]]]

Calculates for the passed actions how many paths are still accessible after the execution of the action.

For this purpose, points are randomly generated on the playing field and an algorithm for finding paths is used to check whether the point can be reached.

Args

actions
List of actions to check.
game
The game that contains the current state of the game.

Returns

List of actions with the accessible paths.

Expand source code
def find_actions_by_best_path_connection(self, actions: List[Action], game: Game) -> Optional[
        List[Tuple[Action, int]]]:
    """ Calculates for the passed actions how many paths are still accessible after the execution of the action.

    For this purpose, points are randomly generated on the playing field and an algorithm for finding paths is
    used to check whether the point can be reached.

    Args:
        actions: List of actions to check.
        game: The game that contains the current state of the game.

    Returns:
        List of actions with the accessible paths.
    """
    if actions is None or len(actions) == 0:
        return None
    # shuffle the actions, so that different actions are chosen if they have the same quality and the AI is not so
    # easily predictable.
    shuffle(actions)
    actions_with_possible_paths: List[Tuple[Action, int]] = []
    free_cells_for_pathfinding = self.get_random_free_cells_from_playground(game)

    path_finder = BestFirst(diagonal_movement=DiagonalMovement.never)

    for action in actions:
        game_copy = game.copy()
        game_service = GameService(game_copy)
        try:
            player = game_service.game.get_player_by_id(self.player.id)
            game_service.visited_cells_by_player[player.id] = game_service.get_and_visit_cells(player, action)
        except InvalidPlayerMoveException:
            continue

        matrix = game_copy.translate_cell_matrix_to_pathfinding_matrix()
        current_possible_paths = 0
        length_free_cells = len(free_cells_for_pathfinding)
        for i in range(length_free_cells):
            grid = Grid(matrix=matrix)
            start = grid.node(player.x, player.y)
            end = grid.node(free_cells_for_pathfinding[i][0], free_cells_for_pathfinding[i][1])
            path, _ = path_finder.find_path(start, end, grid)
            if len(path) > 0:  # a path exists
                current_possible_paths += 1

        actions_with_possible_paths.append((action, current_possible_paths))
    # Action with most accessible paths at index 0
    actions_with_possible_paths.sort(key=operator.itemgetter(1), reverse=True)
    return actions_with_possible_paths
def get_random_free_cells_from_playground(self, game: Game) ‑> List[Tuple[int, int]]

Calculates up to count_paths_to_check many points of all free fields on the playing field.

Args

game
The game that contains the current state of the game.

Returns

List of coordinates with x- and y-value.

Expand source code
def get_random_free_cells_from_playground(self, game: Game) -> List[Tuple[int, int]]:
    """Calculates up to count_paths_to_check many points of all free fields on the playing field.

    Args:
        game: The game that contains the current state of the game.

    Returns:
        List of coordinates with x- and y-value.
    """
    free_cells: List[(int, int)] = []
    for x in range(game.width):
        for y in range(game.height):
            if game.cells[y][x].players is None or len(game.cells[y][x].players) == 0:
                free_cells.append((x, y))
    shuffle(free_cells)  # shuffle the coordinates to get a random distribution
    return free_cells[:min(self.__count_paths_to_check, len(free_cells))]

Inherited members

class PathfindingSearchTreeAI (player: Player, max_speed: int, count_paths_to_check: int, depth: int, paths_tolerance: float = 0.75, distance_to_check: int = 0)

This AI combines the PathfindingAI and the SearchTreeAI by favoring the former.

Therefore it ranks all actions based on the PathfindingAI and checks finds the first surviving action with the SearchTreeAI.

Attributes

player
The player associated with this AI.

Creates a new object of the PathfindingSearchTreeAI.

Args

player
The player assigned to the AI.
max_speed
The maximum speed the AI can reach.
count_paths_to_check
The number of paths used to avoid dead ends.
depth
Depth pre-calculating actions.
paths_tolerance
A tolerance, whereby more than just the best action is calculated. Actions which are worse, but within this tolerance, are also considered. depth: Number of player actions that are looked into the future.
distance_to_check
Distance an enemy player is allowed to be at maximum distance, so that he is taken into account in the calculations.
Expand source code
class PathfindingSearchTreeAI(PathfindingAI, SearchTreeAI):
    """This AI combines the PathfindingAI and the SearchTreeAI by favoring the former.

    Therefore it ranks all actions based on the PathfindingAI and checks finds the first surviving action with the
    SearchTreeAI.

    Attributes:
        player: The player associated with this AI.
    """

    def __init__(self, player: Player, max_speed: int, count_paths_to_check: int, depth: int,
                 paths_tolerance: float = 0.75, distance_to_check: int = 0):
        """Creates a new object of the PathfindingSearchTreeAI.

        Args:
            player: The player assigned to the AI.
            max_speed: The maximum speed the AI can reach.
            count_paths_to_check: The number of paths used to avoid dead ends.
            depth: Depth pre-calculating actions.
            paths_tolerance: A tolerance, whereby more than just the best action is calculated. Actions which are
                worse, but within this tolerance, are also considered.
                depth: Number of player actions that are looked into the future.
            distance_to_check: Distance an enemy player is allowed to be at maximum distance, so that he is taken into
                account in the calculations.
        """
        PathfindingAI.__init__(self, player, max_speed, count_paths_to_check)
        SearchTreeAI.__init__(self, player, depth, max_speed, distance_to_check=distance_to_check)
        self.__paths_tolerance = paths_tolerance

    def get_information(self) -> str:
        """See base class."""
        return "max_speed=" + str(self._max_speed) \
               + ", paths_tolerance=" + str(self.__paths_tolerance) \
               + ", count_paths_to_check=" + str(self._get_count_paths_to_check()) \
               + ", depth=" + str(self._get_depth()) \
               + ", distance_to_check=" + str(self._get_distance_to_check())

    def create_next_action(self, game: Game, return_value: Value):
        """See base class."""
        self._turn_ctr += 1

        pathfinding_actions = self.create_next_actions_ranked(game)
        self.set_best_action(pathfinding_actions, [], return_value)
        search_tree_actions = self.create_all_next_surviving_actions(game)
        self.set_best_action(pathfinding_actions, search_tree_actions, return_value)

    def set_best_action(self, pathfinding_actions: List[Tuple[Action, int]], search_tree_actions: List[Action],
                        return_value: Value):
        """Saves the best action from the list of actions from PathfindingAI and SearchTreeAI.

        Args:
            pathfinding_actions: List of actions calculated by PathfindingAI.
            search_tree_actions: List of actions calculated by SearchTreeAI
            return_value: Object to save the result of the calculation.
        """
        best_action = self.get_best_action(pathfinding_actions, search_tree_actions)
        return_value.value = best_action.get_index() if best_action is not None else return_value.value

    def get_best_action(self, pathfinding_actions: List[Tuple[Action, int]],
                        search_tree_actions: List[Action]) -> Optional[Action]:
        """Calculates the best action from the list of actions from PathfindingAI and SearchTreeAI.

        Args:
            pathfinding_actions: List of actions calculated by PathfindingAI.
            search_tree_actions: List of actions calculated by SearchTreeAI

        Returns:
            Best action if there is any.
        """
        if search_tree_actions is None or len(search_tree_actions) == 0:
            if pathfinding_actions is not None and len(pathfinding_actions) > 0:
                return pathfinding_actions[0][0]
            return None
        elif pathfinding_actions is None or len(pathfinding_actions) == 0:
            return search_tree_actions[0]

        for (action, possible_paths) in pathfinding_actions:
            if action in search_tree_actions:
                if possible_paths == pathfinding_actions[0][1]:
                    return action  # best path and surviving guaranteed
                elif possible_paths >= pathfinding_actions[0][1] * self.__paths_tolerance:
                    return action  # good path and surviving guaranteed
                else:
                    break

        return pathfinding_actions[0][0]

Ancestors

Methods

def get_best_action(self, pathfinding_actions: List[Tuple[Action, int]], search_tree_actions: List[Action]) ‑> Optional[Action]

Calculates the best action from the list of actions from PathfindingAI and SearchTreeAI.

Args

pathfinding_actions
List of actions calculated by PathfindingAI.
search_tree_actions
List of actions calculated by SearchTreeAI

Returns

Best action if there is any.

Expand source code
def get_best_action(self, pathfinding_actions: List[Tuple[Action, int]],
                    search_tree_actions: List[Action]) -> Optional[Action]:
    """Calculates the best action from the list of actions from PathfindingAI and SearchTreeAI.

    Args:
        pathfinding_actions: List of actions calculated by PathfindingAI.
        search_tree_actions: List of actions calculated by SearchTreeAI

    Returns:
        Best action if there is any.
    """
    if search_tree_actions is None or len(search_tree_actions) == 0:
        if pathfinding_actions is not None and len(pathfinding_actions) > 0:
            return pathfinding_actions[0][0]
        return None
    elif pathfinding_actions is None or len(pathfinding_actions) == 0:
        return search_tree_actions[0]

    for (action, possible_paths) in pathfinding_actions:
        if action in search_tree_actions:
            if possible_paths == pathfinding_actions[0][1]:
                return action  # best path and surviving guaranteed
            elif possible_paths >= pathfinding_actions[0][1] * self.__paths_tolerance:
                return action  # good path and surviving guaranteed
            else:
                break

    return pathfinding_actions[0][0]
def set_best_action(self, pathfinding_actions: List[Tuple[Action, int]], search_tree_actions: List[Action], return_value: >)

Saves the best action from the list of actions from PathfindingAI and SearchTreeAI.

Args

pathfinding_actions
List of actions calculated by PathfindingAI.
search_tree_actions
List of actions calculated by SearchTreeAI
return_value
Object to save the result of the calculation.
Expand source code
def set_best_action(self, pathfinding_actions: List[Tuple[Action, int]], search_tree_actions: List[Action],
                    return_value: Value):
    """Saves the best action from the list of actions from PathfindingAI and SearchTreeAI.

    Args:
        pathfinding_actions: List of actions calculated by PathfindingAI.
        search_tree_actions: List of actions calculated by SearchTreeAI
        return_value: Object to save the result of the calculation.
    """
    best_action = self.get_best_action(pathfinding_actions, search_tree_actions)
    return_value.value = best_action.get_index() if best_action is not None else return_value.value

Inherited members

class RandomAI (player: Player, max_speed: int = 10)

AI that randomly chooses an action ignoring the state of the game.

Attributes

player
The player associated with this AI.

Creates a new object of an AI.

Args

player
The player assigned to the AI.
max_speed
The maximum speed the AI can reach.
Expand source code
class RandomAI(ArtificialIntelligence):
    """AI that randomly chooses an action ignoring the state of the game.

    Attributes:
        player: The player associated with this AI.
    """

    def create_next_action(self, game: Game, return_value: Value):
        """See base class."""
        self._turn_ctr += 1
        action = Action.get_random_action()
        return_value.value = action.get_index()

    def get_information(self) -> str:
        """See base class."""
        return ""

Ancestors

Subclasses

Methods

def create_next_action(self, game: Game, return_value: >)

See base class.

Expand source code
def create_next_action(self, game: Game, return_value: Value):
    """See base class."""
    self._turn_ctr += 1
    action = Action.get_random_action()
    return_value.value = action.get_index()
def get_information(self) ‑> str

See base class.

Expand source code
def get_information(self) -> str:
    """See base class."""
    return ""
class RandomWaitingAI (player: Player, max_speed: int = 10)

AI that randomly chooses an action ignoring the state of the game and waits five seconds.

Attributes

player
The player associated with this AI.

Creates a new object of an AI.

Args

player
The player assigned to the AI.
max_speed
The maximum speed the AI can reach.
Expand source code
class RandomWaitingAI(RandomAI):
    """AI that randomly chooses an action ignoring the state of the game and waits five seconds.

    Attributes:
        player: The player associated with this AI.
    """

    def create_next_action(self, game: Game, return_value: Value):
        time.sleep(5)
        super().create_next_action(game, return_value)

Ancestors

Inherited members

class SearchTreeAI (player: Player, depth: int, max_speed: int = 10, randomize: bool = False, distance_to_check: int = 0)

The SearchTreeAI tries to create a tree by simulating different actions for all player for the next rounds.

If there is an initial action that lets the player survive for the next rounds not depending on which action the other players will make, this action will be chosen.

Attributes

player
The player associated with this AI.

Creates a new object of the SearchTreeAI.

Args

player
The player assigned to the AI.
max_speed
The maximum speed the AI can reach.
depth
Depth pre-calculating actions.
randomize
Indicating whether to calculate actions in tree in random order.

distance_to_check: Distance an enemy player is allowed to be at maximum distance, so that he is taken into account in the calculations.

Expand source code
class SearchTreeAI(ArtificialIntelligence):
    """The SearchTreeAI tries to create a tree by simulating different actions for all player for the next rounds.

    If there is an initial action that lets the player survive for the next rounds not depending on which action
    the other players will make, this action will be chosen.

    Attributes:
        player: The player associated with this AI.
    """

    def __init__(self, player: Player, depth: int, max_speed: int = 10, randomize: bool = False,
                 distance_to_check: int = 0):
        """ Creates a new object of the SearchTreeAI.

        Args:
            player: The player assigned to the AI.
            max_speed: The maximum speed the AI can reach.
            depth: Depth pre-calculating actions.
            randomize: Indicating whether to calculate actions in tree in random order.
            distance_to_check:
                Distance an enemy player is allowed to be at maximum distance, so that he is taken into
                account in the calculations.
        """

        super().__init__(player, max_speed)
        self.__depth = depth
        self.__randomize = randomize
        self.__distance_to_check = distance_to_check

    def get_information(self) -> str:
        """See base class."""
        return (super().get_information() +
                ", depth=" + str(self.__depth) +
                ", randomize=" + str(self.__randomize) +
                ", distance_to_check=" + str(self.__distance_to_check))

    def create_next_action(self, game: Game, return_value: Value):
        """See base class."""
        self._turn_ctr += 1

        root = SearchTreeRoot(game.copy())
        player_ids_to_watch = game.get_other_player_ids(self.player, self.__distance_to_check, True)
        combinations = Action.get_combinations(len(player_ids_to_watch))

        action = root.calculate_action(self.player, player_ids_to_watch, combinations, self.__depth, self._turn_ctr,
                                       True, [], self._max_speed, self.__randomize)
        return_value.value = (action if action is not None else Action.get_random_action()).get_index()

    def create_all_next_surviving_actions(self, game: Game) -> List[Action]:
        """Calculates not only one but all actions that will let the player survive for the next rounds.

        Args:
            game: The current state of the game.

        Returns:
            A list of actions which will let the player survive for the next rounds.
        """

        root = SearchTreeRoot(game.copy())
        player_ids_to_watch = game.get_other_player_ids(self.player, self.__distance_to_check, True)
        combinations = Action.get_combinations(len(player_ids_to_watch))

        search_tree_actions = []

        for action in Action.get_actions():
            if root.calculate_action(self.player, player_ids_to_watch, combinations, self.__depth, self._turn_ctr, True,
                                     [action], self._max_speed, True) is not None:
                search_tree_actions.append(action)

        return search_tree_actions

    def _get_depth(self) -> int:
        return self.__depth

    def _get_distance_to_check(self) -> int:
        return self.__distance_to_check

Ancestors

Subclasses

Methods

def create_all_next_surviving_actions(self, game: Game) ‑> List[Action]

Calculates not only one but all actions that will let the player survive for the next rounds.

Args

game
The current state of the game.

Returns

A list of actions which will let the player survive for the next rounds.

Expand source code
def create_all_next_surviving_actions(self, game: Game) -> List[Action]:
    """Calculates not only one but all actions that will let the player survive for the next rounds.

    Args:
        game: The current state of the game.

    Returns:
        A list of actions which will let the player survive for the next rounds.
    """

    root = SearchTreeRoot(game.copy())
    player_ids_to_watch = game.get_other_player_ids(self.player, self.__distance_to_check, True)
    combinations = Action.get_combinations(len(player_ids_to_watch))

    search_tree_actions = []

    for action in Action.get_actions():
        if root.calculate_action(self.player, player_ids_to_watch, combinations, self.__depth, self._turn_ctr, True,
                                 [action], self._max_speed, True) is not None:
            search_tree_actions.append(action)

    return search_tree_actions
def create_next_action(self, game: Game, return_value: >)

See base class.

Expand source code
def create_next_action(self, game: Game, return_value: Value):
    """See base class."""
    self._turn_ctr += 1

    root = SearchTreeRoot(game.copy())
    player_ids_to_watch = game.get_other_player_ids(self.player, self.__distance_to_check, True)
    combinations = Action.get_combinations(len(player_ids_to_watch))

    action = root.calculate_action(self.player, player_ids_to_watch, combinations, self.__depth, self._turn_ctr,
                                   True, [], self._max_speed, self.__randomize)
    return_value.value = (action if action is not None else Action.get_random_action()).get_index()
def get_information(self) ‑> str

See base class.

Expand source code
def get_information(self) -> str:
    """See base class."""
    return (super().get_information() +
            ", depth=" + str(self.__depth) +
            ", randomize=" + str(self.__randomize) +
            ", distance_to_check=" + str(self.__distance_to_check))
class SearchTreePathfindingAI (player: Player, max_speed: int, count_paths_to_check: int, depth: int, distance_to_check: int = 0)

This AI combines the SearchTreeAI and the PathfindingAI by favoring the former.

Therefore it finds all actions that let the player survive the next rounds by using the SearchTreeAI and afterwards lets the PathfindingAI check which of these is the best action to perform.

Attributes

player
The player associated with this AI.

Creates a new object of the SearchTreePathfindingAI.

Args

player
The player assigned to the AI.
max_speed
The maximum speed the AI can reach.
count_paths_to_check
The number of paths used to avoid dead ends.
depth
Number of pre-calculating actions.

distance_to_check: Distance an enemy player is allowed to be at maximum distance, so that he is taken into account in the calculations.

Expand source code
class SearchTreePathfindingAI(PathfindingAI, SearchTreeAI):
    """This AI combines the SearchTreeAI and the PathfindingAI by favoring the former.

    Therefore it finds all actions that let the player survive the next rounds by using the SearchTreeAI and
    afterwards lets the PathfindingAI check which of these is the best action to perform.

    Attributes:
        player: The player associated with this AI.
    """

    def __init__(self, player: Player, max_speed: int, count_paths_to_check: int, depth: int,
                 distance_to_check: int = 0):
        """Creates a new object of the SearchTreePathfindingAI.

        Args:
            player: The player assigned to the AI.
            max_speed: The maximum speed the AI can reach.
            count_paths_to_check: The number of paths used to avoid dead ends.
            depth: Number of pre-calculating actions.
            distance_to_check:
                Distance an enemy player is allowed to be at maximum distance, so that he is taken into
                account in the calculations.
        """
        PathfindingAI.__init__(self, player, max_speed, count_paths_to_check)
        SearchTreeAI.__init__(self, player, depth, max_speed, distance_to_check=distance_to_check)

    def get_information(self) -> str:
        """See base class."""
        return "max_speed=" + str(self._max_speed) \
               + ", count_paths_to_check=" + str(self._get_count_paths_to_check()) \
               + ", depth=" + str(self._get_depth()) \
               + ", distance_to_check=" + str(self._get_distance_to_check())

    def create_next_action(self, game: Game, return_value: Value):
        """See base class."""
        self._turn_ctr += 1

        surviving_actions = self.create_all_next_surviving_actions(game)
        if surviving_actions is not None and len(surviving_actions) > 0:
            return_value.value = choice(surviving_actions).get_index()
            return_value.value = self.find_actions_by_best_path_connection(surviving_actions, game)[0][0].get_index()
        else:
            surviving_pathfinding_actions = self.find_actions_by_best_path_connection(
                self.find_surviving_actions(GameService(game), 1), game)
            return_value.value = surviving_pathfinding_actions[0][0].get_index() \
                if surviving_pathfinding_actions is not None and len(surviving_pathfinding_actions) > 0 \
                else Action.get_default().get_index()

Ancestors

Inherited members