Skip to content

Commit

Permalink
Merge pull request #6 from TINF21CS1/statistics
Browse files Browse the repository at this point in the history
Statistics
  • Loading branch information
Petzys authored Mar 15, 2024
2 parents 4b507db + 45b0582 commit 7830581
Show file tree
Hide file tree
Showing 13 changed files with 467 additions and 22 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,5 @@ dist/**
package.nls.*.json
l10n/
launch.json
venv
venv
*.db
8 changes: 4 additions & 4 deletions Client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class GameClient:
_current_player (Player): The player that is currently allowed to make a move.
_lobby_status (list[str]): The status of the lobby. Contains all players in the lobby.
_playfield (list[list[int]]): The status of the game. Contains the current playfield.
_statistics: The statistics of the game. TODO
_statistics (dict[Player:dict[str:int]] ): The statistics of the game.
_chat_history (list[tuple[Player, str]]): The chat history of the game. Contains all messages sent in the game.
_winner (Player): The winner of the game. None if the game is not finished yet or it is a draw.
_error_history (list[str]): The error history of the game. Contains all errors that occurred for this client.
Expand Down Expand Up @@ -71,7 +71,7 @@ def __init__(self, ip:str, port:int, player:Player) -> None:
self._current_player: Player = None
self._lobby_status: list[str] = []
self._playfield: list[list[int]] = [[0,0,0],[0,0,0],[0,0,0]]
self._statistics = None # TODO
self._statistics = {}
self._chat_history: list[tuple[Player, str]] = []
self._winner: Player = None
self._error_history: list[str] = []
Expand Down Expand Up @@ -203,8 +203,8 @@ async def _preprocess_message(self, message:str) -> str:
self._playfield = message_json["updated_playfield"]
self._current_player = self.get_player_by_uuid(message_json["next_player_uuid"])
case "statistics/statistics":
# TODO: Add statistics handling
pass
for entry in message_json["server_statistics"]:
self._statistics[Player(**entry["player"])] = entry["statistics"]
case "game/error":
self._error_history.append(message_json["error_message"])
logger.error(f"Game error: {message_json['error_message']}")
Expand Down
120 changes: 120 additions & 0 deletions Client/profile_save.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import json
from os.path import exists

# Path to the profiles.json file
path = ('../json_schema/profiles.json')

class Profile:
"""
This class is used to handle the profiles.json file. It is used to get, set, and add profiles to the file.
"""
def check_file(self):
"""
This method checks if the file exists
:return: True if the file exists, False if it does not
"""
if exists(path):
print("found")
return True
else:
print("not found")
return False

def get_profiles(self):
"""
This method returns all the profiles from the file
:return: An array of all profiles
"""
if self.check_file():
with open(path, 'r') as file:
data = json.load(file)
return data
else:
return None
def get_profile(self,profile_uuid):
"""
This method returns a profile by its uuid
:param profile_uuid:
:return: profile matching given uuid
"""
if self.check_file():
try:
with open(path, 'r') as file:
data = json.load(file)
for profile in data:
if profile["profile_uuid"] == profile_uuid:
return profile
except:
print("json error: Make sure profiles.json is formatted correctly")
return None
def set_profile(self, profile_uuid , profile_name = None, profile_color = None):
"""
This method sets the profile name and/or color by the uuid
:param profile_uuid:
:param profile_name: *optional*
:param profile_color: *optional*
"""
if self.check_file():
try:
with open(path, 'r+') as file:
data = json.load(file)
for profile in data:
if profile["profile_uuid"] == profile_uuid:
if profile_name != None:
profile["profile_name"] = profile_name
if profile_color != None:
profile["profile_color"] = profile_color
break
with open(path, 'w') as file:
json.dump(data, file)
except:
print("json error: Make sure profiles.json is formatted correctly")
return None
def get_profile_by_name(self, profile_name):
if self.check_file():
"""
This method returns a profile by its name
:param profile_name:
:return: profile matching given name
"""
try:
with open(path, 'r') as file:
data = json.load(file)
for profile in data:
if profile["profile_name"] == profile_name:
return profile
except:
print("json error: Make sure profiles.json is formatted correctly")
return None

def add_new_profile(self, profile_name, profile_uuid, profile_color):
"""
This method adds a new profile to the file
:param profile_name:
:param profile_uuid:
:param profile_color:
"""
if self.check_file():
entry = {"profile_name": profile_name, "profile_uuid": profile_uuid, "profile_color": profile_color}
try:
with open(path, 'r+') as file:
data = json.load(file)
file.seek(0)
data.append(entry)
json.dump(data, file)
file.truncate()
except:
print("json error: Make sure profiles.json is formatted correctly")

else:
with open(path, 'w') as file:
entry = [{"profile_name": profile_name, "profile_uuid": profile_uuid, "profile_color": profile_color}]
json.dump(entry, file)

#Testing
#profile = Profile()
#profile.add_new_profile("test", "test", "test")
#print(profile.get_profiles())
#print(profile.get_profile("test"))
#profile.set_profile("test", "test2", "test3")
#print(profile.get_profiles())
8 changes: 6 additions & 2 deletions Client/ui_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,6 @@ async def _message_handler(self, message_type: str):
await self.close()
case "game/turn":
self.send_gamestate_to_ui()
case "statistics/statistics":
pass
case "game/error":
self._out_queue.put({
"message_type": "game/error",
Expand Down Expand Up @@ -141,6 +139,12 @@ async def await_commands(self):
await self.terminate()
case "game/gamestate":
self.send_gamestate_to_ui()
case "statistics/statistics":
self._out_queue.put({
"message_type": "statistics/statistics",
"statistics": self._statistics
})
self._tk_root.event_generate("<<statistics/statistics>>", when="tail")
case _:
logger.error(f"Unknown message type received from UI in in_queue: {message['message_type']}")
return
Expand Down
Empty file added Server/Data/.gitkeep
Empty file.
3 changes: 3 additions & 0 deletions Server/player.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ def __eq__(self, other) -> bool:
return False
return str(self.uuid) == str(other.uuid)

def __hash__(self) -> int:
return hash(self.uuid)

@classmethod
def from_dict(cls, player_dict: dict):
return cls(player_dict["display_name"], player_dict["color"], player_dict["uuid"], player_dict["ready"])
205 changes: 203 additions & 2 deletions Server/statistics.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,204 @@
import emoji
import sqlite3
import os

from Server.player import Player


class Statistics:
def __init__(self) -> None:
pass
"""
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
"""


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'
"""
self.path = path
self.conn = sqlite3.connect(self.path)
self.cursor = self.conn.cursor()
with self.conn:
self.cursor.execute(f"""
CREATE TABLE IF NOT EXISTS statistics(
uuid TEXT,
display_name TEXT,
color INT,
wins INT,
losses INT,
draws INT,
moves INT,
emojis INT
)
""")


def get_statistics(self) -> list:
"""
Returns the statistics of all players
:return: all statistics
"""
with self.conn:
self.cursor.execute(f"""
SELECT * FROM statistics
""")
return self.cursor.fetchall()


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
"""

self._check_add_profile(player)

with self.conn:
self.cursor.execute(f"""
UPDATE statistics
SET emojis = emojis + ?
WHERE uuid = ?
""",
(emoji.emoji_count(message), str(player.uuid)))

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

self._check_add_profile(player)

with self.conn:
self.cursor.execute(f"""
UPDATE statistics
SET moves = moves + 1
WHERE uuid = ?
""", (str(player.uuid),))

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
"""

self._check_add_profile(player_list[1])
self._check_add_profile(player_list[2])

if winner == 0:
self._increment_draws(player_list[1])
self._increment_draws(player_list[2])
elif winner == 1:
self._increment_win(player_list[1])
self._increment_loss(player_list[2])
elif winner == 2:
self._increment_win(player_list[2])
self._increment_loss(player_list[1])
else:
raise ValueError("Winner must be 0, 1 or 2")

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
"""

self._check_add_profile(player)

with self.conn:
self.cursor.execute(f"""
UPDATE statistics
SET wins = wins + 1
WHERE uuid = ?
""",
(str(player.uuid),)
)

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
"""

self._check_add_profile(player)

with self.conn:
self.cursor.execute(f"""
UPDATE statistics
SET losses = losses + 1
WHERE uuid = ?
""",
(str(player.uuid),)
)

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
"""

self._check_add_profile(player)

with self.conn:
self.cursor.execute(f"""
UPDATE statistics
SET draws = draws + 1
WHERE uuid = ?
""",
(str(player.uuid),)
)


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
"""
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
"""
with self.conn:
self.cursor.execute(f"""
SELECT * FROM statistics
WHERE uuid = ?
""", (uuid_str,))
return True if self.cursor.fetchone() is not None else False

def _add_profile(self, player: Player) -> None:
"""
Adds a new profile to the database
:param player: player object of the profile that is added
"""
with self.conn:
self.cursor.execute(f"""
INSERT INTO statistics ('uuid', 'display_name', 'color', 'wins', 'losses', 'draws', 'moves', 'emojis')
VALUES (?, ?, ?, 0, 0, 0, 0, 0)
""",
(str(player.uuid), player.display_name, player.color,)
)
Loading

0 comments on commit 7830581

Please sign in to comment.