Module chillow.model.game

Expand source code
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from typing import List, Optional
from pathfinding.core.diagonal_movement import DiagonalMovement
from pathfinding.core.grid import Grid
from pathfinding.finder.best_first import BestFirst

from chillow.model.player import Player
from chillow.model.cell import Cell
from chillow.exceptions import WrongGameWidthException, WrongGameHeightException, OwnPlayerMissingException, \
    PlayerPositionException, PlayerWithGivenIdNotAvailableException


@dataclass
class Game:
    """This class holds all information needed to represent the current state of a game.

    Attributes:
        width: The number of horizontally adjacent cells.
        height: The number of vertically adjacent cells.
        cells:
            A two-dimensional list of cells.
            The first dimension is the row number (y axis), the second dimension represents the column (x axis).
            The field [0][0] is in the upper left corner.
        players: All players participating in this game.
        you: The own player in this game. This player is also part of the players list.
        running: This flag indicates whether the game is still running or is ended.
        deadline:
            The deadline by which all players have to perform their next action.
            This field has no value, if the game is not running.
    """

    width: int
    height: int
    cells: List[List[Cell]]
    players: List[Player]
    __you: int = field(repr=False)
    you: Player = field(init=False)
    running: bool
    deadline: datetime = None

    def __post_init__(self):
        """Performs checks after the game was created.

        Raises:
            WrongGameHeightException: Game height and height of cell array are not the same.
            WrongGameWidthException: Game width and width of cell array are not the same.
            PlayerPositionException: The current position of the player is not represented in the game.
            OwnPlayerMissingException: No player representing the own player was found in the game.
        """
        if len(self.cells) != self.height:
            raise WrongGameHeightException(len(self.cells), self.height)

        if len(self.cells[0]) != self.width:
            raise WrongGameWidthException(len(self.cells[0]), self.width)

        for player in self.players:
            if player.id == self.__you:
                self.you = player

            if player.active \
                    and (self.cells[player.y][player.x].players is None
                         or player not in self.cells[player.y][player.x].players):
                raise PlayerPositionException(player.x, player.y)

        if not hasattr(self, 'you'):
            raise OwnPlayerMissingException()

    def get_winner(self) -> Optional[Player]:
        """Returns the winner of the game.

        The winner is only determined if the game is not running anymore and there is an active player left in the
        game. Otherwise an empty value is returned.

        Returns:
            The winner of the game if there is one. The return vale may be empty.
        """
        if self.running:
            raise Exception("Game not ended and has no winner yet")
        for player in self.players:
            if player.active:
                return player
        return None

    def get_other_player_ids(self, p: Player, distance: int = 0, check_active: bool = False) -> List[int]:
        """Returns all other players in the game reachable in given distance and based on their status.

        Args:
            p: The player who should be ignored.
            distance: The distance in which other players should be found; 0 if distance should be ignored.
            check_active: Flag to check whether only active players should be returned.

        Returns:
            List of ids matching the above criteria.
        """
        players = []
        for player in self.players:
            if player.id != p.id \
                    and (not check_active or player.active) \
                    and (distance == 0 or (0 < self.__measure_shortest_distance(player, p) <= distance)):
                players.append(player.id)
        return players

    def __measure_shortest_distance(self, player_a: Player, player_b: Player) -> int:
        matrix = self.translate_cell_matrix_to_pathfinding_matrix()
        matrix[player_b.y][player_b.x] = 1  # target field must be empty
        path_finder = BestFirst(diagonal_movement=DiagonalMovement.never)
        grid = Grid(matrix=matrix)

        path, _ = path_finder.find_path(grid.node(player_a.x, player_a.y), grid.node(player_b.x, player_b.y), grid)
        return len(path) - 1  # Subtract 1 to not count the starting position

    def translate_cell_matrix_to_pathfinding_matrix(self) -> List[List[int]]:
        """Translates the two-dimensional cell array to an two-dimensional array usable for pathfinding.

        Returns:
            Two-dimensional array of integers, where an empty cell is represented by 1 and cells which were already
            visited by a player are represented by 0.
        """
        matrix = [[1 for _ in range(self.width)] for _ in range(self.height)]
        for i in range(len(self.cells)):
            for j in range(len(self.cells[i])):
                if self.cells[i][j].players is not None and len(self.cells[i][j].players) > 0:
                    matrix[i][j] = 0  # Collision cell
        return matrix

    def get_player_by_id(self, player_id: int) -> Player:
        """Identifies one player in the game by the ID.

        Args:
            player_id: The ID of the player.

        Returns:
            The player identified by the given ID.

        Raises:
             PlayerWithGivenIdNotAvailableException: Raised when no player with this ID is in this game.
        """
        for player in self.players:
            if player.id == player_id:
                return player
        raise PlayerWithGivenIdNotAvailableException(player_id)

    def get_players_by_ids(self, player_ids: List[int]) -> List[Player]:
        """Identifies multiple players in the game by their IDs.

        Args:
            player_ids: A list of IDs of the players.

        Returns:
            A list of players identified by the given IDs.
        """
        players = []
        for player in self.players:
            if player.id in player_ids:
                players.append(player)
        return players

    def copy(self):
        """Creates an exact same copy of this game but all objects point to different memory locations.

        Returns:
            A copy of the game.
        """
        players: List[Player] = []
        for player in self.players:
            players.append(
                Player(player.id, player.x, player.y, player.direction, player.speed, player.active, player.name))

        cells: List[List[Cell]] = [[Cell() for _ in range(self.width)] for _ in range(self.height)]
        for row in range(len(self.cells)):
            for col in range(len(self.cells[row])):
                if self.cells[row][col].players is not None:
                    players_in_cell = []
                    for player in self.cells[row][col].players:
                        for copied_player in players:
                            if copied_player.id == player.id:
                                players_in_cell.append(copied_player)
                    cells[row][col].players = players_in_cell

        return Game(self.width, self.height, cells, players, self.you.id, self.running, self.deadline)

    def normalize_deadline(self, server_time: datetime, own_time: datetime) -> None:
        """Adjusts the deadline according to the difference between server and system time.

        Args:
            server_time: The current time of the game server.
            own_time: The current time of the system where the program is executed.
        """
        time_delta = own_time - server_time
        multiplier = 1

        if server_time > own_time:
            time_delta = server_time - own_time
            multiplier = -1

        microseconds_delta = int((time_delta.total_seconds() * 1000000))
        self.deadline += multiplier * timedelta(microseconds=microseconds_delta)

Classes

class Game (width: int, height: int, cells: List[List[Cell]], players: List[Player], _Game__you: int, running: bool, deadline: datetime.datetime = None)

This class holds all information needed to represent the current state of a game.

Attributes

width
The number of horizontally adjacent cells.
height
The number of vertically adjacent cells.
cells:
A two-dimensional list of cells.
The first dimension is the row number (y axis), the second dimension represents the column (x axis).
The field [0][0] is in the upper left corner.
players
All players participating in this game.
you
The own player in this game. This player is also part of the players list.
running
This flag indicates whether the game is still running or is ended.

deadline: The deadline by which all players have to perform their next action. This field has no value, if the game is not running.

Expand source code
@dataclass
class Game:
    """This class holds all information needed to represent the current state of a game.

    Attributes:
        width: The number of horizontally adjacent cells.
        height: The number of vertically adjacent cells.
        cells:
            A two-dimensional list of cells.
            The first dimension is the row number (y axis), the second dimension represents the column (x axis).
            The field [0][0] is in the upper left corner.
        players: All players participating in this game.
        you: The own player in this game. This player is also part of the players list.
        running: This flag indicates whether the game is still running or is ended.
        deadline:
            The deadline by which all players have to perform their next action.
            This field has no value, if the game is not running.
    """

    width: int
    height: int
    cells: List[List[Cell]]
    players: List[Player]
    __you: int = field(repr=False)
    you: Player = field(init=False)
    running: bool
    deadline: datetime = None

    def __post_init__(self):
        """Performs checks after the game was created.

        Raises:
            WrongGameHeightException: Game height and height of cell array are not the same.
            WrongGameWidthException: Game width and width of cell array are not the same.
            PlayerPositionException: The current position of the player is not represented in the game.
            OwnPlayerMissingException: No player representing the own player was found in the game.
        """
        if len(self.cells) != self.height:
            raise WrongGameHeightException(len(self.cells), self.height)

        if len(self.cells[0]) != self.width:
            raise WrongGameWidthException(len(self.cells[0]), self.width)

        for player in self.players:
            if player.id == self.__you:
                self.you = player

            if player.active \
                    and (self.cells[player.y][player.x].players is None
                         or player not in self.cells[player.y][player.x].players):
                raise PlayerPositionException(player.x, player.y)

        if not hasattr(self, 'you'):
            raise OwnPlayerMissingException()

    def get_winner(self) -> Optional[Player]:
        """Returns the winner of the game.

        The winner is only determined if the game is not running anymore and there is an active player left in the
        game. Otherwise an empty value is returned.

        Returns:
            The winner of the game if there is one. The return vale may be empty.
        """
        if self.running:
            raise Exception("Game not ended and has no winner yet")
        for player in self.players:
            if player.active:
                return player
        return None

    def get_other_player_ids(self, p: Player, distance: int = 0, check_active: bool = False) -> List[int]:
        """Returns all other players in the game reachable in given distance and based on their status.

        Args:
            p: The player who should be ignored.
            distance: The distance in which other players should be found; 0 if distance should be ignored.
            check_active: Flag to check whether only active players should be returned.

        Returns:
            List of ids matching the above criteria.
        """
        players = []
        for player in self.players:
            if player.id != p.id \
                    and (not check_active or player.active) \
                    and (distance == 0 or (0 < self.__measure_shortest_distance(player, p) <= distance)):
                players.append(player.id)
        return players

    def __measure_shortest_distance(self, player_a: Player, player_b: Player) -> int:
        matrix = self.translate_cell_matrix_to_pathfinding_matrix()
        matrix[player_b.y][player_b.x] = 1  # target field must be empty
        path_finder = BestFirst(diagonal_movement=DiagonalMovement.never)
        grid = Grid(matrix=matrix)

        path, _ = path_finder.find_path(grid.node(player_a.x, player_a.y), grid.node(player_b.x, player_b.y), grid)
        return len(path) - 1  # Subtract 1 to not count the starting position

    def translate_cell_matrix_to_pathfinding_matrix(self) -> List[List[int]]:
        """Translates the two-dimensional cell array to an two-dimensional array usable for pathfinding.

        Returns:
            Two-dimensional array of integers, where an empty cell is represented by 1 and cells which were already
            visited by a player are represented by 0.
        """
        matrix = [[1 for _ in range(self.width)] for _ in range(self.height)]
        for i in range(len(self.cells)):
            for j in range(len(self.cells[i])):
                if self.cells[i][j].players is not None and len(self.cells[i][j].players) > 0:
                    matrix[i][j] = 0  # Collision cell
        return matrix

    def get_player_by_id(self, player_id: int) -> Player:
        """Identifies one player in the game by the ID.

        Args:
            player_id: The ID of the player.

        Returns:
            The player identified by the given ID.

        Raises:
             PlayerWithGivenIdNotAvailableException: Raised when no player with this ID is in this game.
        """
        for player in self.players:
            if player.id == player_id:
                return player
        raise PlayerWithGivenIdNotAvailableException(player_id)

    def get_players_by_ids(self, player_ids: List[int]) -> List[Player]:
        """Identifies multiple players in the game by their IDs.

        Args:
            player_ids: A list of IDs of the players.

        Returns:
            A list of players identified by the given IDs.
        """
        players = []
        for player in self.players:
            if player.id in player_ids:
                players.append(player)
        return players

    def copy(self):
        """Creates an exact same copy of this game but all objects point to different memory locations.

        Returns:
            A copy of the game.
        """
        players: List[Player] = []
        for player in self.players:
            players.append(
                Player(player.id, player.x, player.y, player.direction, player.speed, player.active, player.name))

        cells: List[List[Cell]] = [[Cell() for _ in range(self.width)] for _ in range(self.height)]
        for row in range(len(self.cells)):
            for col in range(len(self.cells[row])):
                if self.cells[row][col].players is not None:
                    players_in_cell = []
                    for player in self.cells[row][col].players:
                        for copied_player in players:
                            if copied_player.id == player.id:
                                players_in_cell.append(copied_player)
                    cells[row][col].players = players_in_cell

        return Game(self.width, self.height, cells, players, self.you.id, self.running, self.deadline)

    def normalize_deadline(self, server_time: datetime, own_time: datetime) -> None:
        """Adjusts the deadline according to the difference between server and system time.

        Args:
            server_time: The current time of the game server.
            own_time: The current time of the system where the program is executed.
        """
        time_delta = own_time - server_time
        multiplier = 1

        if server_time > own_time:
            time_delta = server_time - own_time
            multiplier = -1

        microseconds_delta = int((time_delta.total_seconds() * 1000000))
        self.deadline += multiplier * timedelta(microseconds=microseconds_delta)

Class variables

var cells : List[List[Cell]]
var deadline : datetime.datetime
var height : int
var players : List[Player]
var running : bool
var width : int
var youPlayer

Methods

def copy(self)

Creates an exact same copy of this game but all objects point to different memory locations.

Returns

A copy of the game.

Expand source code
def copy(self):
    """Creates an exact same copy of this game but all objects point to different memory locations.

    Returns:
        A copy of the game.
    """
    players: List[Player] = []
    for player in self.players:
        players.append(
            Player(player.id, player.x, player.y, player.direction, player.speed, player.active, player.name))

    cells: List[List[Cell]] = [[Cell() for _ in range(self.width)] for _ in range(self.height)]
    for row in range(len(self.cells)):
        for col in range(len(self.cells[row])):
            if self.cells[row][col].players is not None:
                players_in_cell = []
                for player in self.cells[row][col].players:
                    for copied_player in players:
                        if copied_player.id == player.id:
                            players_in_cell.append(copied_player)
                cells[row][col].players = players_in_cell

    return Game(self.width, self.height, cells, players, self.you.id, self.running, self.deadline)
def get_other_player_ids(self, p: Player, distance: int = 0, check_active: bool = False) ‑> List[int]

Returns all other players in the game reachable in given distance and based on their status.

Args

p
The player who should be ignored.
distance
The distance in which other players should be found; 0 if distance should be ignored.
check_active
Flag to check whether only active players should be returned.

Returns

List of ids matching the above criteria.

Expand source code
def get_other_player_ids(self, p: Player, distance: int = 0, check_active: bool = False) -> List[int]:
    """Returns all other players in the game reachable in given distance and based on their status.

    Args:
        p: The player who should be ignored.
        distance: The distance in which other players should be found; 0 if distance should be ignored.
        check_active: Flag to check whether only active players should be returned.

    Returns:
        List of ids matching the above criteria.
    """
    players = []
    for player in self.players:
        if player.id != p.id \
                and (not check_active or player.active) \
                and (distance == 0 or (0 < self.__measure_shortest_distance(player, p) <= distance)):
            players.append(player.id)
    return players
def get_player_by_id(self, player_id: int) ‑> Player

Identifies one player in the game by the ID.

Args

player_id
The ID of the player.

Returns

The player identified by the given ID.

Raises

PlayerWithGivenIdNotAvailableException
Raised when no player with this ID is in this game.
Expand source code
def get_player_by_id(self, player_id: int) -> Player:
    """Identifies one player in the game by the ID.

    Args:
        player_id: The ID of the player.

    Returns:
        The player identified by the given ID.

    Raises:
         PlayerWithGivenIdNotAvailableException: Raised when no player with this ID is in this game.
    """
    for player in self.players:
        if player.id == player_id:
            return player
    raise PlayerWithGivenIdNotAvailableException(player_id)
def get_players_by_ids(self, player_ids: List[int]) ‑> List[Player]

Identifies multiple players in the game by their IDs.

Args

player_ids
A list of IDs of the players.

Returns

A list of players identified by the given IDs.

Expand source code
def get_players_by_ids(self, player_ids: List[int]) -> List[Player]:
    """Identifies multiple players in the game by their IDs.

    Args:
        player_ids: A list of IDs of the players.

    Returns:
        A list of players identified by the given IDs.
    """
    players = []
    for player in self.players:
        if player.id in player_ids:
            players.append(player)
    return players
def get_winner(self) ‑> Optional[Player]

Returns the winner of the game.

The winner is only determined if the game is not running anymore and there is an active player left in the game. Otherwise an empty value is returned.

Returns

The winner of the game if there is one. The return vale may be empty.

Expand source code
def get_winner(self) -> Optional[Player]:
    """Returns the winner of the game.

    The winner is only determined if the game is not running anymore and there is an active player left in the
    game. Otherwise an empty value is returned.

    Returns:
        The winner of the game if there is one. The return vale may be empty.
    """
    if self.running:
        raise Exception("Game not ended and has no winner yet")
    for player in self.players:
        if player.active:
            return player
    return None
def normalize_deadline(self, server_time: datetime.datetime, own_time: datetime.datetime) ‑> NoneType

Adjusts the deadline according to the difference between server and system time.

Args

server_time
The current time of the game server.
own_time
The current time of the system where the program is executed.
Expand source code
def normalize_deadline(self, server_time: datetime, own_time: datetime) -> None:
    """Adjusts the deadline according to the difference between server and system time.

    Args:
        server_time: The current time of the game server.
        own_time: The current time of the system where the program is executed.
    """
    time_delta = own_time - server_time
    multiplier = 1

    if server_time > own_time:
        time_delta = server_time - own_time
        multiplier = -1

    microseconds_delta = int((time_delta.total_seconds() * 1000000))
    self.deadline += multiplier * timedelta(microseconds=microseconds_delta)
def translate_cell_matrix_to_pathfinding_matrix(self) ‑> List[List[int]]

Translates the two-dimensional cell array to an two-dimensional array usable for pathfinding.

Returns

Two-dimensional array of integers, where an empty cell is represented by 1 and cells which were already visited by a player are represented by 0.

Expand source code
def translate_cell_matrix_to_pathfinding_matrix(self) -> List[List[int]]:
    """Translates the two-dimensional cell array to an two-dimensional array usable for pathfinding.

    Returns:
        Two-dimensional array of integers, where an empty cell is represented by 1 and cells which were already
        visited by a player are represented by 0.
    """
    matrix = [[1 for _ in range(self.width)] for _ in range(self.height)]
    for i in range(len(self.cells)):
        for j in range(len(self.cells[i])):
            if self.cells[i][j].players is not None and len(self.cells[i][j].players) > 0:
                matrix[i][j] = 0  # Collision cell
    return matrix