Module chillow.service.ai.pathfinding_ai

Expand source code
import operator
from multiprocessing import Value
from typing import List, Tuple, Optional
from random import shuffle
from pathfinding.core.diagonal_movement import DiagonalMovement
from pathfinding.core.grid import Grid
from pathfinding.finder.best_first import BestFirst

from chillow.service.ai import NotKillingItselfAI
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


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

Classes

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