MMR Berechnung weiter gemacht. Dynamische Elo berechnung.
This commit is contained in:
parent
c363e35e53
commit
a715cd7bff
|
|
@ -1 +1 @@
|
|||
{"authenticated":true,"discord_id":"277898241750859776","discord_name":"mrteels","db_id":2,"display_name":"Verzweifelter Grot","discord_avatar_url":"https://cdn.discordapp.com/avatars/277898241750859776/7c3446bb51fafd72b1b4c21124b4994f.png"}
|
||||
{"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"}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"authenticated":true,"discord_id":"277898241750859776","discord_name":"mrteels","db_id":2,"display_name":"Daniel N","discord_avatar_url":"https://cdn.discordapp.com/avatars/277898241750859776/7c3446bb51fafd72b1b4c21124b4994f.png"}
|
||||
|
|
@ -9,7 +9,6 @@ if __name__ == "__main__":
|
|||
setup_database.init_db()
|
||||
|
||||
def check_db():
|
||||
# --- DATENBANK CHECK ---
|
||||
# Prüfen, ob die Datei existiert
|
||||
db_file = DB_PATH
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ load_dotenv()
|
|||
|
||||
DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "league_database.db")
|
||||
|
||||
dummy_is_in = True
|
||||
|
||||
dummy_is_in = True
|
||||
|
||||
def init_db():
|
||||
|
||||
|
|
|
|||
|
|
@ -18,9 +18,6 @@ def load_info_texts():
|
|||
|
||||
# Wir laden das Wörterbuch genau 1x beim Serverstart
|
||||
TEXT_DICTIONARY = load_info_texts()
|
||||
print(TEXT_DICTIONARY.get("league_info"))
|
||||
print(JSON_PATH)
|
||||
print(BASE_DIR)
|
||||
|
||||
# 3. Unser neuer Baustein für die Webseite
|
||||
def create_info_button(topic_key):
|
||||
|
|
@ -34,4 +31,4 @@ def create_info_button(topic_key):
|
|||
ui.markdown(sentence).classes("font-bold text-white text-lg text-center")
|
||||
|
||||
# --- DER BUTTON (Sichtbar auf der Seite) ---
|
||||
ui.button(icon="info_outline", color= "", on_click=info_dialog.open).props('round size')
|
||||
ui.button(icon="info_outline", color= "", on_click=info_dialog.open).props('round dense')
|
||||
|
|
|
|||
|
|
@ -23,9 +23,8 @@ def setup_routes():
|
|||
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 mt-10'):
|
||||
ui.label(f'Deine Statistik in {systemname}').classes('text-5xl font-bold text-blue-400')
|
||||
|
||||
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
|
||||
|
|
@ -59,13 +58,10 @@ def setup_routes():
|
|||
# --- 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"):
|
||||
with ui.row().classes("w-full items-center text-center"):
|
||||
ui.label("")
|
||||
ui.space()
|
||||
ui.label("MMR Punkte: ").classes('justify-center text-2xl font-bold')
|
||||
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')
|
||||
|
|
|
|||
|
|
@ -28,18 +28,19 @@ def setup_routes(admin_discord_id):
|
|||
# --- NAVIGATIONSLEISTE (HEADER) ---
|
||||
# ---------------------------
|
||||
|
||||
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 shadow-lg').props("reveal reveal-offset=1"):
|
||||
|
||||
# --- LINKE SEITE ---
|
||||
# Vereinslogo und den Titel in einer eigenen Reihe (Reihe 1)
|
||||
with ui.row().classes('items-center gap-4'):
|
||||
ui.image("gui/pictures/wsdg.png").classes('w-20 h-20 rounded-full')
|
||||
with ui.row().classes('items-center'):
|
||||
ui.image("gui/pictures/wsdg.png").classes('w-15 h-15 rounded-full')
|
||||
ui.label('Diceghost Liga').classes('text-2xl font-bold text-normaltext')
|
||||
|
||||
# --- MITTE ---
|
||||
discord_id = app.storage.user.get("discord_id")
|
||||
if discord_id == admin_discord_id:
|
||||
ui.button('Admin Panel', on_click=lambda: ui.navigate.to('/admin'))
|
||||
if app.storage.user.get('authenticated', False):
|
||||
discord_id = app.storage.user.get("discord_id")
|
||||
if discord_id == admin_discord_id:
|
||||
ui.button(icon="hardware", on_click=lambda: ui.navigate.to('/admin')).props("round")
|
||||
|
||||
# --- RECHTE SEITE ---
|
||||
if app.storage.user.get('authenticated', False):
|
||||
|
|
@ -53,22 +54,21 @@ def setup_routes(admin_discord_id):
|
|||
def toggle_edit_mode():
|
||||
display_row.visible = not display_row.visible
|
||||
edit_row.visible = not edit_row.visible
|
||||
edit_button.visible = not edit_button.visible
|
||||
|
||||
# --- ANSICHT 1: Der normale Text mit Edit-Button ---
|
||||
with ui.row().classes('items-center gap-2') as display_row:
|
||||
ui.label(display_name).classes('text-xl font-bold text-normaltext')
|
||||
ui.label("'aka'").classes('text-sm text-infotext')
|
||||
ui.label(discord_name).classes('text-lg text-infotext')
|
||||
|
||||
# Ein runder Button (.props('round'))
|
||||
ui.button(icon='edit', color='accent', on_click=toggle_edit_mode).props('round dense')
|
||||
with ui.column().classes('items-center gap-0') as display_row:
|
||||
with ui.column():
|
||||
ui.label(display_name).classes('text-xl font-bold text-normaltext')
|
||||
with ui.row().classes("items-center justify-between"):
|
||||
ui.label("'aka'").classes('text-sm text-italic text-infotext')
|
||||
ui.label(discord_name).classes('text-m text-bold text-infotext')
|
||||
edit_button = ui.button(icon='edit', color='accent', on_click=toggle_edit_mode).props('round dense')
|
||||
|
||||
# --- ANSICHT 2: Das Eingabefeld (startet unsichtbar!) ---
|
||||
with ui.row().classes('items-center gap-5') as edit_row:
|
||||
edit_row.visible = False # Am Anfang verstecken
|
||||
|
||||
def save_new_name():
|
||||
|
||||
new_name = name_input.value
|
||||
# Nur speichern, wenn ein Name drinsteht und er anders ist als vorher
|
||||
if new_name and new_name != display_name:
|
||||
|
|
@ -85,7 +85,6 @@ def setup_routes(admin_discord_id):
|
|||
silly_name = data_api.generate_silly_name()
|
||||
name_input.value=silly_name
|
||||
|
||||
|
||||
name_input = ui.input('Neuer Name', value=display_name).on('keydown.enter', save_new_name)
|
||||
ui.button(icon='save', color='positive', on_click=save_new_name).props('round dense')
|
||||
ui.button(icon='casino', color="accent", on_click=generate_random_silly_name).props('round dense')
|
||||
|
|
@ -93,13 +92,13 @@ def setup_routes(admin_discord_id):
|
|||
|
||||
avatar = app.storage.user.get('avatar_url')
|
||||
if avatar:
|
||||
ui.image(avatar).classes('w-12 h-12 rounded-full border-2 border-red-500')
|
||||
ui.image(avatar).classes('w-5 h-5 rounded-full border-2 border-red-500')
|
||||
|
||||
def logout():
|
||||
app.storage.user.clear()
|
||||
ui.navigate.to('/')
|
||||
|
||||
ui.button(icon="logout", on_click=logout).props('round dense')
|
||||
ui.button(icon="logout", on_click=logout).props('round dense size=lg')
|
||||
|
||||
else:
|
||||
auth_url = discord_login.get_auth_url()
|
||||
|
|
@ -116,7 +115,7 @@ def setup_routes(admin_discord_id):
|
|||
if len(unconfirmed_matches) > 0:
|
||||
# Eine auffällige, rote Karte über die volle Breite
|
||||
with ui.card().classes('w-full bg-red-900/80 border-2 border-red-500 mb-6'):
|
||||
ui.label(f"Aktion erforderlich: Du hast {len(unconfirmed_matches)} unbestätigte(s) Spiel(e)!").classes('text-2xl font-bold text-white mb-2')
|
||||
ui.label(f"Aktion erforderlich: Du hast {len(unconfirmed_matches)} offen(e) Spiel(e)!").classes('text-2xl font-bold text-normaltext mb-2')
|
||||
|
||||
for match in unconfirmed_matches:
|
||||
|
||||
|
|
@ -139,10 +138,10 @@ def setup_routes(admin_discord_id):
|
|||
# Die Buttons (Funktion machen wir im nächsten Schritt!)
|
||||
with ui.row().classes('gap-2'):
|
||||
# ABLEHNEN und Spiel löschen
|
||||
ui.button("Ablehnen", color="negative", icon="close", on_click=lambda e, m_id=match['match_id']: reject_match(m_id))
|
||||
ui.button(color="negative", icon="close", on_click=lambda e, m_id=match['match_id']: reject_match(m_id))
|
||||
ui.space()
|
||||
# BESTÄTIGEN und spiel berechnen lassen
|
||||
ui.button("Bestätigen", color="positive", icon="check", on_click=lambda e, m_id=match['match_id']: acccept_match(m_id))
|
||||
ui.button(color="positive", icon="check", on_click=lambda e, m_id=match['match_id']: acccept_match(m_id))
|
||||
|
||||
|
||||
# ---------------------------
|
||||
|
|
@ -155,7 +154,6 @@ def setup_routes(admin_discord_id):
|
|||
ui.label(f"Warten auf Gegner: Du hast {len(submitted_matches)} offene(s) Spiel(e) eingetragen.").classes('text-xl font-bold text-gray-300 mb-2')
|
||||
|
||||
for match in submitted_matches:
|
||||
|
||||
# Die Lösch-Funktion, die beim Klick ausgeführt wird
|
||||
def retract_match(m_id):
|
||||
data_api.delete_match(m_id, player_id)
|
||||
|
|
@ -177,9 +175,9 @@ def setup_routes(admin_discord_id):
|
|||
# --- Spielsysteme ---
|
||||
# ---------------------------
|
||||
if app.storage.user.get('authenticated', False):
|
||||
with ui.card().classes("w-full"):
|
||||
with ui.row().classes("w-full items-center"):
|
||||
ui.label(text="Meine Ligaplätze").classes("font-bold text-white text-xl")
|
||||
with ui.card().classes("w-full items-center"):
|
||||
with ui.row():
|
||||
ui.label(text="Meine Ligaplätze").classes("font-bold text-white text-xl text-normaltext")
|
||||
info_system.create_info_button("league_info")
|
||||
|
||||
placements = data_api.get_player_statistics(player_id)
|
||||
|
|
@ -206,12 +204,12 @@ def setup_routes(admin_discord_id):
|
|||
sys_card = ui.card().classes("h-60 w-full items-center justify-center transition-colors")
|
||||
|
||||
with sys_card:
|
||||
ui.label(text=sys_name).classes('text-xl font-bold text-center')
|
||||
ui.label(text=sys_name).classes('text-xl font-bold text-center text-normaltext')
|
||||
if sys_logo:
|
||||
ui.image(f"/pictures/{sys_logo}").classes("w-60")
|
||||
|
||||
if sys_description:
|
||||
ui.label(text=sys['description']).classes('text-xs text-gray-400 text-center mt-2')
|
||||
ui.label(text=sys['description']).classes('text-xs text-gray-400 text-center mt-2 text-infotext')
|
||||
|
||||
# Prüfen: Ist diese sys_id in den Stats des Spielers? UND hat er ein MMR?
|
||||
if sys_id not in my_stats or my_stats[sys_id]['mmr'] is None:
|
||||
|
|
@ -235,16 +233,16 @@ def setup_routes(admin_discord_id):
|
|||
sys_card.on('click', lambda e, name=sys_name: ui.navigate.to(f'/statistic/{name}'))
|
||||
|
||||
with ui.row().classes('items-center gap-4'):
|
||||
ui.label(text=f"MMR: {stat['mmr']}").classes("text-lg font-bold text-blue-400")
|
||||
ui.label(text=f"Spiele: {stat['games_in_system']}").classes("text-lg")
|
||||
ui.label(text=f"MMR: {stat['mmr']}").classes("text-lg font-bold text-accenttext")
|
||||
ui.label(text=f"Spiele: {stat['games_in_system']}").classes("text-lg font-bold text-accenttext")
|
||||
|
||||
|
||||
|
||||
# ---------------------------
|
||||
# Match Historie
|
||||
# ---------------------------
|
||||
with ui.card().classes("w-full"):
|
||||
ui.label(text= "Meine letzten Spiele").classes("font-bold text-white text-xl")
|
||||
with ui.card().classes("w-full items-center"):
|
||||
ui.label(text= "Meine letzten Spiele").classes("font-bold text-normaltext text-xl")
|
||||
# 1. Daten aus der DB holen ABER per [:5] hart auf die neuesten 5 Listen-Einträge abschneiden!
|
||||
raw_matches = data_api.get_recent_matches_for_player(player_id)[:5]
|
||||
|
||||
|
|
@ -290,5 +288,5 @@ def setup_routes(admin_discord_id):
|
|||
# NEU: Der Button, der zur großen Log-Seite führt
|
||||
ui.button("Komplette Historie & Log anzeigen", icon="history", on_click=lambda: ui.navigate.to('/matchhistory')).classes('mt-4 bg-zinc-700 text-white hover:bg-zinc-600')
|
||||
else:
|
||||
ui.label("Noch keine Spiele absolviert.").classes("text-gray-500 italic")
|
||||
ui.label("Noch keine Spiele absolviert.").classes("text-infotext italic")
|
||||
|
||||
|
|
|
|||
|
|
@ -21,9 +21,6 @@ def calculate_match (match_id):
|
|||
sys_name = match_data['gamesystem_name']
|
||||
sys_id = match_data['gamesystem_id']
|
||||
|
||||
rules = load_mmr_rule_matrix(sys_name)
|
||||
draw_diff = rules["draw_point_difference"]
|
||||
|
||||
calculated = False
|
||||
winner_id = None
|
||||
looser_id = None
|
||||
|
|
@ -31,6 +28,8 @@ def calculate_match (match_id):
|
|||
winner_score = 0
|
||||
looser_score = 0
|
||||
|
||||
draw_diff = determine_draw_diff(sys_id)
|
||||
|
||||
|
||||
# Abgrenzen ob das Match schon berechnet wurde. Weil ein Draw kann 4 Punkte unterschied haben
|
||||
# 43-41 ist ein Draw aber rein Mathematisch würde es auch ein anderes if triggern
|
||||
|
|
@ -60,7 +59,7 @@ def calculate_match (match_id):
|
|||
winner_score = p2_score
|
||||
looser_score = p1_score
|
||||
|
||||
mmr_change_winner, mmr_change_looser = calc_mmr_change.calc_mmr_change(sys_name, winner_id, looser_id, winner_score, looser_score, match_is_draw, rules)
|
||||
mmr_change_winner, mmr_change_looser = calc_mmr_change.calc_mmr_change(sys_name, winner_id, looser_id, winner_score, looser_score, match_is_draw)
|
||||
|
||||
data_api.apply_match_to_player_statistic (winner_id, sys_id, mmr_change_winner, winner_score)
|
||||
data_api.apply_match_to_player_statistic (looser_id, sys_id, mmr_change_looser, looser_score)
|
||||
|
|
@ -80,3 +79,13 @@ def calculate_match (match_id):
|
|||
# Das Match als Berechnet markieren
|
||||
data_api.set_match_counted(match_id)
|
||||
|
||||
|
||||
def determine_draw_diff(sys_id):
|
||||
draw_diff = 0
|
||||
match sys_id:
|
||||
case 1:
|
||||
draw_diff = 3
|
||||
case 2,3:
|
||||
draw_diff = 1
|
||||
|
||||
return draw_diff
|
||||
|
|
@ -21,52 +21,65 @@ def calc_mmr_change(systemname, winner_id, looser_id, winner_points, looser_poin
|
|||
factor = 0.8 - 0.7 * ((days_ago - 30) / 60)
|
||||
return round(factor, 2)
|
||||
|
||||
gamesystem_id = data_api.get_gamesystem_id_by_name(systemname)
|
||||
|
||||
def calc_mmr_change(systemname, winner_id, looser_id, winner_points, looser_points, match_is_draw, rules):
|
||||
gamesystem_id = data_api.get_gamesystem_id_by_name(systemname)
|
||||
# 1. Die aktuellen MMR-Punkte holen
|
||||
w_stat = data_api.get_player_system_stats(winner_id, gamesystem_id)
|
||||
l_stat = data_api.get_player_system_stats(looser_id, gamesystem_id)
|
||||
|
||||
# 1. Die aktuellen MMR-Punkte holen
|
||||
w_stat = data_api.get_player_statistics_for_system(winner_id, gamesystem_id)
|
||||
l_stat = data_api.get_player_statistics_for_system(looser_id, gamesystem_id)
|
||||
|
||||
w_mmr = w_stat['mmr']
|
||||
l_mmr = l_stat['mmr']
|
||||
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
|
||||
|
||||
|
||||
# 2. Die fließende Elo-Mathematik (Ersetzt die JSON Datei)
|
||||
# Berechnet die Siegwahrscheinlichkeit des Gewinners (Wert zwischen 0.0 und 1.0)
|
||||
expected_win = 1 / (1 + 10 ** ((l_mmr - w_mmr) / 400))
|
||||
# 2. Die fließende Elo-Mathematik (Ersetzt die JSON Datei)
|
||||
# Berechnet die Siegwahrscheinlichkeit des Gewinners (Wert zwischen 0.0 und 1.0)
|
||||
expected_win = 1 / (1 + 10 ** ((l_mmr - w_mmr) / 400))
|
||||
|
||||
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 - expected_win)
|
||||
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 - expected_win)
|
||||
else:
|
||||
# Sieg (1.0). Gewinnt der Favorit, gibt es wenig Punkte. Gewinnt der Underdog, gibt es viele!
|
||||
base_change = K_FACTOR * (1.0 - expected_win)
|
||||
|
||||
# 3. Den "Rostigkeits-Dämpfer" errechnen.
|
||||
w_days = data_api.get_days_since_last_system_game(winner_id, gamesystem_id)
|
||||
l_days = data_api.get_days_since_last_system_game(looser_id, gamesystem_id)
|
||||
|
||||
def get_rust_dampener(days_ago):
|
||||
if days_ago <= 30:
|
||||
return 1.0 # Volle Punkte
|
||||
elif days_ago > 90:
|
||||
return 0.1 # Maximal eingerostet (nur 10% der Punkteänderung)
|
||||
else:
|
||||
# Sieg (1.0). Gewinnt der Favorit, gibt es wenig Punkte. Gewinnt der Underdog, gibt es viele!
|
||||
base_change = K_FACTOR * (1.0 - expected_win)
|
||||
# Lineare Rampe von 0.8 (bei Tag 31) runter auf 0.1 (bei Tag 90)
|
||||
# Formel: Startwert - (Differenz * Prozentualer Weg)
|
||||
factor = 0.8 - 0.7 * ((days_ago - 30) / 60)
|
||||
return round(factor, 2)
|
||||
|
||||
# 3. Den "Rostigkeits-Dämpfer" anwenden
|
||||
w_days = data_api.get_days_since_last_system_game(winner_id, gamesystem_id)
|
||||
l_days = data_api.get_days_since_last_system_game(looser_id, gamesystem_id)
|
||||
rust_factor = get_rust_dampener(w_days)
|
||||
|
||||
w_mmr_change = base_change * rust_factor
|
||||
l_mmr_change = base_change * rust_factor
|
||||
|
||||
|
||||
# 4. Deine Sonderregeln (Slaanesh & Khorne) addieren
|
||||
sla_points = 0 #slaanesh_delight(winner_points, looser_points)
|
||||
w_mmr_change += sla_points
|
||||
|
||||
# 4. Deine Sonderregeln (Slaanesh & Khorne) addieren
|
||||
sla_points = slaanesh_delight(winner_points, looser_points, rules)
|
||||
w_mmr_change += sla_points
|
||||
# Auch der Verlierer bekommt Slaanesh Punkte addiert, wenn es die Regel so sagt
|
||||
l_mmr_change += sla_points
|
||||
|
||||
# Auch der Verlierer bekommt Slaanesh Punkte addiert, wenn es die Regel so sagt
|
||||
l_mmr_change += sla_points
|
||||
winner_final = int(w_mmr_change + wrath_of_khorne(winner_id))
|
||||
|
||||
winner_final = int(w_mmr_change + wrath_of_khorne(winner_id))
|
||||
# 5. Verlierer-Punkte abziehen und Inflation (0.7) anwenden
|
||||
if match_is_draw:
|
||||
looser_final = int(-w_mmr_change) # Bei Draw spiegeln wir es einfach (ohne Inflation)
|
||||
else:
|
||||
looser_base = int(l_mmr_change + wrath_of_khorne(looser_id))
|
||||
looser_final = -int(looser_base * point_inflation)
|
||||
|
||||
# 5. Verlierer-Punkte abziehen und Inflation (0.7) anwenden
|
||||
if match_is_draw:
|
||||
looser_final = int(-w_mmr_change) # Bei Draw spiegeln wir es einfach (ohne Inflation)
|
||||
else:
|
||||
looser_base = int(l_mmr_change + wrath_of_khorne(looser_id))
|
||||
looser_final = -int(looser_base * point_inflation)
|
||||
|
||||
return winner_final, looser_final
|
||||
return winner_final, looser_final
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user