diff --git a/data/data_api.py b/data/data_api.py index d6318c8..c4badca 100644 --- a/data/data_api.py +++ b/data/data_api.py @@ -1,100 +1,120 @@ -import sqlite3 import random from wood import logger -from data.setup_database import DB_PATH +from data.database import db_connection + + +# ----------------------------------------------------- +# User / Session +# ----------------------------------------------------- def validate_user_session(db_id, discord_id): """Prüft, ob das Cookie noch zur aktuellen Datenbank passt.""" - connection = sqlite3.connect(DB_PATH) + connection = db_connection() cursor = connection.cursor() - cursor.execute("SELECT discord_id FROM players WHERE id = ?", (db_id,)) + cursor.execute("SELECT discord_id FROM players WHERE id = %s", (db_id,)) result = cursor.fetchone() + cursor.close() connection.close() - # 1. Fall: Die ID gibt es gar nicht mehr in der Datenbank if result is None: logger.log(f"Player not found in database. Discord:{discord_id}") return False - # 2. Fall: Die ID gehört jetzt einem anderen Discord-Account (Datenbank wurde resettet) if str(result[0]) != str(discord_id): logger.log(f"Player with false coockies logged in! {discord_id} doesnt belong to {db_id}") return False - # 3. Fall: Alles ist korrekt! return True def change_display_name(player_id, new_name): - connection = sqlite3.connect(DB_PATH) + connection = db_connection() cursor = connection.cursor() - cursor.execute("UPDATE players SET display_name = ? WHERE id = ?", (new_name, player_id)) + cursor.execute("UPDATE players SET display_name = %s WHERE id = %s", (new_name, player_id)) connection.commit() + cursor.close() connection.close() + def generate_silly_name(): - adjectives = ["Verwirrter", "Blinder", "Heulender", "Zorniger", "Chaos", "Verzweifelter", "Schreiender", "Stolpernder", "Schwitzender"] - nouns = ["Grot", "Kultist", "Servitor", "Snotling", "Guardmen", "Würfellecker", "Regelvergesser", "Meta-Chaser", "Klebschnüffler"] - adj = random.choice(adjectives) - noun = random.choice(nouns) - return f"{adj} {noun}" + adjectives = ["Verwirrter", "Blinder", "Heulender", "Zorniger", "Chaos", + "Verzweifelter", "Schreiender", "Stolpernder", "Schwitzender"] + nouns = ["Grot", "Kultist", "Servitor", "Snotling", "Guardmen", + "Würfellecker", "Regelvergesser", "Meta-Chaser", "Klebschnüffler"] + return f"{random.choice(adjectives)} {random.choice(nouns)}" def get_or_create_player(discord_id, discord_name, avatar_url): - connection = sqlite3.connect(DB_PATH) - cursor = connection.cursor() + connection = db_connection() + cursor = connection.cursor(dictionary=True) - # fragen 4 Dinge ab (id, discord_name, display_name, discord_avatar_url) - cursor.execute("SELECT id, discord_name, display_name, discord_avatar_url FROM players WHERE discord_id = ?", (discord_id,)) + cursor.execute( + "SELECT id, discord_name, display_name, discord_avatar_url FROM players WHERE discord_id = %s", + (discord_id,) + ) player = cursor.fetchone() if player is None: - # Random Silly Name Generator für neue Spieler. Damit sie angeregt werden ihren richtigen Namen einzutragen. silly_name = generate_silly_name() - cursor.execute("INSERT INTO players (discord_id, discord_name, display_name, discord_avatar_url) VALUES (?, ?, ?, ?)", (discord_id, discord_name, silly_name, avatar_url)) - logger.log(f"new player added. Discord:{discord_name}") + cursor.execute( + "INSERT INTO players (discord_id, discord_name, display_name, discord_avatar_url) " + "VALUES (%s, %s, %s, %s)", + (discord_id, discord_name, silly_name, avatar_url) + ) connection.commit() - cursor.execute("SELECT id, discord_name, display_name, discord_avatar_url FROM players WHERE discord_id = ?", (discord_id,)) + logger.log(f"new player added. Discord:{discord_name}") + + cursor.execute( + "SELECT id, discord_name, display_name, discord_avatar_url FROM players WHERE discord_id = %s", + (discord_id,) + ) player = cursor.fetchone() else: - # Falls sich Name oder Bild auf Discord geändert haben, machen wir ein Update - cursor.execute("UPDATE players SET discord_name = ?, discord_avatar_url = ? WHERE discord_id = ?", (discord_name, avatar_url, discord_id)) + cursor.execute( + "UPDATE players SET discord_name = %s, discord_avatar_url = %s WHERE discord_id = %s", + (discord_name, avatar_url, discord_id) + ) connection.commit() - cursor.execute("SELECT id, discord_name, display_name, discord_avatar_url FROM players WHERE discord_id = ?", (discord_id,)) + cursor.execute( + "SELECT id, discord_name, display_name, discord_avatar_url FROM players WHERE discord_id = %s", + (discord_id,) + ) player = cursor.fetchone() + cursor.close() connection.close() return player +# ----------------------------------------------------- +# Player-Listen +# ----------------------------------------------------- + def get_all_players(): - connection = sqlite3.connect(DB_PATH) - cursor = connection.cursor() + """Alle Spieler – Basisdaten.""" + connection = db_connection() + cursor = connection.cursor(dictionary=True) - # Alle Spieler laden, absteigend sortiert nach MMR - cursor.execute("SELECT id, name, mmr, points, games FROM players ORDER BY mmr DESC") - players = cursor.fetchall() + cursor.execute( + "SELECT id, display_name, discord_name, discord_avatar_url FROM players " + "ORDER BY display_name ASC" + ) + rows = cursor.fetchall() + cursor.close() connection.close() - - # Die Daten für das Web-GUI in eine lesbare Form umwandeln (Liste von Dictionaries) - result = [] - - return result + return rows def get_all_players_from_system(system_name): - connection = sqlite3.connect(DB_PATH) - connection.row_factory = sqlite3.Row - cursor = connection.cursor() + connection = db_connection() + cursor = connection.cursor(dictionary=True) - # ID und Namen der Spieler. - # DISTINCT stellt sicher, dass jeder Spieler nur einmal vorkommt. query = """ SELECT DISTINCT p.id AS player_id, @@ -103,47 +123,37 @@ def get_all_players_from_system(system_name): FROM players p JOIN player_game_statistic stat ON p.id = stat.player_id JOIN gamesystems sys ON stat.gamesystem_id = sys.id - WHERE sys.name = ? + WHERE sys.name = %s ORDER BY p.display_name ASC """ - cursor.execute(query, (system_name,)) rows = cursor.fetchall() + + cursor.close() connection.close() - - result = [] - for row in rows: - result.append(dict(row)) - - return result + return rows def get_gamesystem_id_by_name(system_name): - """Holt die interne ID eines Spielsystems anhand seines Namens (z.B. 'Warhammer 40k' -> 1).""" - connection = sqlite3.connect(DB_PATH) + connection = db_connection() cursor = connection.cursor() - # Wir suchen exakt nach dem Namen - cursor.execute("SELECT id FROM gamesystems WHERE name = ?", (system_name,)) + cursor.execute("SELECT id FROM gamesystems WHERE name = %s", (system_name,)) row = cursor.fetchone() + cursor.close() connection.close() - # Wenn wir einen Treffer haben, geben wir die erste Spalte (die ID) zurück - if row: - return row[0] - - # Wenn das System nicht existiert - return None - - + return row[0] if row else None +# ----------------------------------------------------- +# Player Statistics +# ----------------------------------------------------- def get_player_statistics(player_id): - connection = sqlite3.connect(DB_PATH) - connection.row_factory = sqlite3.Row - cursor = connection.cursor() + connection = db_connection() + cursor = connection.cursor(dictionary=True) query = """ SELECT @@ -162,42 +172,71 @@ def get_player_statistics(player_id): stat.nemesis_id FROM gamesystems sys LEFT JOIN player_game_statistic stat - ON sys.id = stat.gamesystem_id AND stat.player_id = ? + ON sys.id = stat.gamesystem_id AND stat.player_id = %s """ cursor.execute(query, (player_id,)) rows = cursor.fetchall() + + cursor.close() connection.close() - - result = [] - for row in rows: - result.append(dict(row)) - - return result + return rows +def get_player_statistic(player_id, system_id): + connection = db_connection() + cursor = connection.cursor(dictionary=True) + + query = """ + SELECT + sys.id AS gamesystem_id, + sys.name AS gamesystem_name, + sys.*, + stat.mmr, + stat.games_in_system, + stat.points, + stat.avv_points, + stat.last_played, + stat.win_rate, + stat.trend, + stat.tyrann_id, + stat.pushover_id, + stat.nemesis_id + FROM gamesystems sys + LEFT JOIN player_game_statistic stat + ON sys.id = stat.gamesystem_id AND stat.player_id = %s + WHERE sys.id = %s + """ + cursor.execute(query, (player_id, system_id)) + row = cursor.fetchone() + + cursor.close() + connection.close() + return row def join_league(player_id, gamesystem_id): - connection = sqlite3.connect(DB_PATH) + connection = db_connection() cursor = connection.cursor() - # Wir fügen nur die beiden IDs ein, der Rest wird von den DEFAULT-Werten der DB erledigt query = """ INSERT INTO player_game_statistic (player_id, gamesystem_id) - VALUES (?, ?) + VALUES (%s, %s) """ logger.log(f"{get_player_name(player_id)} joined {gamesystem_id}") cursor.execute(query, (player_id, gamesystem_id)) connection.commit() + cursor.close() connection.close() +# ----------------------------------------------------- +# Matches: Lesen +# ----------------------------------------------------- def get_recent_matches_for_player(player_id): - connection = sqlite3.connect(DB_PATH) - connection.row_factory = sqlite3.Row - cursor = connection.cursor() + connection = db_connection() + cursor = connection.cursor(dictionary=True) query = """ SELECT @@ -216,69 +255,62 @@ def get_recent_matches_for_player(player_id): JOIN gamesystems sys ON m.gamesystem_id = sys.id JOIN players p1 ON m.player1_id = p1.id JOIN players p2 ON m.player2_id = p2.id - WHERE m.player1_id = ? OR m.player2_id = ? + WHERE m.player1_id = %s OR m.player2_id = %s ORDER BY m.played_at DESC LIMIT 10 """ - cursor.execute(query, (player_id, player_id)) rows = cursor.fetchall() + + cursor.close() connection.close() - - result = [] - for row in rows: - result.append(dict(row)) - - return result - - + return rows def add_new_match(system_name, player1_id, player2_id, score_p1, score_p2): - connection = sqlite3.connect(DB_PATH) + connection = db_connection() cursor = connection.cursor() - # 1. Wir suchen die interne ID des Spielsystems anhand des Namens (z.B. "Warhammer 40k" -> 1) - cursor.execute("SELECT id FROM gamesystems WHERE name = ?", (system_name,)) + cursor.execute("SELECT id FROM gamesystems WHERE name = %s", (system_name,)) sys_row = cursor.fetchone() - # Sicherheitscheck (sollte eigentlich nie passieren) if not sys_row: + cursor.close() connection.close() - return None + return None sys_id = sys_row[0] - # 2. Das Match eintragen! (Datum 'played_at' macht SQLite durch DEFAULT CURRENT_TIMESTAMP automatisch) query = """ INSERT INTO matches (gamesystem_id, player1_id, player2_id, score_player1, score_player2) - VALUES (?, ?, ?, ?, ?) + VALUES (%s, %s, %s, %s, %s) """ - cursor.execute(query, (sys_id, player1_id, player2_id, score_p1, score_p2)) new_match_id = cursor.lastrowid - logger.log(f"{get_player_name(player1_id)}:({score_p1}) posted Match. System: {system_name}, {get_player_name(player2_id)}:({score_p2})") + logger.log( + f"{get_player_name(player1_id)}:({score_p1}) posted Match. " + f"System: {system_name}, {get_player_name(player2_id)}:({score_p2})" + ) connection.commit() + cursor.close() connection.close() return new_match_id - def save_calculated_match(calc_results: dict): - connection = sqlite3.connect(DB_PATH) + connection = db_connection() cursor = connection.cursor() try: - match_id = calc_results["match_id"] - winner_id = calc_results["winner_id"] - looser_id = calc_results["looser_id"] + match_id = calc_results["match_id"] + winner_id = calc_results["winner_id"] + looser_id = calc_results["looser_id"] - # Match aus DB lesen cursor.execute( - "SELECT player1_id, player2_id, gamesystem_id FROM matches WHERE id = ?", + "SELECT player1_id, player2_id, gamesystem_id FROM matches WHERE id = %s", (match_id,) ) row = cursor.fetchone() @@ -287,21 +319,19 @@ def save_calculated_match(calc_results: dict): player1_id, player2_id, gamesystem_id = row - # Daten der Spieler aus calc_results holen (Key = player_id als int/str) p1 = calc_results[player1_id] p2 = calc_results[player2_id] - # 1. Match-Tabelle updaten match_query = """ UPDATE matches SET - player1_base_change = ?, player1_khorne = ?, player1_slaanesh = ?, - player1_tzeentch = ?, player1_mmr_change = ?, - player2_base_change = ?, player2_khorne = ?, player2_slaanesh = ?, - player2_tzeentch = ?, player2_mmr_change = ?, - rust_factor = ?, elo_factor = ?, point_inflation = ?, + player1_base_change = %s, player1_khorne = %s, player1_slaanesh = %s, + player1_tzeentch = %s, player1_mmr_change = %s, + player2_base_change = %s, player2_khorne = %s, player2_slaanesh = %s, + player2_tzeentch = %s, player2_mmr_change = %s, + rust_factor = %s, elo_factor = %s, point_inflation = %s, match_is_counted = 1 - WHERE id = ? + WHERE id = %s """ cursor.execute(match_query, ( p1["base"], p1["khorne"], p1["slaanesh"], p1["tzeentch"], p1["total"], @@ -310,36 +340,29 @@ def save_calculated_match(calc_results: dict): match_id )) - # 2. Scores holen cursor.execute( - "SELECT score_player1, score_player2 FROM matches WHERE id = ?", + "SELECT score_player1, score_player2 FROM matches WHERE id = %s", (match_id,) ) score_row = cursor.fetchone() score_p1, score_p2 = score_row if score_row else (0, 0) - # 3. Statistik-Query stat_query = """ UPDATE player_game_statistic SET - mmr = mmr + ?, + mmr = mmr + %s, games_in_system = games_in_system + 1, - points = points + ?, - avv_points = (points + ?) / (games_in_system + 1), + points = points + %s, + avv_points = (points + %s) / (games_in_system + 1), last_played = CURRENT_TIMESTAMP - WHERE player_id = ? AND gamesystem_id = ? + WHERE player_id = %s AND gamesystem_id = %s """ - # 4. Statistik Spieler 1 cursor.execute(stat_query, ( - p1["total"], score_p1, score_p1, - player1_id, gamesystem_id + p1["total"], score_p1, score_p1, player1_id, gamesystem_id )) - - # 5. Statistik Spieler 2 cursor.execute(stat_query, ( - p2["total"], score_p2, score_p2, - player2_id, gamesystem_id + p2["total"], score_p2, score_p2, player2_id, gamesystem_id )) connection.commit() @@ -352,76 +375,63 @@ def save_calculated_match(calc_results: dict): return False finally: + cursor.close() connection.close() - - - - # ----------------------------------------------------- # Get Data Funktionen -# ----------------------------------------------------- +# ----------------------------------------------------- + def get_gamesystem_data(system_id): - connection = sqlite3.connect(DB_PATH) - cursor = connection.cursor() - cursor.execute("SELECT * FROM gamesystems WHERE id = ?", (system_id,)) + connection = db_connection() + cursor = connection.cursor(dictionary=True) + + cursor.execute("SELECT * FROM gamesystems WHERE id = %s", (system_id,)) row = cursor.fetchone() + + cursor.close() connection.close() - return dict(zip([col[0] for col in cursor.description], row)) if row else None + return row def get_gamesystems_data(): - connection = sqlite3.connect(DB_PATH) - - connection.row_factory = sqlite3.Row - cursor = connection.cursor() + connection = db_connection() + cursor = connection.cursor(dictionary=True) cursor.execute("SELECT * FROM gamesystems") rows = cursor.fetchall() + + cursor.close() connection.close() - - # SQLite-Rows in normale Python-Dictionaries um - result = [] - for row in rows: - result.append(dict(row)) - - return result - + return rows def get_leaderboard(system_name): """Holt alle Spieler eines Systems sortiert nach MMR für die Rangliste.""" - connection = sqlite3.connect(DB_PATH) - connection.row_factory = sqlite3.Row - cursor = connection.cursor() + connection = db_connection() + cursor = connection.cursor(dictionary=True) - # WIR HABEN HIER EINE BEDINGUNG HINZUGEFÜGT: AND stat.games_in_system > 0 - # Dadurch filtert die Datenbank direkt auf dem Server schon alle "0-Spiele"-Accounts raus. query = """ SELECT p.id, p.display_name, p.discord_name, stat.mmr FROM players p JOIN player_game_statistic stat ON p.id = stat.player_id JOIN gamesystems sys ON stat.gamesystem_id = sys.id - WHERE sys.name = ? AND stat.games_in_system > 0 + WHERE sys.name = %s AND stat.games_in_system > 0 ORDER BY stat.mmr DESC """ - cursor.execute(query, (system_name,)) rows = cursor.fetchall() + + cursor.close() connection.close() - - result = [] - for row in rows: - result.append(dict(row)) - - return result + return rows def get_match_by_id(match_id: int) -> dict | None: """Gibt alle Match-Daten inkl. Gamesystem-Name als Dict zurück.""" - connection = sqlite3.connect(DB_PATH) - cursor = connection.cursor() + connection = db_connection() + cursor = connection.cursor(dictionary=True) try: cursor.execute(""" @@ -436,99 +446,86 @@ def get_match_by_id(match_id: int) -> dict | None: m.played_at FROM matches m JOIN gamesystems g ON m.gamesystem_id = g.id - WHERE m.id = ? + WHERE m.id = %s """, (match_id,)) - row = cursor.fetchone() - if not row: - return None - - columns = [desc[0] for desc in cursor.description] - return dict(zip(columns, row)) + return cursor.fetchone() except Exception as e: logger.log(f"Fehler beim Laden des Matches: {e}") return None finally: + cursor.close() connection.close() def get_player_name(player_id): """Gibt den Namen eines Spielers im Format 'Anzeigename (Discordname)' zurück.""" - - # Sicherheits-Check: Falls aus Versehen gar keine ID übergeben wird if player_id is None: return "Unbekannter Spieler" - connection = sqlite3.connect(DB_PATH) + connection = db_connection() cursor = connection.cursor() - cursor.execute("SELECT display_name, discord_name FROM players WHERE id = ?", (player_id,)) + cursor.execute("SELECT display_name, discord_name FROM players WHERE id = %s", (player_id,)) row = cursor.fetchone() + + cursor.close() connection.close() - # Wenn die Datenbank den Spieler gefunden hat: if row: - display_name = row[0] - discord_name = row[1] - return f"{display_name} ({discord_name})" - else: - return "Gelöschter Spieler" + return f"{row[0]} ({row[1]})" + return "Gelöschter Spieler" def get_system_name(sys_id): if sys_id is None: return "Unbekanntes System" - connection = sqlite3.connect(DB_PATH) - cursor = connection.cursor() - cursor.execute("SELECT name FROM gamesystems WHERE id = ?", (sys_id,)) + connection = db_connection() + cursor = connection.cursor() + + cursor.execute("SELECT name FROM gamesystems WHERE id = %s", (sys_id,)) row = cursor.fetchone() + + cursor.close() connection.close() - if row: - return row[0] - else: - return "Gelöschtes System" - - + return row[0] if row else "Gelöschtes System" def get_days_since_last_system_game(player_id, gamesystem_id): """Gibt zurück, wie viele Tage das letzte Spiel in einem bestimmten System her ist.""" - connection = sqlite3.connect(DB_PATH) + connection = db_connection() cursor = connection.cursor() - # Julianday() berechnet in SQLite die Differenz in Tagen zwischen zwei Daten + # MariaDB: DATEDIFF statt julianday() query = """ - SELECT CAST(julianday('now') - julianday(last_played) AS INTEGER) + SELECT DATEDIFF(NOW(), last_played) FROM player_game_statistic - WHERE player_id = ? AND gamesystem_id = ? + WHERE player_id = %s AND gamesystem_id = %s """ cursor.execute(query, (player_id, gamesystem_id)) result = cursor.fetchone() + + cursor.close() connection.close() - # Wenn er gefunden wurde und ein Datum hat, gib die Tage zurück. Sonst (Erstes Spiel) 0. if result and result[0] is not None: - return result[0] + return int(result[0]) return 0 - - # ----------------------------------------------------- # Matches Bestätigen, Löschen, Berechnen, ... -# ----------------------------------------------------- +# ----------------------------------------------------- def get_unconfirmed_matches(player_id): """Holt alle offenen Matches, die der Spieler noch bestätigen muss.""" - connection = sqlite3.connect(DB_PATH) - connection.row_factory = sqlite3.Row - cursor = connection.cursor() + connection = db_connection() + cursor = connection.cursor(dictionary=True) - # Wir joinen players (für den Namen des Gegners) und gamesystems (für den Systemnamen) query = """ SELECT m.id AS match_id, m.score_player1, m.score_player2, m.played_at, sys.name AS system_name, @@ -536,58 +533,58 @@ def get_unconfirmed_matches(player_id): FROM matches m JOIN gamesystems sys ON m.gamesystem_id = sys.id JOIN players p1 ON m.player1_id = p1.id - WHERE m.player2_id = ? AND m.player2_check = 0 + WHERE m.player2_id = %s AND m.player2_check = 0 """ - cursor.execute(query, (player_id,)) rows = cursor.fetchall() - connection.close() - # Wir geben eine Liste mit Dictionaries zurück - return [dict(row) for row in rows] + cursor.close() + connection.close() + return rows def delete_match(match_id, player_id): """Löscht ein Match anhand seiner ID komplett aus der Datenbank.""" - connection = sqlite3.connect(DB_PATH) + connection = db_connection() cursor = connection.cursor() - # DELETE FROM löscht die gesamte Zeile, bei der die ID übereinstimmt. - cursor.execute("DELETE FROM matches WHERE id = ?", (match_id,)) + cursor.execute("DELETE FROM matches WHERE id = %s", (match_id,)) logger.log(f"Match ID{match_id} deleted from user {get_player_name(player_id)}(ID:{player_id})") connection.commit() + + cursor.close() connection.close() def confirm_match(match_id): - connection = sqlite3.connect(DB_PATH) + connection = db_connection() cursor = connection.cursor() - # Ändert nur die Spalte player2_check auf 1 (True) - cursor.execute("UPDATE matches SET player2_check = 1 WHERE id = ?", (match_id,)) + cursor.execute("UPDATE matches SET player2_check = 1 WHERE id = %s", (match_id,)) logger.log(f"Match mit ID{match_id} von Player2 bestätigt.") connection.commit() + + cursor.close() connection.close() def set_match_counted(match_id): """Setzt den Haken (1), dass das Match erfolgreich in die MMR eingeflossen ist.""" - connection = sqlite3.connect(DB_PATH) + connection = db_connection() cursor = connection.cursor() - # Ändert nur die Spalte match_is_counted auf 1 (True) - cursor.execute("UPDATE matches SET match_is_counted = 1 WHERE id = ?", (match_id,)) + cursor.execute("UPDATE matches SET match_is_counted = 1 WHERE id = %s", (match_id,)) connection.commit() + + cursor.close() connection.close() def get_submitted_matches(player_id): - """Holt alle offenen Matches, die der Spieler selbst eingetragen hat, aber vom Gegner noch nicht bestätigt wurden.""" - connection = sqlite3.connect(DB_PATH) - connection.row_factory = sqlite3.Row - cursor = connection.cursor() + """Holt alle offenen Matches, die der Spieler selbst eingetragen hat.""" + connection = db_connection() + cursor = connection.cursor(dictionary=True) - # ACHTUNG: Wir joinen hier p2 (player2), weil wir den Namen des Gegners anzeigen wollen! query = """ SELECT m.id AS match_id, m.score_player1, m.score_player2, m.played_at, sys.name AS system_name, @@ -595,21 +592,20 @@ def get_submitted_matches(player_id): FROM matches m JOIN gamesystems sys ON m.gamesystem_id = sys.id JOIN players p2 ON m.player2_id = p2.id - WHERE m.player1_id = ? AND m.player2_check = 0 + WHERE m.player1_id = %s AND m.player2_check = 0 """ - cursor.execute(query, (player_id,)) rows = cursor.fetchall() - connection.close() - return [dict(row) for row in rows] + cursor.close() + connection.close() + return rows def get_match_history_log(player_id): - """Holt ALLE Matches eines Spielers inklusive der MMR-Änderungen und Faktoren für das Log.""" - connection = sqlite3.connect(DB_PATH) - connection.row_factory = sqlite3.Row - cursor = connection.cursor() + """Holt ALLE Matches eines Spielers inkl. MMR-Änderungen und Faktoren.""" + connection = db_connection() + cursor = connection.cursor(dictionary=True) query = """ SELECT m.played_at, sys.name AS gamesystem_name, @@ -625,54 +621,12 @@ def get_match_history_log(player_id): JOIN gamesystems sys ON m.gamesystem_id = sys.id JOIN players p1 ON m.player1_id = p1.id JOIN players p2 ON m.player2_id = p2.id - WHERE m.player1_id = ? OR m.player2_id = ? + WHERE m.player1_id = %s OR m.player2_id = %s ORDER BY m.played_at DESC """ cursor.execute(query, (player_id, player_id)) rows = cursor.fetchall() + + cursor.close() connection.close() - - return [dict(row) for row in rows] - - - - - - -# ----------------------------------------------------- -# Testing and Prototyping -# ----------------------------------------------------- -def create_random_dummy_match(player_id): - connection = sqlite3.connect(DB_PATH) - cursor = connection.cursor() - - # 1. Die ID des Dummys suchen (Wir suchen einfach nach dem Namen 'Dummy') - cursor.execute("SELECT id FROM players WHERE display_name LIKE '%Dummy%' OR discord_name LIKE '%Dummy%' LIMIT 1") - dummy_row = cursor.fetchone() - - if not dummy_row: - connection.close() - print("Fehler: Kein Dummy in der Datenbank gefunden!") - return False - dummy_id = dummy_row[0] - # 2. Zufällige Punkte generieren (z.B. zwischen 0 und 100) - score_p1 = random.randint(0, 100) - score_p2 = random.randint(0, 100) - # 3. Das Match hart in System 1 (gamesystem_id = 1) eintragen - query = """ - INSERT INTO matches (gamesystem_id, player2_id, player1_id, score_player1, score_player2, player2_check) - VALUES (?, ?, ?, ?, ?, 0) - """ - cursor.execute(query, (1, player_id, dummy_id, score_p1, score_p2)) - - connection.commit() - connection.close() - - p1_name = get_player_name(player_id) - dummy_name = get_player_name(dummy_id) - sys_name = get_system_name(1) - - logger.log(f"Zufallsspiel generiert. [{sys_name}]: {p1_name} ({score_p1}) vs. {dummy_name} ({score_p2})") - - return True - + return rows diff --git a/data/database.py b/data/database.py index e67a275..5f0daa1 100644 --- a/data/database.py +++ b/data/database.py @@ -1,27 +1,23 @@ -import sqlite3 + +import mariadb import os -from data import setup_database, data_api +from dotenv import load_dotenv -from data.setup_database import DB_PATH +load_dotenv() + +def db_connection(): + try: + connection = mariadb.connect( + host=os.getenv("DB_HOST", "localhost"), + port=int(os.getenv("DB_PORT", 3306)), + user=os.getenv("DB_USER"), + password=os.getenv("DB_PASSWORD"), + database=os.getenv("DB_NAME") + ) + return connection + except mariadb.Error as e: + raise RuntimeError(f"Fehler beim Verbinden mit DB: {e}") - -if __name__ == "__main__": - setup_database.init_db() - -def check_db(): - # Prüfen, ob die Datei existiert - db_file = DB_PATH - - if not os.path.exists(db_file): - print(f"WARNUNG: '{db_file}' nicht gefunden!") - print("Starte Datenbank-Einrichtung...") - - # 1. Erstellt die leere Datei und alle Tabellen-Strukturen - setup_database.init_db() - - print("Datenbank aufgebaut!") - else: - print(f"OK: Datenbank '{db_file}' gefunden. Lade System...") diff --git a/data/setup_database.py b/data/setup_database.py deleted file mode 100644 index b68b828..0000000 --- a/data/setup_database.py +++ /dev/null @@ -1,222 +0,0 @@ - -import sqlite3 -import os -import json -from dotenv import load_dotenv - -load_dotenv() - -DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "league_database.db") - - -dummy_is_in = True - -def init_db(): - - connection = sqlite3.connect(DB_PATH) - cursor = connection.cursor() - - # Fremdschlüssel (Foreign Keys) in SQLite aktivieren - cursor.execute('PRAGMA foreign_keys = ON;') - - # 1. Tabelle: players (Stammdaten) - cursor.execute(''' - CREATE TABLE IF NOT EXISTS players ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - discord_id TEXT UNIQUE, - discord_name TEXT NOT NULL, - discord_avatar_url TEXT, - display_name TEXT - ) - ''') - - # 2. Tabelle: gamesystems (Globale Spielsysteme) - cursor.execute(''' - CREATE TABLE IF NOT EXISTS gamesystems ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - picture TEXT, - description TEXT, - active_players INTEGER DEFAULT 0, - min_score INTEGER DEFAULT 0, - max_score INTEGER DEFAULT 100 - ) - ''') - - # 3. Tabelle: player_game_statistic (Welcher Spieler hat in welchem System welche Stats?) - cursor.execute(''' - CREATE TABLE IF NOT EXISTS player_game_statistic ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - player_id INTEGER, - gamesystem_id INTEGER, - mmr INTEGER DEFAULT 1000, - games_in_system INTEGER DEFAULT 0, - points INTEGER DEFAULT 0, - avv_points INTEGER DEFAULT 0, - last_played TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - win_rate REAL DEFAULT 0, - win_streak INTEGER DEFAULT 0, - trend INTEGER DEFAULT 0, - tyrann_id INTEGER, - pushover_id INTEGER, - nemesis_id INTEGER, - FOREIGN KEY (player_id) REFERENCES players (id), - FOREIGN KEY (tyrann_id) REFERENCES players (id), - FOREIGN KEY (pushover_id) REFERENCES players (id), - FOREIGN KEY (nemesis_id) REFERENCES players (id), - FOREIGN KEY (gamesystem_id) REFERENCES gamesystems (id) - ) - ''') - - # 4. Tabelle: matches (Wer hat wann gegen wen in welchem System gespielt?) - cursor.execute(''' - CREATE TABLE IF NOT EXISTS matches ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - gamesystem_id INTEGER, - player1_id INTEGER, - score_player1 INTEGER, - player2_id INTEGER, - score_player2 INTEGER, - played_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - - player1_base_change INTEGER, - player1_khorne INTEGER, - player1_slaanesh INTEGER, - player1_tzeentch INTEGER, - player1_mmr_change INTEGER, - - player2_base_change INTEGER, - player2_khorne INTEGER, - player2_slaanesh INTEGER, - player2_tzeentch INTEGER, - player2_mmr_change INTEGER, - - rust_factor REAL, - elo_factor REAL, - point_inflation REAL, - - player2_check INTEGER DEFAULT 0, - match_is_counted INTEGER DEFAULT 0, - FOREIGN KEY (gamesystem_id) REFERENCES gamesystems (id), - FOREIGN KEY (player1_id) REFERENCES players (id), - FOREIGN KEY (player2_id) REFERENCES players (id) - ) - ''') - - # 5. Tabelle: achievements (Der globale Katalog aller möglichen Erfolge - Stammdaten) - cursor.execute(''' - CREATE TABLE IF NOT EXISTS achievements ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - description TEXT NOT NULL, - icon TEXT - ) - ''') - - # 6. Tabelle: player_achievements. Wer hat welchen Erfolg wann bekommen - cursor.execute(''' - CREATE TABLE IF NOT EXISTS player_achievements ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - player_id INTEGER, - achievement_id INTEGER, - earned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - earned_in INTEGER, - FOREIGN KEY (earned_in) REFERENCES gamesystems (id), - FOREIGN KEY (player_id) REFERENCES players (id), - FOREIGN KEY (achievement_id) REFERENCES achievements (id) - ) - ''') - - connection.commit() - connection.close() - print("Datenbank Struktur aufgebaut") - - #nächster Schritt: Standard Spielsysteme eintragen - seed_gamesystems() - - -# -------------------- -# --- Start Setup der DB Daten. Damit sie nicht GANZ leer sind. -# -------------------- -def seed_gamesystems(): - connection = sqlite3.connect(DB_PATH) - cursor = connection.cursor() - systems = [ - ("Warhammer 40k", "40k_logo.png", "Die Schlacht um die Galaxie in einer entfernten Zukunft.", 0, 100), - ("Warhammer Age of Sigmar", "aos_logo.png", "Der ewige Krieg um die Reiche der Sterblichen in einer epischen Fantasy-Welt.", 0, 50), - ("Spearhead", "aos_logo.png", "Schnelle und taktische Scharmützel für actiongeladene Kämpfe.", 0, 50) - ] - - # executemany ist eine extrem schnelle SQL For-Schleife für Inserts - cursor.executemany("INSERT INTO gamesystems (name, picture, description, min_score, max_score) VALUES (?, ?, ?, ?, ?)", systems) - - connection.commit() - connection.close() - print("Spielsysteme angelegt!") - - #Nächster Schritt: Standard Achievments eintragen. - seed_achievements() - -def seed_achievements(): - connection = sqlite3.connect(DB_PATH) - cursor = connection.cursor() - - # Unsere Start-Stückliste an Erfolgen (Name, Beschreibung, Icon) - achievements = [ - ("Kingslayer", "Hat den Spieler auf Platz 1 besiegt!", "👑"), - ("Unstoppable", "3 Spiele in Folge gewonnen.", "🔥"), - ("First Blood", "Das allererste Ligaspiel absolviert.", "🩸") - ] - - cursor.executemany("INSERT INTO achievements (name, description, icon) VALUES (?, ?, ?)", achievements) - - connection.commit() - connection.close() - print("Achievements angelegt.") - - if dummy_is_in: - seed_dummy_player() - - -def seed_dummy_player(): - connection = sqlite3.connect(DB_PATH) - cursor = connection.cursor() - - # 1. Dummy-Spieler anlegen (falls noch nicht vorhanden) - query_player = """ - INSERT OR IGNORE INTO players - (discord_id, discord_name, display_name, discord_avatar_url) - VALUES (?, ?, ?, ?) - """ - cursor.execute(query_player, ("dummy_001", "dummy_user", "Dummy Mc DummDumm", "")) - - # 2. Wir holen uns die ID des Dummys (egal ob neu oder alt) - cursor.execute("SELECT id FROM players WHERE discord_id = 'dummy_001'") - dummy_row = cursor.fetchone() - - if dummy_row: - dummy_id = dummy_row[0] - - # 3. Wir holen alle IDs der aktuellen Spielsysteme - cursor.execute("SELECT id FROM gamesystems") - systems = cursor.fetchall() - - # 4. Wir gehen jedes System durch und prüfen, ob er schon drin ist - for sys in systems: - sys_id = sys[0] - - cursor.execute("SELECT id FROM player_game_statistic WHERE player_id = ? AND gamesystem_id = ?", (dummy_id, sys_id)) - is_in_league = cursor.fetchone() - - if not is_in_league: - # Er ist noch nicht drin -> Eintragen! (MMR startet durch DEFAULT automatisch bei 1000) - cursor.execute(""" - INSERT INTO player_game_statistic (player_id, gamesystem_id) - VALUES (?, ?) - """, (dummy_id, sys_id)) - - connection.commit() - connection.close() - print("Test-Gegner 'Dummy Mc DummDumm' ist bereit und in allen Ligen angemeldet!") - - diff --git a/gui/discord_login.py b/gui/discord_login.py index ef013f0..0aa9fab 100644 --- a/gui/discord_login.py +++ b/gui/discord_login.py @@ -89,9 +89,16 @@ def setup_login_routes(): app.storage.user['authenticated'] = True app.storage.user['discord_id'] = discord_id app.storage.user['discord_name'] = discord_name - app.storage.user['db_id'] = player[0] - app.storage.user['display_name'] = player[2] - app.storage.user['discord_avatar_url'] = player[3] + + # Kompatibel mit dict (MariaDB) UND tuple (SQLite) + if isinstance(player, dict): + app.storage.user['db_id'] = player['id'] # oder der echte Spaltenname + app.storage.user['display_name'] = player['display_name'] + app.storage.user['discord_avatar_url'] = player['discord_avatar_url'] + else: + app.storage.user['db_id'] = player[0] + app.storage.user['display_name'] = player[2] + app.storage.user['discord_avatar_url'] = player['discord_avatar_url'] ui.navigate.to('/') else: diff --git a/gui/info_text/info_texts.json b/gui/info_text/info_texts.json index 3d4ccc6..0354f08 100644 --- a/gui/info_text/info_texts.json +++ b/gui/info_text/info_texts.json @@ -5,7 +5,7 @@ ], "mmr_info": [ "**MMR Punkte** sind die Liga Punkte um die gespielt wird.", - "Verliert man ein Spiel, verliert man Punkte. Und umgekehrt." + "Auf der Hauptseite findest du eine detailierte Übersicht über deine letzen Spiele und die Punkte die du verloren oder gewonnen hast." ], "match_form_info": [ "Um ein Spiel einzutragen gibt einfach deine Punkte ein. Wähle deinen Gegner aus. Und gibt seine Punkte ein.", @@ -25,5 +25,29 @@ "Verlierer: (-l_base + l_khorne - slaanesh - tzeentch) * rust_factor)" ], "tyrann_info": [], - "prügelknabe_info": [] + "prügelknabe_info": [], + "rang_info": [ + "Der Rang ergibt sich aus den MMR Punkten. Je mehr Punkte du hast, desto höher bist du wahrscheinlich in der Rangliste.", + "MMR Punkte werden nach jedem Spiel errechnet." + ], + "khorne_rule_info": [ + "Khorne mag es wenn Blut fließt!", + "Deshalb schenkt er 8 Punkte den Spielern die oft Spielen. Ist dein letztes Spiel in dieser Liga noch keine 16 Tage her, kriegst du 8 Bonus Punkte. Egal ob du gewonnen oder verloren hast." + ], + "tzeentch_rule_info": [ + "Der Gebieter der Intriegen mag es wenn geschmiedete Pläne aufgehen. Wenn du deinen Gegner mit einem großen Punkte abstand besiegst, gewährt er dir extra Puntke.", + "Sieger Punkte - Verlierer Punkte / Maximal Punkte im Spielsystem. Ergibt 0-9 Punkte, je nach Abstand." + ], + "slaanesh_rule_info": [ + "Diese Regel wird noch entworfen. Irgend eine Funktion mit dem ***Rivale*** und ***Prügelknabe***. Updates folgen!" + ], + "rust_factor_info": [ + "Hat ein Spieler schon länger als 30 Tage in einem System nicht mehr gespielt, wird ein Multiplikator auf die gewonnenen oder verlorenen MMR Punkte angewandt.", + "Bei 30 Tagen startet der Rost-Faktor bei 0,8 (80%) und erhöht sich bis 0,1 (10%) bei 90 Tagen.", + "Der Rost Faktor soll verhindern dass Gelegenheitsspieler mit einem Spiel die ganze Rangliste umkrempeln. Oder als ***Schutz*** für Wiedereinsteiger." + ], + "elo_factor_info": [ + "Der Elo Faktor errechnet sich aus den Sieger MMR Punkte und des Verlierer MMR Punkte. Für genauere Infos: Google=>'Schach Elo'", + "im Prinzip gibt er an wie wahrscheinlich es war dass der Sieger gewonnen hat. Elo 0,5 entspricht 50%. Je höher der Elo Faktor desto **weniger** Base Punkte kriegt der Sieger. Oder verliert der Verlierer." + ] } \ No newline at end of file diff --git a/gui/league_statistic_gui.py b/gui/league_statistic_gui.py index fa679cf..3a76d03 100644 --- a/gui/league_statistic_gui.py +++ b/gui/league_statistic_gui.py @@ -70,7 +70,10 @@ def setup_routes(): ui.label(str(mmr)).classes('text-4xl font-bold text-accent') with ui.card().classes("w-full items-center justify-center text-center"): - ui.label("Rang: ").classes('text-2xl font-bold') + with ui.row().classes("w-full items-center text-center"): + ui.label("Rang: ").classes('justify-center text-2xl font-bold text-normaltext') + ui.space() + info_system.create_info_button("rang_info") ui.label(str(my_rank)).classes('text-4xl font-bold text-blue-100') with ui.card().classes("w-full lg:col-span-2"): diff --git a/gui/pictures/achievments/AchievementIcon_128_01.png b/gui/pictures/achievments/AchievementIcon_128_01.png new file mode 100644 index 0000000..4c63aa4 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_01.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_02.png b/gui/pictures/achievments/AchievementIcon_128_02.png new file mode 100644 index 0000000..bbd541c Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_02.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_03.png b/gui/pictures/achievments/AchievementIcon_128_03.png new file mode 100644 index 0000000..b53a654 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_03.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_04.png b/gui/pictures/achievments/AchievementIcon_128_04.png new file mode 100644 index 0000000..3e4c665 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_04.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_05.png b/gui/pictures/achievments/AchievementIcon_128_05.png new file mode 100644 index 0000000..d6f0d44 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_05.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_06.png b/gui/pictures/achievments/AchievementIcon_128_06.png new file mode 100644 index 0000000..6c122ca Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_06.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_07.png b/gui/pictures/achievments/AchievementIcon_128_07.png new file mode 100644 index 0000000..47d5f8d Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_07.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_08.png b/gui/pictures/achievments/AchievementIcon_128_08.png new file mode 100644 index 0000000..ff2c47b Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_08.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_09.png b/gui/pictures/achievments/AchievementIcon_128_09.png new file mode 100644 index 0000000..b01ee16 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_09.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_10.png b/gui/pictures/achievments/AchievementIcon_128_10.png new file mode 100644 index 0000000..873a00e Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_10.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_11.png b/gui/pictures/achievments/AchievementIcon_128_11.png new file mode 100644 index 0000000..92be9cd Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_11.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_12.png b/gui/pictures/achievments/AchievementIcon_128_12.png new file mode 100644 index 0000000..8855db9 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_12.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_13.png b/gui/pictures/achievments/AchievementIcon_128_13.png new file mode 100644 index 0000000..c9aea12 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_13.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_14.png b/gui/pictures/achievments/AchievementIcon_128_14.png new file mode 100644 index 0000000..03ecbc3 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_14.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_15.png b/gui/pictures/achievments/AchievementIcon_128_15.png new file mode 100644 index 0000000..15ef23e Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_15.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_16.png b/gui/pictures/achievments/AchievementIcon_128_16.png new file mode 100644 index 0000000..131e8bb Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_16.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_17.png b/gui/pictures/achievments/AchievementIcon_128_17.png new file mode 100644 index 0000000..aa1ac5c Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_17.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_18.png b/gui/pictures/achievments/AchievementIcon_128_18.png new file mode 100644 index 0000000..bec0184 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_18.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_19.png b/gui/pictures/achievments/AchievementIcon_128_19.png new file mode 100644 index 0000000..9eebd4f Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_19.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_20.png b/gui/pictures/achievments/AchievementIcon_128_20.png new file mode 100644 index 0000000..c8f2546 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_20.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_21.png b/gui/pictures/achievments/AchievementIcon_128_21.png new file mode 100644 index 0000000..19097b5 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_21.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_22.png b/gui/pictures/achievments/AchievementIcon_128_22.png new file mode 100644 index 0000000..9846631 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_22.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_23.png b/gui/pictures/achievments/AchievementIcon_128_23.png new file mode 100644 index 0000000..ccdf88f Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_23.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_24.png b/gui/pictures/achievments/AchievementIcon_128_24.png new file mode 100644 index 0000000..ac1f306 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_24.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_25.png b/gui/pictures/achievments/AchievementIcon_128_25.png new file mode 100644 index 0000000..b83e261 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_25.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_26.png b/gui/pictures/achievments/AchievementIcon_128_26.png new file mode 100644 index 0000000..dd0c5be Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_26.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_27.png b/gui/pictures/achievments/AchievementIcon_128_27.png new file mode 100644 index 0000000..dbaed50 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_27.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_28.png b/gui/pictures/achievments/AchievementIcon_128_28.png new file mode 100644 index 0000000..46763b2 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_28.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_29.png b/gui/pictures/achievments/AchievementIcon_128_29.png new file mode 100644 index 0000000..bc8db3d Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_29.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_30.png b/gui/pictures/achievments/AchievementIcon_128_30.png new file mode 100644 index 0000000..e3231c9 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_30.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_31.png b/gui/pictures/achievments/AchievementIcon_128_31.png new file mode 100644 index 0000000..3864bc0 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_31.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_32.png b/gui/pictures/achievments/AchievementIcon_128_32.png new file mode 100644 index 0000000..23fdf86 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_32.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_33.png b/gui/pictures/achievments/AchievementIcon_128_33.png new file mode 100644 index 0000000..56e3f9e Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_33.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_34.png b/gui/pictures/achievments/AchievementIcon_128_34.png new file mode 100644 index 0000000..b9bad66 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_34.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_35.png b/gui/pictures/achievments/AchievementIcon_128_35.png new file mode 100644 index 0000000..66b72c2 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_35.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_36.png b/gui/pictures/achievments/AchievementIcon_128_36.png new file mode 100644 index 0000000..d834ecc Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_36.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_37.png b/gui/pictures/achievments/AchievementIcon_128_37.png new file mode 100644 index 0000000..8af3520 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_37.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_38.png b/gui/pictures/achievments/AchievementIcon_128_38.png new file mode 100644 index 0000000..dc27e89 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_38.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_39.png b/gui/pictures/achievments/AchievementIcon_128_39.png new file mode 100644 index 0000000..a212964 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_39.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_40.png b/gui/pictures/achievments/AchievementIcon_128_40.png new file mode 100644 index 0000000..782b05c Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_40.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_41.png b/gui/pictures/achievments/AchievementIcon_128_41.png new file mode 100644 index 0000000..8163aef Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_41.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_42.png b/gui/pictures/achievments/AchievementIcon_128_42.png new file mode 100644 index 0000000..a635c4b Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_42.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_43.png b/gui/pictures/achievments/AchievementIcon_128_43.png new file mode 100644 index 0000000..6486a5b Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_43.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_44.png b/gui/pictures/achievments/AchievementIcon_128_44.png new file mode 100644 index 0000000..0ddbef6 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_44.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_45.png b/gui/pictures/achievments/AchievementIcon_128_45.png new file mode 100644 index 0000000..b4552eb Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_45.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_46.png b/gui/pictures/achievments/AchievementIcon_128_46.png new file mode 100644 index 0000000..9bbd1ea Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_46.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_47.png b/gui/pictures/achievments/AchievementIcon_128_47.png new file mode 100644 index 0000000..08d9ed2 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_47.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_48.png b/gui/pictures/achievments/AchievementIcon_128_48.png new file mode 100644 index 0000000..7c13aed Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_48.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_49.png b/gui/pictures/achievments/AchievementIcon_128_49.png new file mode 100644 index 0000000..f1ef604 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_49.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_50.png b/gui/pictures/achievments/AchievementIcon_128_50.png new file mode 100644 index 0000000..8e38378 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_50.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_51.png b/gui/pictures/achievments/AchievementIcon_128_51.png new file mode 100644 index 0000000..6f5a81a Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_51.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_52.png b/gui/pictures/achievments/AchievementIcon_128_52.png new file mode 100644 index 0000000..4b8592e Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_52.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_53.png b/gui/pictures/achievments/AchievementIcon_128_53.png new file mode 100644 index 0000000..ff0e6c0 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_53.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_54.png b/gui/pictures/achievments/AchievementIcon_128_54.png new file mode 100644 index 0000000..a66acff Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_54.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_55.png b/gui/pictures/achievments/AchievementIcon_128_55.png new file mode 100644 index 0000000..bc01c71 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_55.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_56.png b/gui/pictures/achievments/AchievementIcon_128_56.png new file mode 100644 index 0000000..03333f2 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_56.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_57.png b/gui/pictures/achievments/AchievementIcon_128_57.png new file mode 100644 index 0000000..b75330f Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_57.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_58.png b/gui/pictures/achievments/AchievementIcon_128_58.png new file mode 100644 index 0000000..dbf02e3 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_58.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_59.png b/gui/pictures/achievments/AchievementIcon_128_59.png new file mode 100644 index 0000000..a17dce3 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_59.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_60.png b/gui/pictures/achievments/AchievementIcon_128_60.png new file mode 100644 index 0000000..893b940 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_60.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_61.png b/gui/pictures/achievments/AchievementIcon_128_61.png new file mode 100644 index 0000000..e6a8375 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_61.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_62.png b/gui/pictures/achievments/AchievementIcon_128_62.png new file mode 100644 index 0000000..235480e Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_62.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_63.png b/gui/pictures/achievments/AchievementIcon_128_63.png new file mode 100644 index 0000000..4d226eb Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_63.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_64.png b/gui/pictures/achievments/AchievementIcon_128_64.png new file mode 100644 index 0000000..4a9dd2b Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_64.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_65.png b/gui/pictures/achievments/AchievementIcon_128_65.png new file mode 100644 index 0000000..9794896 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_65.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_66.png b/gui/pictures/achievments/AchievementIcon_128_66.png new file mode 100644 index 0000000..2a6efe0 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_66.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_67.png b/gui/pictures/achievments/AchievementIcon_128_67.png new file mode 100644 index 0000000..4c24a59 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_67.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_68.png b/gui/pictures/achievments/AchievementIcon_128_68.png new file mode 100644 index 0000000..7b29a17 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_68.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_69.png b/gui/pictures/achievments/AchievementIcon_128_69.png new file mode 100644 index 0000000..4ba6ffd Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_69.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_70.png b/gui/pictures/achievments/AchievementIcon_128_70.png new file mode 100644 index 0000000..ac603de Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_70.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_71.png b/gui/pictures/achievments/AchievementIcon_128_71.png new file mode 100644 index 0000000..359814e Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_71.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_72.png b/gui/pictures/achievments/AchievementIcon_128_72.png new file mode 100644 index 0000000..fde4a0b Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_72.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_73.png b/gui/pictures/achievments/AchievementIcon_128_73.png new file mode 100644 index 0000000..2895617 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_73.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_74.png b/gui/pictures/achievments/AchievementIcon_128_74.png new file mode 100644 index 0000000..a2eb68c Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_74.png differ diff --git a/gui/pictures/achievments/AchievementIcon_128_75.png b/gui/pictures/achievments/AchievementIcon_128_75.png new file mode 100644 index 0000000..3619313 Binary files /dev/null and b/gui/pictures/achievments/AchievementIcon_128_75.png differ diff --git a/main.py b/main.py index 85cbfd5..78e9274 100644 --- a/main.py +++ b/main.py @@ -23,10 +23,8 @@ client_secret = os.getenv("DISCORD_CLIENT_SECRET") admin_discord_id = os.getenv("ADMIN") url = os.getenv("APP_URL") -database.check_db() logger.setup_log_db() - # 3. Seitenrouten aufbauen main_gui.setup_routes(admin_discord_id) discord_login.setup_login_routes() diff --git a/match_calculations/achievments/achievments.py b/match_calculations/achievments/achievments.py new file mode 100644 index 0000000..9bde754 --- /dev/null +++ b/match_calculations/achievments/achievments.py @@ -0,0 +1,25 @@ +from data import data_api +from wood import logger +import sqlite3 + + +def check_player_achievments(player_id): + # Schauen ob der Spieler sich nach dem Match (function wird aufgerufen NACHDEM ein Match berechnet wurde) sich ein Achievment verdient hat. + game_stats = data_api.get_player_statistic(player_id, system_id) + + # Checken ob die Gamestats da sind und geladen sind. + if not game_stats: + logger.log(f"no game_stats could be loaded for PlayerID {player_id}.") + return + else: + if game_stats["mmr"] is None: + logger.log(f"PlayerID {player_id} has no game statistics AFTER match got calculated.") + return + + played_games_achievment(player_id) + + + + +def played_games_achievment(player_id): + return diff --git a/match_calculations/calc_match.py b/match_calculations/calc_match.py index 433cd04..7939a1d 100644 --- a/match_calculations/calc_match.py +++ b/match_calculations/calc_match.py @@ -3,6 +3,7 @@ from match_calculations import calculation import json import os from wood import logger +from match_calculations.achievments import achievments point_inflation = 1 # => entspricht % @@ -96,6 +97,9 @@ def calculate_match (match_id): logger.log(f"Match{match_id}: Winner {data_api.get_player_name(winner_id)}: Base {w_base} + Khorne({w_khorne}) + Slaanesh({slaanesh}) + Tzeentch({tzeentch}) = {calc_results[winner_id]["total"]}") logger.log(f"Match{match_id}: Looser {data_api.get_player_name(looser_id)}: -Base({l_base}) + Khorne({l_khorne}) - Slaanesh({slaanesh}) - Tzeentch({tzeentch}) = {calc_results[looser_id]["total"]}") data_api.save_calculated_match(calc_results) + achievments.check_player_achievments(winner_id, system_id) + achievments.check_player_achievments(looser_id, system_id) + def determine_draw_diff(sys_id): diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 905efa9..0000000 --- a/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -nicegui -requests -python-dotenv diff --git a/setup_env.sh b/setup_env.sh index 4bb87d2..63b40dc 100644 --- a/setup_env.sh +++ b/setup_env.sh @@ -1,15 +1,88 @@ #!/bin/bash -# 1. Erstellt die virtuelle Umgebung im Ordner ".venv" (falls sie nicht existiert) +# Sicherstellen dass das Script abbricht wenn ein Fehler passiert +# -e: Beendet das Script sofort wenn ein Befehl einen Fehlercode zurückgibt (Exit-Code != 0) +set -e + +echo "✅ Starte einrichtung der Liga Umgebung. Updates und System Abhängigkeiten werden installiert ..." + +# Prüfen ob der Liga-Service auf dem System installiert ist +# systemctl list-unit-files zeigt alle registrierten Service-Dateien +SERVICE_EXISTS=false +if systemctl list-unit-files --type service | grep -q "liga.service"; then + # Service existiert → sicher anhalten bevor Updates kommen + sudo systemctl stop liga + SERVICE_EXISTS=true + echo "✅ Liga Service wurde gestoppt." +else + # Service nicht installiert → kein Fehler, nur Info + echo "ℹ️ Liga Service nicht gefunden. Wird übersprungen." +fi + +# 1. System updaten +# apt update: Lädt die aktuellen Paketlisten vom Server herunter +# apt upgrade -y: Installiert alle verfügbaren Updates, -y bestätigt automatisch +sudo apt update && sudo apt upgrade -y + +# 2. System-Abhängigkeiten installieren +# -y: Automatisch mit "Ja" bestätigen +sudo apt install -y \ + python3 \ + python3-venv \ + python3-full \ + libmariadb3 \ + libmariadb-dev + +echo "✅ Updates installiert. Überprüfe Softwarestand von Git ..." + +# Git Pull: Holt die neuesten Änderungen vom Remote-Repository +# und merged sie in den lokalen Branch +git pull + +echo "✅ Softwarestand von Git aktuell. Richte Umgebung ein ..." + +# 3. Virtuelle Umgebung erstellen (falls noch nicht vorhanden) +# Wenn .venv schon existiert wird es einfach aktualisiert/überschrieben python3 -m venv .venv -# 2. Aktiviert die virtuelle Umgebung für dieses Skript +# 4. Virtuelle Umgebung aktivieren +# WICHTIG: Da wir in einem Bash-Script sind, gilt die Aktivierung nur für dieses Script source .venv/bin/activate -# 3. Aktualisiert das Installationsprogramm "pip" auf die neueste Version +echo "✅ Umgebung eingerichtet! Installiere Pakete ..." + +# 5. pip aktualisieren +# pip ist der Paketmanager für Python – sollte aktuell sein um Kompatibilitätsprobleme zu vermeiden pip install --upgrade pip -# 4. Installiert alle Pakete aus deiner "Stückliste" -pip install -r requirements.txt +# 6. Python-Pakete installieren +pip install \ + nicegui \ + requests \ + python-dotenv \ + mariadb + +echo "✅ Pakete installiert. Starte Liga Service ..." + +# Prüfen ob der Service vorher existierte und versuche ihn zu starten +if [ "$SERVICE_EXISTS" = true ]; then + # Service existiert bereits auf dem System → versuchen zu starten + # systemctl start gibt Exit-Code 0 bei Erfolg, != 0 bei Fehler + if sudo systemctl start liga; then + echo "✅ Liga Service erfolgreich gestartet. Alles bereit!" + else + # Service konnte nicht gestartet werden – mögliche Ursachen: + # - Konfigurationsfehler in der .service Datei + # - Python-Fehler beim Start (Import-Error, Syntax-Error etc.) + # - Port bereits belegt + # - Fehlende Berechtigungen + echo "❌ FEHLER: Liga Service konnte nicht gestartet werden!" + echo " Prüfe mit: sudo systemctl status liga" + echo " Prüfe mit: sudo journalctl -u liga --no-pager -n 50" + exit 1 + fi +else + # Service war vorher auch nicht installiert – Info an den User + echo "ℹ️ Liga Service nicht installiert. Manuell starten oder Service erst einrichten." +fi -echo "Umgebung wurde erfolgreich eingerichtet!" diff --git a/wood/logger.py b/wood/logger.py index 9c51bd5..8e62e44 100644 --- a/wood/logger.py +++ b/wood/logger.py @@ -2,7 +2,6 @@ import sqlite3 import os import inspect -from data.setup_database import DB_PATH DB_FILE = "wood/log.db"