From 931189baca764e20383e968404c6c6fe22391859 Mon Sep 17 00:00:00 2001 From: Daniel Nagel Date: Mon, 16 Mar 2026 15:04:08 +0000 Subject: [PATCH] =?UTF-8?q?MMR=20Berechnung=20sollte=20jeztt=20funktionier?= =?UTF-8?q?en.=20Muss=20noch=20testen.=20Ein=20paar=20Tooltips=20verteilen?= =?UTF-8?q?=20w=C3=A4re=20noch=20nett...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...-21d6fdd4-43d2-4625-8dc2-9282b6aa433f.json | 2 +- data/data_api.py | 300 ++++++------------ gui/info_text/info_texts.json | 8 + gui/league_statistic.py | 105 ++---- gui/main_gui.py | 1 + match_calculations/calc_match.py | 64 ++-- match_calculations/calculation.py | 27 +- 7 files changed, 178 insertions(+), 329 deletions(-) diff --git a/.nicegui/storage-user-21d6fdd4-43d2-4625-8dc2-9282b6aa433f.json b/.nicegui/storage-user-21d6fdd4-43d2-4625-8dc2-9282b6aa433f.json index e85e364..6a6bb3f 100644 --- a/.nicegui/storage-user-21d6fdd4-43d2-4625-8dc2-9282b6aa433f.json +++ b/.nicegui/storage-user-21d6fdd4-43d2-4625-8dc2-9282b6aa433f.json @@ -1 +1 @@ -{"authenticated":true,"discord_id":"277898241750859776","discord_name":"mrteels","db_id":2,"display_name":"Schwitzender Klebschnüffler","discord_avatar_url":"https://cdn.discordapp.com/avatars/277898241750859776/7c3446bb51fafd72b1b4c21124b4994f.png"} \ No newline at end of file +{"authenticated":true,"discord_id":"277898241750859776","discord_name":"mrteels","db_id":2,"display_name":"Verwirrter Servitor","discord_avatar_url":"https://cdn.discordapp.com/avatars/277898241750859776/7c3446bb51fafd72b1b4c21124b4994f.png"} \ No newline at end of file diff --git a/data/data_api.py b/data/data_api.py index b5d5fb3..6d08e53 100644 --- a/data/data_api.py +++ b/data/data_api.py @@ -70,8 +70,6 @@ def get_or_create_player(discord_id, discord_name, avatar_url): return player - - def get_all_players(): connection = sqlite3.connect(DB_PATH) cursor = connection.cursor() @@ -88,8 +86,6 @@ def get_all_players(): return result - - def get_all_players_from_system(system_name): connection = sqlite3.connect(DB_PATH) connection.row_factory = sqlite3.Row @@ -119,6 +115,7 @@ def get_all_players_from_system(system_name): return result + 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) @@ -138,32 +135,6 @@ def get_gamesystem_id_by_name(system_name): return None -def get_player_rank(player_id, gamesystem_id): - """Sortiert die Liga nach MMR und gibt den aktuellen Platz (Rang) des Spielers zurück.""" - connection = sqlite3.connect(DB_PATH) - connection.row_factory = sqlite3.Row - cursor = connection.cursor() - - # Alle Spieler dieser Liga, sortiert nach MMR (höchstes zuerst) - query = """ - SELECT player_id - FROM player_game_statistic - WHERE gamesystem_id = ? - ORDER BY mmr DESC - """ - cursor.execute(query, (gamesystem_id,)) - rows = cursor.fetchall() - connection.close() - - # Wir zählen die Liste durch (enumerate fängt bei 0 an, also machen wir +1 für den Rang) - for index, row in enumerate(rows): - if row['player_id'] == player_id: - return index + 1 - - # Falls der Spieler nicht in der Liga ist (sollte nicht passieren) - return 999 - - def get_gamesystem_data(): connection = sqlite3.connect(DB_PATH) @@ -305,219 +276,98 @@ def add_new_match(system_name, player1_id, player2_id, score_p1, score_p2): -def get_player_system_stats(player_id, system_name): - """Holt die gespeicherten Statistiken eines Spielers für ein spezielles System direkt aus der Tabelle.""" - connection = sqlite3.connect(DB_PATH) - connection.row_factory = sqlite3.Row - cursor = connection.cursor() - - # stat.* holt einfach ALLE Spalten, die in der Tabelle player_game_statistic stehen - query = """ - SELECT stat.* - FROM player_game_statistic stat - JOIN gamesystems sys ON stat.gamesystem_id = sys.id - WHERE stat.player_id = ? AND sys.name = ? - """ - - cursor.execute(query, (player_id, system_name)) - row = cursor.fetchone() - connection.close() - - # Wenn wir was finden, machen wir ein Dictionary draus, ansonsten geben wir None zurück - if row: - return dict(row) - logger.log("data_api/Get Player Data", "get_player_system_stats returned None. New player?") - return None - - -def get_last_20_match_scores(player_id, system_name): - """Holt die erspielten Punkte und das Datum der letzten 20 Matches.""" - connection = sqlite3.connect(DB_PATH) - connection.row_factory = sqlite3.Row - cursor = connection.cursor() - - # NEU: Wir haben 'm.played_at' im SELECT hinzugefügt! - query = """ - SELECT m.player1_id, m.score_player1, m.score_player2, m.played_at - FROM matches m - JOIN gamesystems sys ON m.gamesystem_id = sys.id - WHERE sys.name = ? AND (m.player1_id = ? OR m.player2_id = ?) - ORDER BY m.played_at DESC - LIMIT 20 - """ - - cursor.execute(query, (system_name, player_id, player_id)) - rows = cursor.fetchall() - connection.close() - - # Wieder umdrehen für den zeitlichen Verlauf (links alt, rechts neu) - rows.reverse() - - points_list = [] - labels_list = [] - - for row in rows: - if row['player1_id'] == player_id: - points_list.append(row['score_player1']) - else: - points_list.append(row['score_player2']) - - # NEU: Wir schneiden das Datum ab (z.B. 2024-03-04) und nutzen es als Label! - date_clean = str(row['played_at'])[:10] - labels_list.append(date_clean) - - return {"points": points_list, "labels": labels_list} - - - -def get_match_by_id(match_id): - """Holt alle Daten eines spezifischen Matches anhand der Match-ID.""" - connection = sqlite3.connect(DB_PATH) - connection.row_factory = sqlite3.Row # Damit wir wieder ein schönes Dictionary bekommen - cursor = connection.cursor() - - # m.* holt alle Spalten aus dem Match (Punkte, IDs, Datum) - # sys.name AS gamesystem_name holt uns direkt den passenden Text für deine JSON-Ladefunktion - query = """ - SELECT m.*, sys.name AS gamesystem_name - FROM matches m - JOIN gamesystems sys ON m.gamesystem_id = sys.id - WHERE m.id = ? - """ - - cursor.execute(query, (match_id,)) - row = cursor.fetchone() - - connection.close() - - # Wenn ein Match gefunden wurde, geben wir es als Dictionary zurück - if row: - return dict(row) - - return None # Falls die ID nicht existiert -from datetime import datetime - - -def get_days_since_last_game(player_id): - """ - Sucht das absolut letzte Spiel eines Spielers (über alle Systeme) - und berechnet, wie viele Tage das her ist. - """ - connection = sqlite3.connect(DB_PATH) - cursor = connection.cursor() - - # MAX(last_played) sucht den absolut neuesten Zeitstempel aus allen Einträgen dieses Spielers - query = """ - SELECT MAX(last_played) - FROM player_game_statistic - WHERE player_id = ? - """ - - cursor.execute(query, (player_id,)) - row = cursor.fetchone() - connection.close() - - # row[0] enthält jetzt unseren Zeitstempel (z.B. '2026-03-05 14:30:00') - last_played_str = row[0] if row else None - - # Sicherheitscheck: Hat der Spieler überhaupt schon Einträge? - if not last_played_str: - return None - - try: - # [:19] schneidet Millisekunden ab, damit das Format exakt passt. - last_played_date = datetime.strptime(last_played_str[:19], '%Y-%m-%d %H:%M:%S') - - # 2. Die Differenz zu "Jetzt genau in diesem Moment" berechnen - time_difference = datetime.now() - last_played_date - - # 3. .days holt aus der Zeitdifferenz nur die reinen, vollen Tage heraus - days_ago = time_difference.days - - return { - "date_string": last_played_str[:10], # Nur das Datum (YYYY-MM-DD) für die GUI - "days_ago": days_ago # Die nackte Zahl zum Rechnen (z.B. 14) - } - - except ValueError: - # Falls in der Datenbank mal ein kaputter String steht - return None - - -# Eintragen eines berechneten Matches. -def save_calculated_match(results): - """ - Speichert alle Match-Details und aktualisiert gleichzeitig die Statistiken beider Spieler. - 'results' ist ein Dictionary (dict) mit allen berechneten Werten. - """ +def save_calculated_match(calc_results: dict): connection = sqlite3.connect(DB_PATH) cursor = connection.cursor() try: - # 1. Die Match-Tabelle updaten (Alle Detailwerte eintragen und als berechnet markieren) + 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 = ?", + (match_id,) + ) + row = cursor.fetchone() + if not row: + raise ValueError(f"Match ID {match_id} nicht in der Datenbank gefunden.") + + 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 = ?, - match_is_counted = 1 + 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 = ?, + match_is_counted = 1 WHERE id = ? """ cursor.execute(match_query, ( - results.get('p1_base', 0), results.get('p1_khorne', 0), results.get('p1_slaanesh', 0), results.get('p1_tzeentch', 0), results.get('p1_total', 0), - results.get('p2_base', 0), results.get('p2_khorne', 0), results.get('p2_slaanesh', 0), results.get('p2_tzeentch', 0), results.get('p2_total', 0), - results.get('rust_factor', 1.0), results.get('elo_factor', 0.5), results.get('point_inflation', 0.7), - results['match_id'] + p1["base"], p1["khorne"], p1["slaanesh"], p1["tzeentch"], p1["total"], + p2["base"], p2["khorne"], p2["slaanesh"], p2["tzeentch"], p2["total"], + calc_results["rust_factor"], calc_results["elo_factor"], calc_results["point_inflation"], + match_id )) - # 2. Vorlage für das Update der Spieler-Statistiken + # 2. Scores holen + cursor.execute( + "SELECT score_player1, score_player2 FROM matches WHERE id = ?", + (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 + ?, - games_in_system = games_in_system + 1, - points = points + ?, - avv_points = (points + ?) / (games_in_system + 1), - last_played = CURRENT_TIMESTAMP + SET + mmr = mmr + ?, + games_in_system = games_in_system + 1, + points = points + ?, + avv_points = (points + ?) / (games_in_system + 1), + last_played = CURRENT_TIMESTAMP WHERE player_id = ? AND gamesystem_id = ? """ - # 3. Statistik für Spieler 1 überschreiben + # 4. Statistik Spieler 1 cursor.execute(stat_query, ( - results.get('p1_total', 0), results.get('p1_score', 0), results.get('p1_score', 0), - results['p1_id'], results['gamesystem_id'] + p1["total"], score_p1, score_p1, + player1_id, gamesystem_id )) - # 4. Statistik für Spieler 2 überschreiben + # 5. Statistik Spieler 2 cursor.execute(stat_query, ( - results.get('p2_total', 0), results.get('p2_score', 0), results.get('p2_score', 0), - results['p2_id'], results['gamesystem_id'] + p2["total"], score_p2, score_p2, + player2_id, gamesystem_id )) - # 5. WICHTIG: Erst jetzt (wenn alle 3 Befehle fehlerfrei durchliefen) speichern wir fest! connection.commit() - - # 6. Ein sauberer Log-Eintrag - # (Achtung: Stelle sicher, dass du deinen 'logger' hier richtig importiert hast) - logger.log("MATCH_CALC", f"Match ID {results['match_id']} wurde komplett berechnet und verbucht.") + logger.log("MATCH_CALC", f"Match ID {match_id} wurde komplett berechnet und verbucht.") return True except Exception as e: - # FAIL-SAFE: Wenn irgendwas schiefgeht, machen wir einen ROLLBACK! - # Die Datenbank wird auf den Stand vor dieser Funktion zurückgesetzt. Nichts wird gespeichert. connection.rollback() print(f"KRITISCHER FEHLER beim Speichern des Matches: {e}") return False finally: - # Die Verbindung wird am Ende immer geschlossen connection.close() + # ----------------------------------------------------- # Get Data Funktionen # ----------------------------------------------------- @@ -550,6 +400,42 @@ def get_leaderboard(system_name): return result +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() + + try: + cursor.execute(""" + SELECT + m.id, + m.gamesystem_id, + g.name AS gamesystem_name, + m.player1_id, + m.score_player1, + m.player2_id, + m.score_player2, + m.played_at + FROM matches m + JOIN gamesystems g ON m.gamesystem_id = g.id + WHERE m.id = ? + """, (match_id,)) + + row = cursor.fetchone() + if not row: + return None + + columns = [desc[0] for desc in cursor.description] + return dict(zip(columns, row)) + + except Exception as e: + print(f"Fehler beim Laden des Matches: {e}") + return None + + finally: + connection.close() + + def get_player_name(player_id): """Gibt den Namen eines Spielers im Format 'Anzeigename (Discordname)' zurück.""" diff --git a/gui/info_text/info_texts.json b/gui/info_text/info_texts.json index b53be0a..83c107b 100644 --- a/gui/info_text/info_texts.json +++ b/gui/info_text/info_texts.json @@ -12,5 +12,13 @@ "match_form_info": [ "Um ein Spiel einzutragen gibt einfach deine Punkte ein. Wähle deinen Gegner aus. Und gibt seine Punkte ein.", "**ACHTUNG:** Ein Spieler ist nur als Gegner auswählbar wenn er sich in der Liga angemeldet hat!" + ], + + "tyrann_info":[ + + ], + + "prügelknabe_info":[ + ] } diff --git a/gui/league_statistic.py b/gui/league_statistic.py index 07137b7..a1cddfb 100644 --- a/gui/league_statistic.py +++ b/gui/league_statistic.py @@ -4,11 +4,9 @@ from data import data_api from gui.info_text import info_system def setup_routes(): - # 1. Die {}-Klammern definieren eine dynamische Variable in der URL @ui.page('/statistic/{systemname}', dark=True) - def gamesystem_statistic_page(systemname: str): # <--- WICHTIG: Hier fangen wir das Wort aus der URL auf! + def gamesystem_statistic_page(systemname: str): - # Sicherheitscheck: Ist der User eingeloggt? if not app.storage.user.get('authenticated', False): ui.navigate.to('/') return @@ -16,47 +14,53 @@ def setup_routes(): gui_style.apply_design() player_id = app.storage.user.get('db_id') - stats = data_api.get_player_system_stats(player_id, systemname) + all_stats = data_api.get_player_statistics(player_id) + + # Passendes System anhand des Namens (case-insensitive) herausfiltern + system_stat = next( + (s for s in all_stats if s["gamesystem_name"].lower() == systemname.lower()), + None + ) + + if system_stat: + # None-Werte absichern + mmr = system_stat["mmr"] or 0 + games = system_stat["games_in_system"] or 0 + points = system_stat["points"] or 0 + avv_points = system_stat.get("avv_points") or "-" + last_played_raw = system_stat.get("last_played") + last_played = str(last_played_raw)[:10] if last_played_raw else "-" - if stats: with ui.header().classes('items-center justify-between bg-zinc-900 p-4 shadow-lg'): ui.button(icon="arrow_back", on_click=lambda: ui.navigate.to('/')).props("round") ui.button("Spiel eintragen", on_click=lambda: ui.navigate.to(f'/add-match/{systemname}')) - + with ui.column().classes('w-full items-center justify-center mt-10'): ui.label(f'Deine Statistik in {systemname}').classes('text-3xl justify-center font-bold text-normaltext') - # --- BLOCK 1 (2 Karten) --- - # 1. Daten für die Rangliste holen + # --- BLOCK 1 (MMR & Rang | Rangliste) --- leaderboard_data = data_api.get_leaderboard(systemname) - - # 2. Tabelle vorbereiten und den EIGENEN Rang herausfinden table_rows = [] my_rank = "-" for index, player in enumerate(leaderboard_data): current_rank = index + 1 - - # Wenn wir in der Liste über uns selbst stolpern, merken wir uns den Rang für unsere Karte! if player['id'] == player_id: my_rank = current_rank - table_rows.append({ 'rank': current_rank, - 'trend': '➖', # Platzhalter für später + 'trend': '➖', 'name': f"{player['display_name']} 'aka' {player['discord_name']}", 'mmr': player['mmr'] }) table_columns = [ - {'name': 'rank', 'label': '#', 'field': 'rank', 'align': 'left'}, - {'name': 'trend', 'label': 'Trend', 'field': 'trend', 'align': 'center'}, - {'name': 'name', 'label': 'Spieler', 'field': 'name', 'align': 'left'}, - {'name': 'mmr', 'label': 'MMR', 'field': 'mmr', 'align': 'left'} + {'name': 'rank', 'label': '#', 'field': 'rank', 'align': 'left'}, + {'name': 'trend', 'label': 'Trend', 'field': 'trend', 'align': 'center'}, + {'name': 'name', 'label': 'Spieler', 'field': 'name', 'align': 'left'}, + {'name': 'mmr', 'label': 'MMR', 'field': 'mmr', 'align': 'left'}, ] - # --- BLOCK 1 (Links: MMR & Rang | Rechts: Rangliste) --- - # lg:grid-cols-3 teilt den Bildschirm auf großen Monitoren in 3 gleich große unsichtbare Spalten with ui.element('div').classes("w-full grid grid-cols-1 lg:grid-cols-3 gap-4 mt-4"): with ui.column().classes("w-full gap-4"): with ui.card().classes("w-full items-center justify-center text-center"): @@ -64,30 +68,27 @@ def setup_routes(): ui.label("MMR Punkte: ").classes('justify-center text-2xl font-bold text-normaltext') ui.space() info_system.create_info_button("mmr_info") - ui.label(str(stats["mmr"])).classes('text-4xl font-bold text-accent') + 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') - # Hier tragen wir jetzt unsere gefundene Platzierung ein! ui.label(str(my_rank)).classes('text-4xl font-bold text-blue-100') - # RECHTE SEITE (Belegt 2 Spalten -> lg:col-span-2) with ui.card().classes("w-full lg:col-span-2"): ui.label("Liga Rangliste").classes("text-xl font-bold text-white mb-2") ui.table(columns=table_columns, rows=table_rows, row_key='rank').classes('w-full bg-zinc-900 text-white') - # --- BLOCK 2 (5 Karten) --- with ui.card().classes("w-full"): with ui.element('div').classes("w-full grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"): with ui.card().classes("items-center justify-center text-center"): ui.label("Spiele: ").classes('text-2xl font-bold') - ui.label(str(stats["games_in_system"])).classes('text-4xl font-bold text-blue-100') + ui.label(str(games)).classes('text-4xl font-bold text-blue-100') with ui.card().classes("items-center justify-center text-center"): ui.label("Ø Punkte pro Spiel: ").classes('text-2xl font-bold') - ui.label(str(stats["avv_points"])).classes('text-4xl font-bold text-blue-100') + ui.label(str(avv_points)).classes('text-4xl font-bold text-blue-100') with ui.card().classes("items-center justify-center text-center"): ui.label("Win-Rate: ").classes('text-2xl font-bold') @@ -95,8 +96,6 @@ def setup_routes(): with ui.card().classes("items-center justify-center text-center"): ui.label("Letztes Spiel am: ").classes('text-2xl font-bold') - # Schneidet die Zeit vom Datum ab, falls eins existiert - last_played = str(stats["last_played"])[:10] if stats["last_played"] else "-" ui.label(last_played).classes('text-4xl font-bold text-blue-100') with ui.card().classes("items-center justify-center text-center"): @@ -119,52 +118,6 @@ def setup_routes(): ui.label("Dein 'Prügelknabe': ").classes('text-2xl font-bold') ui.label("-").classes('text-4xl font-bold text-blue-100') - - # --- BLOCK 4 (Diagramm der letzten 20 Spiele) --- - with ui.card().classes("w-full items-center"): - ui.label("Punkte in den letzten 20 Spielen").classes('text-2xl font-bold mb-4') - - # 1. Daten aus der API holen - chart_data = data_api.get_last_20_match_scores(player_id, systemname) - - # 2. Nur zeichnen, wenn es überhaupt schon Spiele gibt! - if len(chart_data['points']) > 0: - ui.echart({ - 'xAxis': { - 'type': 'category', - 'data': chart_data['labels'], - 'axisLabel': {'color': '#e5e7eb'} - }, - 'yAxis': { - 'type': 'value', - 'axisLabel': {'color': '#e5e7eb'}, - 'splitLine': {'lineStyle': {'color': '#3f3f46'}} - }, - 'series': [ - { - 'name': 'Punkte', # Zeigt im Tooltip "Punkte: 76" statt einer unbenannten Zahl - 'type': 'line', - 'data': chart_data['points'], - 'smooth': True, - 'color': '#60a5fa' - } - ], - 'tooltip': { - 'trigger': 'axis', - 'backgroundColor': '#27272a', - 'textStyle': {'color': '#ffffff'}, - 'borderColor': '#3f3f46' # Ein feiner Rand für den Kasten - } - }).classes('w-full h-64') - - else: - ui.label("Keine Statistiken für dieses System gefunden.").classes('text-red-500 mt-4') - - - - - - - - + with ui.column().classes('w-full items-center justify-center mt-10'): + ui.label(f'Keine Statistik für "{systemname}" gefunden.').classes('text-red-500 text-xl mt-4') diff --git a/gui/main_gui.py b/gui/main_gui.py index 97f7d57..dc0c5f6 100644 --- a/gui/main_gui.py +++ b/gui/main_gui.py @@ -128,6 +128,7 @@ def setup_routes(admin_discord_id): def acccept_match(m_id): ui.notify("Spiel akzeptiert. Wird Berechnet.") ui.navigate.reload() # Lädt die Seite neu, um die Karte zu aktualisieren + data_api.confirm_match(m_id) calc_match.calculate_match(m_id) with ui.row().classes('w-full items-center justify-between bg-zinc-900 p-3 rounded shadow-inner'): diff --git a/match_calculations/calc_match.py b/match_calculations/calc_match.py index 8195bdb..7621740 100644 --- a/match_calculations/calc_match.py +++ b/match_calculations/calc_match.py @@ -6,7 +6,7 @@ from wood import logger point_inflation = 0.7 # => entspricht % -K_FACTOR = 40 # Die "Border" (Maximalpunkte) die ein Sieg gibt. +K_FACTOR = 30 # Die "Border" (Maximalpunkte) die ein Sieg gibt. # Mach die DB abfrage für die Relevanten Daten. Von hier aus werden die "Aufgaben" und Daten dann an die kleineren Berechnungs Funktionen verteilt. def calculate_match (match_id): @@ -16,20 +16,19 @@ def calculate_match (match_id): print("Fehler: Match nicht gefunden!") return - data_api.confirm_match(match_id) - # Laden und aufdröseln der Match Daten p1_id = match_data['player1_id'] p2_id = match_data['player2_id'] p1_score = match_data['score_player1'] p2_score = match_data['score_player2'] - sys_name = match_data['gamesystem_name'] - sys_id = match_data['gamesystem_id'] + system_name = match_data['gamesystem_name'] + system_id = match_data['gamesystem_id'] match_is_draw = False + draw_diff = determine_draw_diff(system_id) # ========================================== -# 1. IDENTIFIKATION (Bleibt gleich) +# 1. IDENTIFIKATION # ========================================== # Draw if -draw_diff <= (p1_score - p2_score) <= draw_diff: @@ -52,49 +51,51 @@ def calculate_match (match_id): # Wir speichern die Ergebnisse direkt in einem temporären Dictionary, # und nutzen die SPIELER-ID als Schlüsselwort! elo_factor = calculation.calc_elo_factor(winner_id, looser_id, system_name) - base_change = calculation.calc_base_change(elo_factor, match_is_draw) - rust_factor = calculation.calc_rust_factor(winner_id, looser_id, gamesystem_id) + base_change = int(calculation.calc_base_change(elo_factor, match_is_draw, K_FACTOR)) + rust_factor = calculation.calc_rust_factor(winner_id, looser_id, system_id) + #winner - w_base = base_change - w_khorne = calculation.wrath_of_khorne(winner_id) + w_base = int(base_change) + w_khorne = int(calculation.wrath_of_khorne(winner_id, system_id)) #looser l_base = int(base_change*point_inflation) - l_khorne = calculation.wrath_of_khorne(looser_id) - - slaanesh = calculation.slaanesh_delight(), - tzeentch = calculation.tzeentch_scemes(winner_score, looser_score), + l_khorne = int(calculation.wrath_of_khorne(looser_id, system_id)) + slaanesh = int(calculation.slaanesh_delight()) + tzeentch = int(calculation.tzeentch_scemes(winner_score, looser_score)) # ========================================== # 3. Daten Verpacken # ========================================== calc_results = { - "match_id" : match_id, - "elo_factor" : elo_factor, - "rust_factor" : rust_factor, + "match_id" : match_id, + "elo_factor" : elo_factor, + "rust_factor" : rust_factor, "point_inflation" : point_inflation, - winner_id: { - "base": w_base, - "khorne": w_khorne, - "slaanesh": slaanesh, - "tzeentch": tzeentch, - "total": int((w_base + w_khorne + slaanesh + tzeentch)*rust_factor), + "winner_id" : winner_id, # <-- String-Key für save_calculated_match + "looser_id" : looser_id, # <-- String-Key für save_calculated_match + + winner_id: { # <-- Variable als Key (z.B. 42: {...}) + "base" : w_base, + "khorne" : w_khorne, + "slaanesh" : slaanesh, + "tzeentch" : tzeentch, + "total" : int((w_base + w_khorne + slaanesh + tzeentch) * rust_factor), }, - looser_id: { - "base": l_base, - "khorne": l_khorne, - "slaanesh": -slaanesh, - "tzeentch": calculation.tzeentch_scemes(winner_score, looser_score), - "total": int((calc_results[looser_id]["base"] + calc_results[looser_id]["khorne"] - calc_results[looser_id]["slaanesh"] - calc_results[looser_id]["tzeentch"])*calc_results["rust_factor"]), + looser_id: { # <-- Variable als Key (z.B. 7: {...}) + "base" : -l_base, + "khorne" : l_khorne, + "slaanesh" : -slaanesh, + "tzeentch" : -tzeentch, + "total" : int((-l_base + l_khorne - slaanesh - tzeentch) * rust_factor), } } - - + data_api.save_calculated_match(calc_results) def determine_draw_diff(sys_id): @@ -104,5 +105,4 @@ def determine_draw_diff(sys_id): draw_diff = 3 case 2,3: draw_diff = 1 - return draw_diff \ No newline at end of file diff --git a/match_calculations/calculation.py b/match_calculations/calculation.py index 2694157..9262e85 100644 --- a/match_calculations/calculation.py +++ b/match_calculations/calculation.py @@ -5,7 +5,7 @@ from wood import logger # die meisten Spieler über 1000MMR sein. Sprich: Neueinsteiger, oder leute die weniger spielen sind eher im unteren Ende als in der Mitte. -def calc_base_change(elo_factor, match_is_draw): +def calc_base_change(elo_factor, match_is_draw, K_FACTOR): if match_is_draw: # Bei einem Draw (0.5) gewinnt der Schwächere leicht Punkte, der Stärkere verliert leicht. base_change = (K_FACTOR * (0.5 - elo_factor)/2) @@ -13,19 +13,21 @@ def calc_base_change(elo_factor, match_is_draw): # Sieg (1.0). Gewinnt der Favorit, gibt es wenig Punkte. Gewinnt der Underdog, gibt es viele! base_change = K_FACTOR * (1.0 - elo_factor) + return base_change + def calc_elo_factor(winner_id, looser_id, system_name): + w_stats = data_api.get_player_statistics(winner_id) + l_stats = data_api.get_player_statistics(looser_id) - # ------------------------------------------ - # 1. Die aktuellen MMR-Punkte holen - w_stat = data_api.get_player_system_stats(winner_id, systemname) - l_stat = data_api.get_player_system_stats(looser_id, systemname) + # Passendes System aus der Liste herausfiltern + w_stat = next((s for s in w_stats if s['gamesystem_name'] == system_name), None) + l_stat = next((s for s in l_stats if s['gamesystem_name'] == system_name), None) - # Wenn ein Spieler noch keine keine Stats hat wird None zurück gegeben. Fallback für diesen Fall - w_mmr = w_stat['mmr'] if w_stat and w_stat['mmr'] is not None else 1000 + w_mmr = w_stat['mmr'] if w_stat and w_stat['mmr'] is not None else 1000 l_mmr = l_stat['mmr'] if l_stat and l_stat['mmr'] is not None else 1000 - return (1 / (1 + 10 ** ((l_mmr - w_mmr)/400))) + return (1 / (1 + 10 ** ((l_mmr - w_mmr) / 400))) @@ -36,8 +38,7 @@ def calc_rust_factor(winner_id, looser_id, gamesystem_id): l_days = data_api.get_days_since_last_system_game(looser_id, gamesystem_id) # Der größeren der beiden Werte wird verwendet. - days = max(w_days, l_days) - rust_factor = get_rust_dampener(days) + days_ago = max(w_days, l_days) """Berechnet den Dämpfungsfaktor basierend auf den vergangen Tagen.""" if days_ago <= 30: @@ -51,20 +52,20 @@ def calc_rust_factor(winner_id, looser_id, gamesystem_id): return round(factor, 2) -def wrath_of_khorne(player_id): +def wrath_of_khorne(player_id, system_id): # ----------------- khorne_days = 16 khorne_bonus = 8 # ----------------- - last_played = data_api.get_days_since_last_game(player_id)["days_ago"] + last_played = int(data_api.get_days_since_last_system_game(player_id, system_id)) if last_played <= khorne_days: return khorne_bonus else: return 0 - def slaanesh_delight(): return 0 + def tzeentch_scemes(winner_score, looser_score): return 0