Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serverdocs #26

Merged
merged 2 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 18 additions & 10 deletions Server/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,23 @@
from random import shuffle

class Game:
def __init__(self, player1: Player, player2: Player, rule_base: RuleBase = RuleBase()):
"""
Initializes a new instance of the Game class.
"""
Initializes a new instance of the Game class.

Args:
player1 (Player): The first player.
player2 (Player): The second player.
rule_base (RuleBase, optional): The rule base for the game. Defaults to RuleBase().
"""
Parameters:
player1 (Player): The first player.
player2 (Player): The second player.
rule_base (RuleBase, optional): The rule base for the game. Defaults to RuleBase().

Properties:
current_player_uuid (str): The UUID string of the current player.
winner (Player): The winner of the game.

Methods:
move(self, player: int, new_position: tuple[int, int]): Makes a move in the game.
"""

def __init__(self, player1: Player, player2: Player, rule_base: RuleBase = RuleBase()):
self._uuid: uuid.UUID = uuid.uuid4()
self._id: int = self._uuid.int
self.state = GameState()
Expand All @@ -26,7 +34,7 @@ def move(self, player: int, new_position: tuple[int, int]):
"""
Makes a move in the game.

Args:
Parameters::
player (int): The player making the move.
new_position (tuple[int, int]): The new position to place the player's move.

Expand All @@ -39,7 +47,7 @@ def move(self, player: int, new_position: tuple[int, int]):
self.rule_base.check_win(self.state)
except ValueError as e:
raise ValueError(e)

@property
def current_player_uuid(self) -> str:
"""
Expand Down
19 changes: 10 additions & 9 deletions Server/gamestate.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,20 @@ class GameState:
"""
Represents the state of a Tic-Tac-Toe game.

Attributes:
_playfield (list[list[int]]): The playfield of the game, represented as a 2D list.
_finished (bool): Indicates whether the game has finished.
_winner (int): The number of the winning player, if any.
Parameters:
playfield_dimensions (tuple[int, int]): The dimensions of the playfield (default: (3, 3)).

Properties:
winner (int): The index of the winning player.
finished (bool): True if the game has finished, False otherwise.
playfield (list[list[int]]): The playfield of the game.
current_player (int): The index of the current player.
playfield_dimensions (tuple[int, int]): The dimensions of the playfield.

Methods:
__init__(self, starting_player: int, playfiled_dimensions: (int, int) = (3,3)): Initializes a new instance of the GameState class.
set_player_position(self, player: int, new_position: (int, int)): Sets the position of a player on the playfield.
set_winner(self, winner: int): Sets the winner of the game.
winner(self) -> int: Returns the number of the winning player.
finished(self) -> bool: Returns True if the game has finished, False otherwise.
playfield(self) -> list[list[int]]: Returns the playfield of the game.
playfield_value(self, position: (int, int)) -> int: Returns the value at the specified position on the playfield.
playfield_value(self, position: (int, int)): Returns the value at the specified position on the playfield.
"""

_playfield: list[list[int]]
Expand Down
5 changes: 5 additions & 0 deletions Server/player.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ class Player:
uuid (UUID): The UUID of the player.
display_name (str): The display name of the player.
color (int): The color of the player.
color_str (str): The color of the player as a html string.
ready (bool): Whether the player is ready to start the game.

Class Methods:
from_dict(player_dict: dict) -> Player: Create a Player object from a dictionary.
with_color_str(display_name: str, color_str: str, uuid: UUID = None, ready: bool = False) -> Player: Create a Player object with an html string as color instead of an int.
"""
def __init__(self, display_name: str, color: int, uuid: UUID = None, ready:bool = False):
self.uuid: UUID = uuid if uuid else uuid4()
Expand Down
6 changes: 5 additions & 1 deletion Server/rulebase.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ class RuleBase:
"""
Represents the rule base for a Tic-Tac-Toe game.

Args:
Parameters:
playfield_dimensions (tuple[int, int]): The dimensions of the playfield (default: (3, 3)).

Attributes:
playfield_dimensions (tuple[int, int]): The dimensions of the playfield.

Functions:
is_move_valid(state: GameState, new_position: tuple[int, int]) -> bool: Check if a move is valid.
check_win(state: GameState) -> None: Check if there is a winner in the current game state.
is_game_state_valid(state: GameState) -> bool: Check if the current game state is valid.
"""

def __init__(self, playfield_dimensions: tuple[int, int] = (3,3)):
Expand Down
84 changes: 51 additions & 33 deletions Server/statistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,28 @@ class Statistics:
"""
Handle Statistics and Writing to permanent storage.

Methods:
get_statistics() -> list: returns all statistics
increment_emojis(player: Player, message: str) -> None: counts the emojis in the given message and updates the emoji statistics of a profile by its player object
increment_moves(player: Player) -> None: increments the moves of a profile by its player object
increment_games(player_list: list[Player], winner: int) -> None: increments the wins and losses of both players by their player objects

Private Methods:
_increment_win(player: Player) -> None: increments the wins of a profile by its player object
_increment_loss(player: Player) -> None: increments the losses of a profile by its player object
_increment_draws(player: Player) -> None: increments the draws of a profile by its player object
_check_add_profile(player: Player) -> None: checks if a profile with the given uuid exists and adds it if it doesn't
_check_profile(uuid: str) -> bool: checks if a profile with the given uuid exists
_add_profile(player: Player) -> None: adds a new profile to the database
Parameters:
path (str): path to db file, default is './Data/statistics.db'

Functions:
get_statistics() -> list: returns all statistics
increment_emojis(player: Player, message: str) -> None: counts the emojis in the given message and updates the emoji statistics of a profile by its player object
increment_moves(player: Player) -> None: increments the moves of a profile by its player object
increment_games(player_list: list[Player], winner: int) -> None: increments the wins and losses of both players by their player objects

Private Functions:
_increment_win(player: Player) -> None: increments the wins of a profile by its player object
_increment_loss(player: Player) -> None: increments the losses of a profile by its player object
_increment_draws(player: Player) -> None: increments the draws of a profile by its player object
_check_add_profile(player: Player) -> None: checks if a profile with the given uuid exists and adds it if it doesn't
_check_profile(uuid: str) -> bool: checks if a profile with the given uuid exists
_add_profile(player: Player) -> None: adds a new profile to the database
"""


def __init__(self, path: str = os.path.abspath('Server/Data/statistics.db')) -> None:
"""
Initializes the statistics object by creating a connection to the database
and creating the table if it doesn't exist
:param path: path to db file, default is './Data/statistics.db'
Initializes the statistics object by creating a connection to the database and creating the table if it doesn't exist
"""
self.path = path
self.conn = sqlite3.connect(self.path)
Expand All @@ -52,7 +53,9 @@ def __init__(self, path: str = os.path.abspath('Server/Data/statistics.db')) ->
def get_statistics(self) -> list:
"""
Returns the statistics of all players
:return: all statistics

Returns:
(list): all statistics
"""
with self.conn:
self.cursor.execute(f"""
Expand All @@ -63,10 +66,11 @@ def get_statistics(self) -> list:

def increment_emojis(self, player: Player, message: str) -> None:
"""
Counts the emojis in the given message and updates the emoji
statistics of a profile by its uuid
:param player:
:param message: message that is checked for emojis
Counts the emojis in the given message and updates the emoji statistics of a profile

Parameters:
player (Player): player object of the profile that sent the message
message (str): message that is checked for emojis
"""

self._check_add_profile(player)
Expand All @@ -81,8 +85,10 @@ def increment_emojis(self, player: Player, message: str) -> None:

def increment_moves(self, player: Player) -> None:
"""
Increments the moves of a profile by its uuid
:param player:
Increments the moves of a profile

Parameters:
player (Player): player object of the profile that made the move
"""

self._check_add_profile(player)
Expand All @@ -97,8 +103,10 @@ def increment_moves(self, player: Player) -> None:
def increment_games(self, player_list: list[Player], winner: int) -> None:
"""
Increments the wins and losses of both players by their player objects
:param player_list: list of None, player1, player2
:param winner: 0 if draw, 1 if player1 wins, 2 if player2 wins

Parameters:
player_list (list[Player]): list of None, player1, player2
winner (int): 0 if draw, 1 if player1 wins, 2 if player2 wins
"""

self._check_add_profile(player_list[1])
Expand All @@ -119,7 +127,9 @@ def increment_games(self, player_list: list[Player], winner: int) -> None:
def _increment_win(self, player: Player) -> None:
"""
Increments the wins of a profile by its uuid
:param player: player object of the profile that is updated

Parameters:
player (Player): player object of the profile that is updated
"""

self._check_add_profile(player)
Expand All @@ -136,7 +146,9 @@ def _increment_win(self, player: Player) -> None:
def _increment_loss(self, player: Player) -> None:
"""
Increments the losses of a profile by its uuid
:param player: player object of the profile that is updated

Parameters:
player (Player): player object of the profile that is updated
"""

self._check_add_profile(player)
Expand All @@ -153,7 +165,9 @@ def _increment_loss(self, player: Player) -> None:
def _increment_draws(self, player: Player) -> None:
"""
Increments the draws of a profile by its uuid
:param player: player object of the profile that is updated

Parameters:
player (Player): player object of the profile that is updated
"""

self._check_add_profile(player)
Expand All @@ -171,17 +185,19 @@ def _increment_draws(self, player: Player) -> None:
def _check_add_profile(self, player: Player) -> None:
"""
Checks if a profile with the given uuid exists and adds it if it doesn't
:param uuid: uuid of the profile that is checked
:return: True if the profile exists, False if it doesn't

Parameters:
player (Player): player object of the profile that is checked
"""
if not self._check_profile(str(player.uuid)):
self._add_profile(player)

def _check_profile(self, uuid_str: str) -> bool:
"""
Checks if a profile with the given uuid exists
:param uuid: uuid of the profile that is checked
:return: True if the profile exists, False if it doesn't

Parameters:
uuid_str (str): uuid of the profile that should be checked
"""
with self.conn:
self.cursor.execute(f"""
Expand All @@ -193,7 +209,9 @@ def _check_profile(self, uuid_str: str) -> bool:
def _add_profile(self, player: Player) -> None:
"""
Adds a new profile to the database
:param player: player object of the profile that is added

Parameters:
player (Player): player object of the profile that is added
"""
with self.conn:
self.cursor.execute(f"""
Expand Down
23 changes: 21 additions & 2 deletions Server/websocket_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,22 @@
logger = logging.getLogger(__name__)

class Lobby:
"""
This class represents an instance of the gameserver.

Parameters:
admin (Player): The player object of the admin user.
port (int): The port the server is running on. Default is 8765. (This also shouldnt be changed, since many parts of the client depend on this port.)

Functions:
handler(websocket) -> None: Handles incoming websocket messages from the client.
run() -> None: Runs the server async.

Private Functions:
_end_game() -> None: Ends the game and sends the final playfield and winner to the clients. Then closes the server.
start_server() -> None: Starts the server.
"""

def __init__(self, admin:Player, port: int = 8765) -> None:
self._players = {}
self._admin = admin
Expand All @@ -37,6 +53,9 @@ def __init__(self, admin:Player, port: int = 8765) -> None:


async def handler(self, websocket):
"""
Handle all websocket messages and pass them to the appropriate game function.
"""

self._connections.add(websocket)

Expand Down Expand Up @@ -210,12 +229,12 @@ async def _end_game(self):
await asyncio.sleep(1)
exit()

async def start_server(self):
async def _start_server(self):
async with websockets.serve(self.handler, host = "", port = self._port):
await asyncio.Future() # run forever

def run(self):
asyncio.run(self.start_server())
asyncio.run(self._start_server())

if __name__ == "__main__":
lobby = Lobby(port = 8765, admin = Player(uuid=uuid.UUID("c4f0eccd-a6a4-4662-999c-17669bc23d5e"), display_name="admin", color=0xffffff, ready=True))
Expand Down
Loading