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 you : Player
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