MMR Berechnung sollte jeztt funktionieren. Muss noch testen.

Ein paar Tooltips verteilen wäre noch nett...
This commit is contained in:
Daniel Nagel 2026-03-16 15:04:08 +00:00
parent aaaf96caed
commit 931189baca
7 changed files with 178 additions and 329 deletions

View File

@ -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"} {"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"}

View File

@ -70,8 +70,6 @@ def get_or_create_player(discord_id, discord_name, avatar_url):
return player return player
def get_all_players(): def get_all_players():
connection = sqlite3.connect(DB_PATH) connection = sqlite3.connect(DB_PATH)
cursor = connection.cursor() cursor = connection.cursor()
@ -88,8 +86,6 @@ def get_all_players():
return result return result
def get_all_players_from_system(system_name): def get_all_players_from_system(system_name):
connection = sqlite3.connect(DB_PATH) connection = sqlite3.connect(DB_PATH)
connection.row_factory = sqlite3.Row connection.row_factory = sqlite3.Row
@ -119,6 +115,7 @@ def get_all_players_from_system(system_name):
return result return result
def get_gamesystem_id_by_name(system_name): def get_gamesystem_id_by_name(system_name):
"""Holt die interne ID eines Spielsystems anhand seines Namens (z.B. 'Warhammer 40k' -> 1).""" """Holt die interne ID eines Spielsystems anhand seines Namens (z.B. 'Warhammer 40k' -> 1)."""
connection = sqlite3.connect(DB_PATH) connection = sqlite3.connect(DB_PATH)
@ -138,32 +135,6 @@ def get_gamesystem_id_by_name(system_name):
return None 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(): def get_gamesystem_data():
connection = sqlite3.connect(DB_PATH) connection = sqlite3.connect(DB_PATH)
@ -305,173 +276,58 @@ def add_new_match(system_name, player1_id, player2_id, score_p1, score_p2):
def get_player_system_stats(player_id, system_name): def save_calculated_match(calc_results: dict):
"""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.
"""
connection = sqlite3.connect(DB_PATH) connection = sqlite3.connect(DB_PATH)
cursor = connection.cursor() cursor = connection.cursor()
try: 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 = """ match_query = """
UPDATE matches UPDATE matches
SET SET
player1_base_change = ?, player1_khorne = ?, player1_slaanesh = ?, player1_tzeentch = ?, player1_mmr_change = ?, player1_base_change = ?, player1_khorne = ?, player1_slaanesh = ?,
player2_base_change = ?, player2_khorne = ?, player2_slaanesh = ?, player2_tzeentch = ?, player2_mmr_change = ?, player1_tzeentch = ?, player1_mmr_change = ?,
player2_base_change = ?, player2_khorne = ?, player2_slaanesh = ?,
player2_tzeentch = ?, player2_mmr_change = ?,
rust_factor = ?, elo_factor = ?, point_inflation = ?, rust_factor = ?, elo_factor = ?, point_inflation = ?,
match_is_counted = 1 match_is_counted = 1
WHERE id = ? WHERE id = ?
""" """
cursor.execute(match_query, ( 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), p1["base"], p1["khorne"], p1["slaanesh"], p1["tzeentch"], p1["total"],
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), p2["base"], p2["khorne"], p2["slaanesh"], p2["tzeentch"], p2["total"],
results.get('rust_factor', 1.0), results.get('elo_factor', 0.5), results.get('point_inflation', 0.7), calc_results["rust_factor"], calc_results["elo_factor"], calc_results["point_inflation"],
results['match_id'] 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 = """ stat_query = """
UPDATE player_game_statistic UPDATE player_game_statistic
SET SET
@ -483,41 +339,35 @@ def save_calculated_match(results):
WHERE player_id = ? AND gamesystem_id = ? WHERE player_id = ? AND gamesystem_id = ?
""" """
# 3. Statistik für Spieler 1 überschreiben # 4. Statistik Spieler 1
cursor.execute(stat_query, ( cursor.execute(stat_query, (
results.get('p1_total', 0), results.get('p1_score', 0), results.get('p1_score', 0), p1["total"], score_p1, score_p1,
results['p1_id'], results['gamesystem_id'] player1_id, gamesystem_id
)) ))
# 4. Statistik für Spieler 2 überschreiben # 5. Statistik Spieler 2
cursor.execute(stat_query, ( cursor.execute(stat_query, (
results.get('p2_total', 0), results.get('p2_score', 0), results.get('p2_score', 0), p2["total"], score_p2, score_p2,
results['p2_id'], results['gamesystem_id'] player2_id, gamesystem_id
)) ))
# 5. WICHTIG: Erst jetzt (wenn alle 3 Befehle fehlerfrei durchliefen) speichern wir fest!
connection.commit() connection.commit()
logger.log("MATCH_CALC", f"Match ID {match_id} wurde komplett berechnet und verbucht.")
# 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.")
return True return True
except Exception as e: 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() connection.rollback()
print(f"KRITISCHER FEHLER beim Speichern des Matches: {e}") print(f"KRITISCHER FEHLER beim Speichern des Matches: {e}")
return False return False
finally: finally:
# Die Verbindung wird am Ende immer geschlossen
connection.close() connection.close()
# ----------------------------------------------------- # -----------------------------------------------------
# Get Data Funktionen # Get Data Funktionen
# ----------------------------------------------------- # -----------------------------------------------------
@ -550,6 +400,42 @@ def get_leaderboard(system_name):
return result 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): def get_player_name(player_id):
"""Gibt den Namen eines Spielers im Format 'Anzeigename (Discordname)' zurück.""" """Gibt den Namen eines Spielers im Format 'Anzeigename (Discordname)' zurück."""

View File

@ -12,5 +12,13 @@
"match_form_info": [ "match_form_info": [
"Um ein Spiel einzutragen gibt einfach deine Punkte ein. Wähle deinen Gegner aus. Und gibt seine Punkte ein.", "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!" "**ACHTUNG:** Ein Spieler ist nur als Gegner auswählbar wenn er sich in der Liga angemeldet hat!"
],
"tyrann_info":[
],
"prügelknabe_info":[
] ]
} }

View File

@ -4,11 +4,9 @@ from data import data_api
from gui.info_text import info_system from gui.info_text import info_system
def setup_routes(): def setup_routes():
# 1. Die {}-Klammern definieren eine dynamische Variable in der URL
@ui.page('/statistic/{systemname}', dark=True) @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): if not app.storage.user.get('authenticated', False):
ui.navigate.to('/') ui.navigate.to('/')
return return
@ -16,9 +14,23 @@ def setup_routes():
gui_style.apply_design() gui_style.apply_design()
player_id = app.storage.user.get('db_id') 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'): 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(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}')) ui.button("Spiel eintragen", on_click=lambda: ui.navigate.to(f'/add-match/{systemname}'))
@ -26,24 +38,18 @@ def setup_routes():
with ui.column().classes('w-full items-center justify-center mt-10'): 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') ui.label(f'Deine Statistik in {systemname}').classes('text-3xl justify-center font-bold text-normaltext')
# --- BLOCK 1 (2 Karten) --- # --- BLOCK 1 (MMR & Rang | Rangliste) ---
# 1. Daten für die Rangliste holen
leaderboard_data = data_api.get_leaderboard(systemname) leaderboard_data = data_api.get_leaderboard(systemname)
# 2. Tabelle vorbereiten und den EIGENEN Rang herausfinden
table_rows = [] table_rows = []
my_rank = "-" my_rank = "-"
for index, player in enumerate(leaderboard_data): for index, player in enumerate(leaderboard_data):
current_rank = index + 1 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: if player['id'] == player_id:
my_rank = current_rank my_rank = current_rank
table_rows.append({ table_rows.append({
'rank': current_rank, 'rank': current_rank,
'trend': '', # Platzhalter für später 'trend': '',
'name': f"{player['display_name']} 'aka' {player['discord_name']}", 'name': f"{player['display_name']} 'aka' {player['discord_name']}",
'mmr': player['mmr'] 'mmr': player['mmr']
}) })
@ -52,11 +58,9 @@ def setup_routes():
{'name': 'rank', 'label': '#', 'field': 'rank', 'align': 'left'}, {'name': 'rank', 'label': '#', 'field': 'rank', 'align': 'left'},
{'name': 'trend', 'label': 'Trend', 'field': 'trend', 'align': 'center'}, {'name': 'trend', 'label': 'Trend', 'field': 'trend', 'align': 'center'},
{'name': 'name', 'label': 'Spieler', 'field': 'name', 'align': 'left'}, {'name': 'name', 'label': 'Spieler', 'field': 'name', 'align': 'left'},
{'name': 'mmr', 'label': 'MMR', 'field': 'mmr', '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.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.column().classes("w-full gap-4"):
with ui.card().classes("w-full items-center justify-center text-center"): 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.label("MMR Punkte: ").classes('justify-center text-2xl font-bold text-normaltext')
ui.space() ui.space()
info_system.create_info_button("mmr_info") 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"): with ui.card().classes("w-full items-center justify-center text-center"):
ui.label("Rang: ").classes('text-2xl font-bold') 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') 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"): with ui.card().classes("w-full lg:col-span-2"):
ui.label("Liga Rangliste").classes("text-xl font-bold text-white mb-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') ui.table(columns=table_columns, rows=table_rows, row_key='rank').classes('w-full bg-zinc-900 text-white')
# --- BLOCK 2 (5 Karten) --- # --- BLOCK 2 (5 Karten) ---
with ui.card().classes("w-full"): 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.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"): with ui.card().classes("items-center justify-center text-center"):
ui.label("Spiele: ").classes('text-2xl font-bold') 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"): with ui.card().classes("items-center justify-center text-center"):
ui.label("Ø Punkte pro Spiel: ").classes('text-2xl font-bold') 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"): with ui.card().classes("items-center justify-center text-center"):
ui.label("Win-Rate: ").classes('text-2xl font-bold') 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"): with ui.card().classes("items-center justify-center text-center"):
ui.label("Letztes Spiel am: ").classes('text-2xl font-bold') 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') ui.label(last_played).classes('text-4xl font-bold text-blue-100')
with ui.card().classes("items-center justify-center text-center"): 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("Dein 'Prügelknabe': ").classes('text-2xl font-bold')
ui.label("-").classes('text-4xl font-bold text-blue-100') 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: 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')

View File

@ -128,6 +128,7 @@ def setup_routes(admin_discord_id):
def acccept_match(m_id): def acccept_match(m_id):
ui.notify("Spiel akzeptiert. Wird Berechnet.") ui.notify("Spiel akzeptiert. Wird Berechnet.")
ui.navigate.reload() # Lädt die Seite neu, um die Karte zu aktualisieren 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) calc_match.calculate_match(m_id)
with ui.row().classes('w-full items-center justify-between bg-zinc-900 p-3 rounded shadow-inner'): with ui.row().classes('w-full items-center justify-between bg-zinc-900 p-3 rounded shadow-inner'):

View File

@ -6,7 +6,7 @@ from wood import logger
point_inflation = 0.7 # => entspricht % 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. # 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): def calculate_match (match_id):
@ -16,20 +16,19 @@ def calculate_match (match_id):
print("Fehler: Match nicht gefunden!") print("Fehler: Match nicht gefunden!")
return return
data_api.confirm_match(match_id)
# Laden und aufdröseln der Match Daten # Laden und aufdröseln der Match Daten
p1_id = match_data['player1_id'] p1_id = match_data['player1_id']
p2_id = match_data['player2_id'] p2_id = match_data['player2_id']
p1_score = match_data['score_player1'] p1_score = match_data['score_player1']
p2_score = match_data['score_player2'] p2_score = match_data['score_player2']
sys_name = match_data['gamesystem_name'] system_name = match_data['gamesystem_name']
sys_id = match_data['gamesystem_id'] system_id = match_data['gamesystem_id']
match_is_draw = False match_is_draw = False
draw_diff = determine_draw_diff(system_id)
# ========================================== # ==========================================
# 1. IDENTIFIKATION (Bleibt gleich) # 1. IDENTIFIKATION
# ========================================== # ==========================================
# Draw # Draw
if -draw_diff <= (p1_score - p2_score) <= draw_diff: if -draw_diff <= (p1_score - p2_score) <= draw_diff:
@ -52,20 +51,20 @@ def calculate_match (match_id):
# Wir speichern die Ergebnisse direkt in einem temporären Dictionary, # Wir speichern die Ergebnisse direkt in einem temporären Dictionary,
# und nutzen die SPIELER-ID als Schlüsselwort! # und nutzen die SPIELER-ID als Schlüsselwort!
elo_factor = calculation.calc_elo_factor(winner_id, looser_id, system_name) elo_factor = calculation.calc_elo_factor(winner_id, looser_id, system_name)
base_change = calculation.calc_base_change(elo_factor, match_is_draw) base_change = int(calculation.calc_base_change(elo_factor, match_is_draw, K_FACTOR))
rust_factor = calculation.calc_rust_factor(winner_id, looser_id, gamesystem_id) rust_factor = calculation.calc_rust_factor(winner_id, looser_id, system_id)
#winner #winner
w_base = base_change w_base = int(base_change)
w_khorne = calculation.wrath_of_khorne(winner_id) w_khorne = int(calculation.wrath_of_khorne(winner_id, system_id))
#looser #looser
l_base = int(base_change*point_inflation) l_base = int(base_change*point_inflation)
l_khorne = calculation.wrath_of_khorne(looser_id) l_khorne = int(calculation.wrath_of_khorne(looser_id, system_id))
slaanesh = calculation.slaanesh_delight(),
tzeentch = calculation.tzeentch_scemes(winner_score, looser_score),
slaanesh = int(calculation.slaanesh_delight())
tzeentch = int(calculation.tzeentch_scemes(winner_score, looser_score))
# ========================================== # ==========================================
@ -77,24 +76,26 @@ def calculate_match (match_id):
"rust_factor" : rust_factor, "rust_factor" : rust_factor,
"point_inflation" : point_inflation, "point_inflation" : point_inflation,
winner_id: { "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, "base" : w_base,
"khorne" : w_khorne, "khorne" : w_khorne,
"slaanesh" : slaanesh, "slaanesh" : slaanesh,
"tzeentch" : tzeentch, "tzeentch" : tzeentch,
"total" : int((w_base + w_khorne + slaanesh + tzeentch) * rust_factor), "total" : int((w_base + w_khorne + slaanesh + tzeentch) * rust_factor),
}, },
looser_id: { looser_id: { # <-- Variable als Key (z.B. 7: {...})
"base": l_base, "base" : -l_base,
"khorne" : l_khorne, "khorne" : l_khorne,
"slaanesh" : -slaanesh, "slaanesh" : -slaanesh,
"tzeentch": calculation.tzeentch_scemes(winner_score, looser_score), "tzeentch" : -tzeentch,
"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"]), "total" : int((-l_base + l_khorne - slaanesh - tzeentch) * rust_factor),
} }
} }
data_api.save_calculated_match(calc_results)
def determine_draw_diff(sys_id): def determine_draw_diff(sys_id):
@ -104,5 +105,4 @@ def determine_draw_diff(sys_id):
draw_diff = 3 draw_diff = 3
case 2,3: case 2,3:
draw_diff = 1 draw_diff = 1
return draw_diff return draw_diff

View File

@ -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. # 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: if match_is_draw:
# Bei einem Draw (0.5) gewinnt der Schwächere leicht Punkte, der Stärkere verliert leicht. # 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) base_change = (K_FACTOR * (0.5 - elo_factor)/2)
@ -13,15 +13,17 @@ 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! # Sieg (1.0). Gewinnt der Favorit, gibt es wenig Punkte. Gewinnt der Underdog, gibt es viele!
base_change = K_FACTOR * (1.0 - elo_factor) base_change = K_FACTOR * (1.0 - elo_factor)
return base_change
def calc_elo_factor(winner_id, looser_id, system_name): 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)
# ------------------------------------------ # Passendes System aus der Liste herausfiltern
# 1. Die aktuellen MMR-Punkte holen w_stat = next((s for s in w_stats if s['gamesystem_name'] == system_name), None)
w_stat = data_api.get_player_system_stats(winner_id, systemname) l_stat = next((s for s in l_stats if s['gamesystem_name'] == system_name), None)
l_stat = data_api.get_player_system_stats(looser_id, systemname)
# 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 l_mmr = l_stat['mmr'] if l_stat and l_stat['mmr'] is not None else 1000
@ -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) l_days = data_api.get_days_since_last_system_game(looser_id, gamesystem_id)
# Der größeren der beiden Werte wird verwendet. # Der größeren der beiden Werte wird verwendet.
days = max(w_days, l_days) days_ago = max(w_days, l_days)
rust_factor = get_rust_dampener(days)
"""Berechnet den Dämpfungsfaktor basierend auf den vergangen Tagen.""" """Berechnet den Dämpfungsfaktor basierend auf den vergangen Tagen."""
if days_ago <= 30: if days_ago <= 30:
@ -51,20 +52,20 @@ def calc_rust_factor(winner_id, looser_id, gamesystem_id):
return round(factor, 2) return round(factor, 2)
def wrath_of_khorne(player_id): def wrath_of_khorne(player_id, system_id):
# ----------------- # -----------------
khorne_days = 16 khorne_days = 16
khorne_bonus = 8 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: if last_played <= khorne_days:
return khorne_bonus return khorne_bonus
else: else:
return 0 return 0
def slaanesh_delight(): def slaanesh_delight():
return 0 return 0
def tzeentch_scemes(winner_score, looser_score): def tzeentch_scemes(winner_score, looser_score):
return 0 return 0