dev #19

Merged
daniel merged 12 commits from dev into master 2026-03-18 07:20:51 +01:00
9 changed files with 96 additions and 83 deletions
Showing only changes of commit a715cd7bff - Show all commits

View File

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

View File

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

View File

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

View File

@ -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():

View File

@ -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')

View File

@ -23,10 +23,9 @@ 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
leaderboard_data = data_api.get_leaderboard(systemname)
@ -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')

View File

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

View File

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

View File

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