Compare commits
No commits in common. "master" and "dev_achievments" have entirely different histories.
master
...
dev_achiev
2
.gitignore
vendored
|
|
@ -18,5 +18,3 @@ env/
|
|||
# 5. IDE / Editor Einstellungen
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
data_trans.py
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
Projekt und Repo werden nicht mehr geupdatet.
|
||||
|
||||
Werde das in ein Liga System für mehrere Clubs umwandeln. Das aktuelle System ist nur auf einen Club ausgelegt. Wer es als Grundlage verwenden will für was eigenes darf sich gerne bedienen.
|
||||
|
|
@ -190,7 +190,7 @@ def get_player_statistic(player_id, system_id):
|
|||
SELECT
|
||||
sys.id AS gamesystem_id,
|
||||
sys.name AS gamesystem_name,
|
||||
sys.description,
|
||||
sys.*,
|
||||
stat.mmr,
|
||||
stat.games_in_system,
|
||||
stat.points,
|
||||
|
|
@ -206,7 +206,6 @@ def get_player_statistic(player_id, system_id):
|
|||
ON sys.id = stat.gamesystem_id AND stat.player_id = %s
|
||||
WHERE sys.id = %s
|
||||
"""
|
||||
|
||||
cursor.execute(query, (player_id, system_id))
|
||||
row = cursor.fetchone()
|
||||
|
||||
|
|
@ -215,87 +214,21 @@ def get_player_statistic(player_id, system_id):
|
|||
return row
|
||||
|
||||
|
||||
def get_player_rank(player_id, system_id):
|
||||
"""Holt den Rang eines Spielers in einem System basierend auf MMR (absteigend)."""
|
||||
connection = db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
query = """
|
||||
SELECT COUNT(*) + 1 AS rank
|
||||
FROM player_game_statistic stat
|
||||
WHERE stat.gamesystem_id = %s
|
||||
AND stat.games_in_system > 0
|
||||
AND stat.mmr > (
|
||||
SELECT stat2.mmr
|
||||
FROM player_game_statistic stat2
|
||||
WHERE stat2.player_id = %s
|
||||
AND stat2.gamesystem_id = %s
|
||||
)
|
||||
"""
|
||||
cursor.execute(query, (system_id, player_id, system_id))
|
||||
row = cursor.fetchone()
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
if row and row['rank'] is not None:
|
||||
return row['rank']
|
||||
return None
|
||||
|
||||
|
||||
|
||||
def join_league(player_id, gamesystem_id):
|
||||
try:
|
||||
# 1. Die richtige Season für dieses System holen
|
||||
season_id = get_current_season_id(gamesystem_id)
|
||||
|
||||
connection = db_connection()
|
||||
cursor = connection.cursor()
|
||||
|
||||
query = """
|
||||
INSERT INTO player_game_statistic
|
||||
(player_id, gamesystem_id, season_id)
|
||||
VALUES (%s, %s, %s)
|
||||
"""
|
||||
cursor.execute(query, (player_id, gamesystem_id, season_id))
|
||||
connection.commit()
|
||||
|
||||
logger.log(f"Erfolg: Spieler {player_id} joined System {gamesystem_id} in Season {season_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.log(f"FEHLER beim Beitreten: {e}")
|
||||
raise
|
||||
finally:
|
||||
# Hier ist ein kleiner Check, ob die connection überhaupt existiert
|
||||
if 'connection' in locals() and connection:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
|
||||
|
||||
def get_current_season_id(gamesystem_id):
|
||||
connection = db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
cursor = connection.cursor()
|
||||
|
||||
query = """
|
||||
SELECT id FROM seasons
|
||||
WHERE gamesystem_id = %s
|
||||
ORDER BY start_date DESC
|
||||
LIMIT 1
|
||||
INSERT INTO player_game_statistic (player_id, gamesystem_id)
|
||||
VALUES (%s, %s)
|
||||
"""
|
||||
cursor.execute(query, (gamesystem_id,))
|
||||
result = cursor.fetchone()
|
||||
logger.log(f"{get_player_name(player_id)} joined {gamesystem_id}")
|
||||
cursor.execute(query, (player_id, gamesystem_id))
|
||||
connection.commit()
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
if result:
|
||||
return result['id']
|
||||
else:
|
||||
# Hier wissen wir jetzt genau, welches Spielsystem keine Saison hat
|
||||
raise RuntimeError(f"Keine Saison für {gamesystem_id} gefunden!")
|
||||
|
||||
|
||||
|
||||
# -----------------------------------------------------
|
||||
# Matches: Lesen
|
||||
|
|
@ -474,21 +407,19 @@ def get_gamesystems_data():
|
|||
return rows
|
||||
|
||||
|
||||
|
||||
def get_leaderboard(system_name):
|
||||
"""Holt alle Spieler eines Systems sortiert nach MMR für die Rangliste."""
|
||||
connection = db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
query = query = """
|
||||
SELECT p.id, p.display_name, p.discord_name, p.discord_id, p.discord_avatar_url,
|
||||
stat.mmr, stat.games_in_system, stat.wins, stat.loss, stat.avv_points
|
||||
FROM players p
|
||||
JOIN player_game_statistic stat ON p.id = stat.player_id
|
||||
JOIN gamesystems sys ON stat.gamesystem_id = sys.id
|
||||
WHERE sys.name = %s AND stat.games_in_system > 0
|
||||
ORDER BY stat.mmr DESC
|
||||
"""
|
||||
query = """
|
||||
SELECT p.id, p.display_name, p.discord_name, stat.mmr
|
||||
FROM players p
|
||||
JOIN player_game_statistic stat ON p.id = stat.player_id
|
||||
JOIN gamesystems sys ON stat.gamesystem_id = sys.id
|
||||
WHERE sys.name = %s AND stat.games_in_system > 0
|
||||
ORDER BY stat.mmr DESC
|
||||
"""
|
||||
cursor.execute(query, (system_name,))
|
||||
rows = cursor.fetchall()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
import mariadb
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
|
@ -5,24 +6,25 @@ from dotenv import load_dotenv
|
|||
load_dotenv()
|
||||
|
||||
def db_connection():
|
||||
# Wir wählen die Konfiguration basierend auf der URL
|
||||
is_prod = os.getenv("APP_URL") == "https://liga.au-fab.eu"
|
||||
|
||||
# Prefix für die Umgebungsvariablen wählen
|
||||
prefix = "DB_" if is_prod else "DB_TEST_"
|
||||
|
||||
|
||||
try:
|
||||
connection = mariadb.connect(
|
||||
host=os.getenv("DB_HOST", "localhost"),
|
||||
port=int(os.getenv("DB_PORT", 3306)),
|
||||
user=os.getenv(f"{prefix}USER"),
|
||||
user=os.getenv("DB_USER"),
|
||||
password=os.getenv("DB_PASSWORD"),
|
||||
database=os.getenv(f"{prefix}NAME")
|
||||
database=os.getenv("DB_NAME")
|
||||
)
|
||||
return connection
|
||||
if is_prod:
|
||||
print("Verbindung mit Test DB")
|
||||
|
||||
except mariadb.Error as e:
|
||||
raise RuntimeError(f"Fehler beim Verbinden mit {'Prod' if is_prod else 'Test'}-DB: {e}")
|
||||
raise RuntimeError(f"Fehler beim Verbinden mit DB: {e}")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,10 @@ from nicegui import ui, app
|
|||
from gui import gui_style
|
||||
from data import data_api
|
||||
from gui.info_text import info_system
|
||||
from gui.templates.leaderboard import Leaderboard
|
||||
|
||||
def setup_routes():
|
||||
@ui.page('/statistic/{system_name}', dark=True)
|
||||
def gamesystem_statistic_page(system_name: str):
|
||||
@ui.page('/statistic/{systemname}', dark=True)
|
||||
def gamesystem_statistic_page(systemname: str):
|
||||
|
||||
if not app.storage.user.get('authenticated', False):
|
||||
ui.navigate.to('/')
|
||||
|
|
@ -15,77 +14,112 @@ def setup_routes():
|
|||
gui_style.apply_design()
|
||||
|
||||
player_id = app.storage.user.get('db_id')
|
||||
system_id = data_api.get_gamesystem_id_by_name(system_name)
|
||||
print(player_id, system_id)
|
||||
all_stats = data_api.get_player_statistics(player_id)
|
||||
|
||||
player_stats = data_api.get_player_statistic(player_id, system_id)
|
||||
|
||||
# Header
|
||||
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/{system_name}'))
|
||||
# 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
|
||||
)
|
||||
|
||||
with ui.column().classes('w-full items-center justify-center mt-10'):
|
||||
ui.label(f'Deine Statistik in {system_name}').classes('text-3xl justify-center font-bold text-normaltext')
|
||||
if system_stat:
|
||||
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 "-"
|
||||
|
||||
# --- BLOCK 1 (MMR & Rang | Rangliste) ---
|
||||
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("MMR Punkte: ").classes('justify-center text-2xl font-bold text-normaltext')
|
||||
ui.space()
|
||||
info_system.create_info_button("mmr_info")
|
||||
ui.label(str(player_stats["mmr"])).classes('text-4xl font-bold text-accent')
|
||||
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.card().classes("w-full items-center justify-center text-center"):
|
||||
with ui.row().classes("w-full items-center text-center"):
|
||||
ui.label("Rang: ").classes('justify-center text-2xl font-bold text-normaltext')
|
||||
ui.space()
|
||||
info_system.create_info_button("rang_info")
|
||||
ui.label(str(data_api.get_player_rank(player_id,system_id))).classes('text-4xl font-bold text-blue-100')
|
||||
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')
|
||||
|
||||
|
||||
with ui.card().classes("w-full lg:col-span-2 max-h-[50vh] overflow-y-auto"):
|
||||
ui.label("Liga Rangliste").classes("text-xl font-bold text-white mb-2 top-0 bg-zinc-900 z-10")
|
||||
Leaderboard(system_name)
|
||||
# --- BLOCK 1 (MMR & Rang | Rangliste) ---
|
||||
leaderboard_data = data_api.get_leaderboard(systemname)
|
||||
table_rows = []
|
||||
my_rank = "-"
|
||||
|
||||
# --- 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"):
|
||||
for index, player in enumerate(leaderboard_data):
|
||||
current_rank = index + 1
|
||||
if player['id'] == player_id:
|
||||
my_rank = current_rank
|
||||
table_rows.append({
|
||||
'rank': current_rank,
|
||||
'trend': '➖',
|
||||
'name': f"{player['display_name']} 'aka' {player['discord_name']}",
|
||||
'mmr': player['mmr']
|
||||
})
|
||||
|
||||
with ui.card().classes("items-center justify-center text-center"):
|
||||
ui.label("Spiele: ").classes('text-2xl font-bold')
|
||||
ui.label(str(player_stats["games_in_system"])).classes('text-4xl font-bold text-blue-100')
|
||||
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'},
|
||||
]
|
||||
|
||||
with ui.card().classes("items-center justify-center text-center"):
|
||||
ui.label("Ø Punkte pro Spiel: ").classes('text-2xl font-bold')
|
||||
ui.label(str(player_stats["avv_points"])).classes('text-4xl font-bold text-blue-100')
|
||||
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("MMR Punkte: ").classes('justify-center text-2xl font-bold text-normaltext')
|
||||
ui.space()
|
||||
info_system.create_info_button("mmr_info")
|
||||
ui.label(str(mmr)).classes('text-4xl font-bold text-accent')
|
||||
|
||||
with ui.card().classes("items-center justify-center text-center"):
|
||||
ui.label("Win-Rate: ").classes('text-2xl font-bold')
|
||||
ui.label("-").classes('text-4xl font-bold text-blue-100')
|
||||
with ui.card().classes("w-full items-center justify-center text-center"):
|
||||
with ui.row().classes("w-full items-center text-center"):
|
||||
ui.label("Rang: ").classes('justify-center text-2xl font-bold text-normaltext')
|
||||
ui.space()
|
||||
info_system.create_info_button("rang_info")
|
||||
ui.label(str(my_rank)).classes('text-4xl font-bold text-blue-100')
|
||||
|
||||
with ui.card().classes("items-center justify-center text-center"):
|
||||
ui.label("Letztes Spiel am: ").classes('text-2xl font-bold')
|
||||
ui.label(str(player_stats["last_played"])).classes('text-4xl font-bold text-blue-100')
|
||||
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')
|
||||
|
||||
with ui.card().classes("items-center justify-center text-center"):
|
||||
ui.label("Win-Streak: ").classes('text-2xl font-bold')
|
||||
ui.label("-").classes('text-4xl font-bold text-blue-100')
|
||||
# --- 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"):
|
||||
|
||||
# --- BLOCK 3 (3 Karten) ---
|
||||
with ui.card().classes("w-full"):
|
||||
with ui.element('div').classes("w-full grid grid-cols-1 md:grid-cols-3 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(games)).classes('text-4xl font-bold text-blue-100')
|
||||
|
||||
with ui.card().classes("items-center justify-center text-center"):
|
||||
ui.label("Dein Nemesis: ").classes('text-2xl font-bold')
|
||||
ui.label(str(player_stats["nemesis_id"])).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(avv_points)).classes('text-4xl font-bold text-blue-100')
|
||||
|
||||
with ui.card().classes("items-center justify-center text-center"):
|
||||
ui.label("Meisten Spiele mit: ").classes('text-2xl font-bold')
|
||||
ui.label(str(player_stats["nemesis_id"])).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')
|
||||
ui.label("-").classes('text-4xl font-bold text-blue-100')
|
||||
|
||||
with ui.card().classes("items-center justify-center text-center"):
|
||||
ui.label("Dein 'Prügelknabe': ").classes('text-2xl font-bold')
|
||||
ui.label(str(player_stats["pushover_id"])).classes('text-4xl font-bold text-blue-100')
|
||||
with ui.card().classes("items-center justify-center text-center"):
|
||||
ui.label("Letztes Spiel am: ").classes('text-2xl font-bold')
|
||||
ui.label(last_played).classes('text-4xl font-bold text-blue-100')
|
||||
|
||||
with ui.card().classes("items-center justify-center text-center"):
|
||||
ui.label("Win-Streak: ").classes('text-2xl font-bold')
|
||||
ui.label("-").classes('text-4xl font-bold text-blue-100')
|
||||
|
||||
# --- BLOCK 3 (3 Karten) ---
|
||||
with ui.card().classes("w-full"):
|
||||
with ui.element('div').classes("w-full grid grid-cols-1 md:grid-cols-3 lg:grid-cols-3 gap-4"):
|
||||
|
||||
with ui.card().classes("items-center justify-center text-center"):
|
||||
ui.label("Dein Nemesis: ").classes('text-2xl font-bold')
|
||||
ui.label("-").classes('text-4xl font-bold text-blue-100')
|
||||
|
||||
with ui.card().classes("items-center justify-center text-center"):
|
||||
ui.label("Meisten Spiele mit: ").classes('text-2xl font-bold')
|
||||
ui.label("-").classes('text-4xl font-bold text-blue-100')
|
||||
|
||||
with ui.card().classes("items-center justify-center text-center"):
|
||||
ui.label("Dein 'Prügelknabe': ").classes('text-2xl font-bold')
|
||||
ui.label("-").classes('text-4xl font-bold text-blue-100')
|
||||
|
||||
else:
|
||||
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')
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
from nicegui import ui, app
|
||||
from gui import gui_style
|
||||
from data import data_api
|
||||
from gui.info_text import info_system
|
||||
|
||||
from gui.templates.leaderboard import Leaderboard
|
||||
|
||||
|
||||
|
||||
def setup_routes():
|
||||
@ui.page('/statistic/visitor/{systemname}', dark=True)
|
||||
def gamesystem_statistic_visitor_page(systemname: str):
|
||||
if not app.storage.user.get('authenticated', False):
|
||||
ui.navigate.to('/')
|
||||
return
|
||||
|
||||
gui_style.apply_design()
|
||||
|
||||
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")
|
||||
|
||||
with ui.column().classes('w-full items-center justify-center mt-10'):
|
||||
ui.label(f'Die aktuelle Statistik in {systemname}').classes('text-3xl justify-center font-bold text-normaltext')
|
||||
Leaderboard(systemname)
|
||||
|
|
@ -21,9 +21,7 @@ def setup_routes(admin_discord_id):
|
|||
|
||||
ui.navigate.reload()
|
||||
return
|
||||
|
||||
|
||||
|
||||
# -----------------------------------------
|
||||
# ---------------------------
|
||||
# --- NAVIGATIONSLEISTE (HEADER)
|
||||
# ---------------------------
|
||||
|
|
@ -33,19 +31,19 @@ def setup_routes(admin_discord_id):
|
|||
# --- LINKE SEITE ---
|
||||
# Vereinslogo und den Titel in einer eigenen Reihe (Reihe 1)
|
||||
with ui.row().classes('items-center'):
|
||||
ui.image("gui/pictures/wsdg.png").classes('w-12 h-12 md:w-25 md:h-25 rounded-full')
|
||||
ui.label('Westside Diceghost Liga').classes('text-lg md:text-3xl font-bold text-normaltext')
|
||||
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 ---
|
||||
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')).classes('w-2 h-2')
|
||||
ui.button(icon="hardware", on_click=lambda: ui.navigate.to('/admin')).props("round")
|
||||
|
||||
# --- RECHTE SEITE ---
|
||||
if app.storage.user.get('authenticated', False):
|
||||
with ui.row().classes('items-center gap-4'):
|
||||
ui.image(app.storage.user.get('discord_avatar_url')).classes('w-10 h-10 md:w-25 md:h-25 rounded-full border-1 border-red-900')
|
||||
ui.image(app.storage.user.get('discord_avatar_url')).classes('w-15 h-15 rounded-full')
|
||||
discord_name = app.storage.user.get('discord_name')
|
||||
display_name = app.storage.user.get('display_name')
|
||||
player_id = app.storage.user.get('db_id')
|
||||
|
|
@ -59,10 +57,10 @@ def setup_routes(admin_discord_id):
|
|||
# --- ANSICHT 1: Der normale Text mit Edit-Button ---
|
||||
with ui.column().classes('items-center gap-0') as display_row:
|
||||
with ui.column():
|
||||
ui.label(display_name).classes('md:text-3xl font-bold text-normaltext')
|
||||
ui.label(display_name).classes('text-xl font-bold text-normaltext')
|
||||
with ui.row().classes("items-center justify-between"):
|
||||
ui.label("'aka'").classes('md:text-lg text-xs text-italic text-infotext')
|
||||
ui.label(discord_name).classes('text-sm md:text-xl text-bold text-infotext')
|
||||
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!) ---
|
||||
|
|
@ -203,25 +201,19 @@ def setup_routes(admin_discord_id):
|
|||
sys_description = sys['description']
|
||||
|
||||
sys_card = ui.card().classes("h-60 w-full items-center justify-center transition-colors")
|
||||
|
||||
|
||||
with sys_card:
|
||||
# Wir erstellen einen Container für den "klickbaren" Teil
|
||||
clickable_content = ui.column().classes("w-full h-full items-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")
|
||||
|
||||
with clickable_content:
|
||||
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 text-infotext')
|
||||
if sys_description:
|
||||
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:
|
||||
ui.label(text="Du bist noch nicht in dieser Liga.").classes("text-red-500 font-bold")
|
||||
|
||||
clickable_content.classes("cursor-pointer hover:bg-zinc-800")
|
||||
clickable_content.on('click', lambda e, name=sys_name: ui.navigate.to(f'/statistic/visitor/{name}'))
|
||||
|
||||
join_row = ui.row().classes('items-center gap-2')
|
||||
confirm_row = ui.row().classes('items-center gap-2')
|
||||
confirm_row.visible = False
|
||||
|
|
@ -233,17 +225,18 @@ def setup_routes(admin_discord_id):
|
|||
ui.button("Liga Beitreten", color="green", on_click=lambda e, p=player_id, s=sys_id: click_join_league(p, s))
|
||||
ui.button(icon='cancel', color='red', on_click=lambda e, r1=join_row, r2=confirm_row: toggle_visibility(r1, r2)).props('round dense')
|
||||
else:
|
||||
# Spieler IST in der Liga
|
||||
stat = my_stats[sys_id]
|
||||
# Spieler IST in der Liga!
|
||||
stat = my_stats[sys_id] # Wir holen uns seine Stats aus dem Wörterbuch
|
||||
|
||||
# JETZT machen wir nur den Container klickbar, nicht die ganze Karte!
|
||||
clickable_content.classes("cursor-pointer hover:bg-zinc-800")
|
||||
clickable_content.on('click', lambda e, name=sys_name: ui.navigate.to(f'/statistic/{name}'))
|
||||
sys_card.classes("cursor-pointer hover:bg-zinc-800")
|
||||
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-accenttext")
|
||||
ui.label(text=f"Spiele: {stat['games_in_system']}").classes("text-lg font-bold text-accenttext")
|
||||
|
||||
|
||||
|
||||
# ---------------------------
|
||||
# Match Historie
|
||||
# ---------------------------
|
||||
|
|
|
|||
|
|
@ -33,12 +33,19 @@ def setup_routes():
|
|||
raw_players = data_api.get_all_players_from_system(system_name)
|
||||
my_id = app.storage.user.get('db_id')
|
||||
|
||||
with ui.column().classes("items-center justify-between"):
|
||||
p1_points = ui.number(placeholder="Punkte eingeben",
|
||||
min=system_data["min_score"],
|
||||
max=system_data["max_score"],
|
||||
precision=0
|
||||
).classes("w-35").props("clearable")
|
||||
def add_point():
|
||||
p1_points.value += 1
|
||||
|
||||
def sub_point():
|
||||
p1_points.value -= 1
|
||||
|
||||
with ui.row().classes("w-full items-center justify-between"):
|
||||
p1_points = ui.slider(min=min_score, max=max_score, value=10).classes("w-35")
|
||||
with ui.column().classes("items-center justify-between"):
|
||||
# Punkte Up- Down- Buttons. und Textanzeige
|
||||
ui.button(icon="expand_less", on_click=add_point)
|
||||
ui.label().bind_text_from(p1_points, 'value').classes("text-lg text-normaltext")
|
||||
ui.button(icon="expand_more", on_click=sub_point)
|
||||
|
||||
with ui.card().classes('w-full max-w-md mx-auto items-center mt-10 p-6'):
|
||||
dropdown_options = {}
|
||||
|
|
@ -49,39 +56,36 @@ def setup_routes():
|
|||
|
||||
opponent_select = ui.select(options=dropdown_options, label='Gegner auswählen').classes('w-full')
|
||||
|
||||
with ui.column().classes("items-center justify-between"):
|
||||
p2_points = ui.number(placeholder="Punkte eingeben",
|
||||
min=system_data["min_score"],
|
||||
max=system_data["max_score"],
|
||||
precision=0
|
||||
).classes("w-35").props("clearable")
|
||||
def add_point():
|
||||
p2_points.value += 1
|
||||
|
||||
def sub_point():
|
||||
p2_points.value -= 1
|
||||
|
||||
with ui.row().classes("w-full items-center justify-between"):
|
||||
p2_points = ui.slider(min=min_score, max=max_score, value=10).classes("w-35")
|
||||
with ui.column().classes("items-center justify-between"):
|
||||
# Punkte Up- Down- Buttons. und Textanzeige
|
||||
ui.button(icon="expand_less", on_click=add_point)
|
||||
ui.label().bind_text_from(p2_points, 'value').classes("text-lg text-normaltext")
|
||||
ui.button(icon="expand_more", on_click=sub_point)
|
||||
|
||||
|
||||
|
||||
# Das Match in die Datenbank eintragen lassen.
|
||||
# Das Match in die Datenbank eintragen lassen und die MMR Berechnung triggern.
|
||||
def input_match_to_database():
|
||||
p2_id = opponent_select.value
|
||||
score_p1 = p1_points.value
|
||||
score_p2 = p2_points.value
|
||||
|
||||
|
||||
# Check der Eingegebenen Daten.
|
||||
# Fehler wenn kein Spieler oder keine Punkte eingegeben werden. Das Punkte Eingabefeld fängt mit min_score und max_score ab,
|
||||
# ob für das Spielsystem legale Punkte eingetragen wurden.
|
||||
if p2_id is None:
|
||||
ui.notify("Bitte wähle zuerst einen Gegner aus!", color="red", position="top")
|
||||
return
|
||||
|
||||
if score_p1 is None:
|
||||
ui.notify("Ungültige Punkteeingabe Spieler 1!", color="red", position="top")
|
||||
return
|
||||
score_p1 = p1_points.value
|
||||
score_p2 = p2_points.value
|
||||
|
||||
if score_p2 is None:
|
||||
ui.notify("Ungültige Punkteeingabe Spieler 2!", color="red", position="top")
|
||||
return
|
||||
|
||||
match_id = data_api.add_new_match(system_name, my_id, p2_id, score_p1, score_p2)
|
||||
|
||||
# 4. Erfolgsmeldung und Berechnung
|
||||
ui.notify("Match erfolgreich eingetragen!", color="green")
|
||||
ui.navigate.to(f'/statistic/{system_name}')
|
||||
|
||||
# Buttons ganz unten in einer Reihe
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_43.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_44.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_45.png
Normal file
|
After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 46 KiB |
|
|
@ -1,24 +0,0 @@
|
|||
from nicegui import ui
|
||||
from data import data_api
|
||||
from gui.templates.player_card import PlayerCard
|
||||
|
||||
class Leaderboard:
|
||||
def __init__(self, system_name: str):
|
||||
# Daten holen
|
||||
leaderboard_data = data_api.get_leaderboard(system_name)
|
||||
|
||||
# Container
|
||||
with ui.column().classes('w-full gap-2') as self.container:
|
||||
for index, player in enumerate(leaderboard_data):
|
||||
PlayerCard(
|
||||
rank=index + 1,
|
||||
display_name=player['display_name'],
|
||||
discord_name=player['discord_name'],
|
||||
mmr=player['mmr'],
|
||||
discord_avatar_url=player.get('discord_avatar_url'),
|
||||
discord_id=player.get('discord_id'),
|
||||
games_in_system=player.get('games_in_system', 0),
|
||||
wins=player.get('wins', 0),
|
||||
loss=player.get('loss', 0),
|
||||
avv_points=player.get('avv_points', 0)
|
||||
)
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
from nicegui import ui
|
||||
|
||||
class PlayerCard:
|
||||
def __init__(self, rank: int, display_name: str, discord_name: str, mmr: int,
|
||||
discord_avatar_url: str = None, discord_id: str = None,
|
||||
games_in_system: int = 0, wins: int = 0, loss: int = 0, avv_points: int = 0):
|
||||
|
||||
# Rang links, Card rechts
|
||||
with ui.row().classes('w-full items-center gap-1 flex-nowrap'):
|
||||
ui.label(f'#{rank}').classes('text-2xl font-bold w-10 text-center shrink-0')
|
||||
|
||||
# Player Card
|
||||
with ui.card().classes("flex-1 no-shadow bg-zinc-900 min-w-0 overflow-hidden"):
|
||||
# Discord Block - klickbar
|
||||
if discord_id:
|
||||
with ui.link(target=f'https://discord.com/users/{discord_id}', new_tab=True).classes('no-underline w-full'):
|
||||
self._discord_content(discord_avatar_url, display_name, discord_name)
|
||||
else:
|
||||
self._discord_content(discord_avatar_url, display_name, discord_name)
|
||||
|
||||
# Stats und Achievments
|
||||
with ui.row().classes('w-full items-center flex-nowrap'):
|
||||
with ui.row().classes('flex-2 items-center gap-2 md:gap-3 md:justify-center shrink min-w-0'):
|
||||
ui.label(f'{mmr} MMR').classes('font-bold md:text-lg')
|
||||
with ui.row().classes('flex-1 justify-end items-center gap-1 md:gap-3 shrink-0'):
|
||||
ui.image('gui/pictures/achievments/AchievementIcon_4_1Sieg.png').classes('w-5 md:w-15')
|
||||
ui.image('gui/pictures/achievments/AchievementIcon_4_1Sieg.png').classes('w-5 md:w-15')
|
||||
ui.image('gui/pictures/achievments/AchievementIcon_4_1Sieg.png').classes('w-5 md:w-15')
|
||||
|
||||
|
||||
def _discord_content(self, avatar_url, display_name, discord_name):
|
||||
short_name = display_name[:15] + '...' if len(display_name) > 15 else display_name
|
||||
short_discord = discord_name[:15] + '...' if len(discord_name) > 15 else discord_name
|
||||
|
||||
with ui.card().classes("w-full md:w-90 bg-indigo-950 no-shadow"):
|
||||
with ui.row().classes('items-center gap-3 min-w-0'):
|
||||
if avatar_url:
|
||||
ui.image(avatar_url).classes('w-10 h-10 md:w-20 md:h-20 rounded-full border-red-900 border-2 shrink-0')
|
||||
with ui.column().classes('gap-0 min-w-0'):
|
||||
ui.label(short_name).classes('text-sm md:text-2xl font-bold whitespace-nowrap truncate')
|
||||
ui.label(short_discord).classes('text-xs md:text-sm opacity-60 whitespace-nowrap truncate')
|
||||
18
main.py
|
|
@ -1,13 +1,11 @@
|
|||
import os
|
||||
import schedule
|
||||
from dotenv import load_dotenv
|
||||
from nicegui import ui, app
|
||||
|
||||
from data import database
|
||||
from gui import main_gui, match_gui, discord_login, league_statistic_gui, admin_gui, match_history_gui, imprint_gui, league_statistic_visitor_gui
|
||||
from gui import main_gui, match_gui, discord_login, league_statistic_gui, admin_gui, match_history_gui, imprint_gui
|
||||
from wood import logger
|
||||
from gui.info_text import info_system
|
||||
from utils import season_scheduler
|
||||
|
||||
# 1. Lade die geheimen Variablen aus der .env Datei in den Speicher
|
||||
load_dotenv()
|
||||
|
|
@ -35,18 +33,6 @@ match_gui.setup_routes()
|
|||
admin_gui.setup_routes()
|
||||
match_history_gui.setup_routes()
|
||||
imprint_gui.setup_routes()
|
||||
league_statistic_visitor_gui.setup_routes()
|
||||
|
||||
# Timer der schedule regelmäßig prüft
|
||||
@app.on_startup
|
||||
def setup_scheduler():
|
||||
import threading
|
||||
from utils.season_scheduler import run_scheduler
|
||||
|
||||
thread = threading.Thread(target=run_scheduler, daemon=True)
|
||||
thread.start()
|
||||
|
||||
|
||||
|
||||
# 4. Starten der App Runtime auf dem Server.
|
||||
# 4. Wir starten die NiceGUI App
|
||||
ui.run(title="Westside Diceghost Liga", port=9000, storage_secret="EIN_super-geheimes_Pa$$wort#!", favicon="gui/pictures/wsdg.png")
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from wood import logger
|
|||
from match_calculations.achievments import achievments
|
||||
|
||||
|
||||
point_inflation = 1 # => entspricht % 1 == 100% 0.5 == 50%
|
||||
point_inflation = 1 # => entspricht %
|
||||
K_FACTOR = 40 # 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.
|
||||
|
|
@ -79,23 +79,23 @@ def calculate_match (match_id):
|
|||
"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 * rust_factor + w_khorne + slaanesh + tzeentch)
|
||||
"base" : w_base,
|
||||
"khorne" : w_khorne,
|
||||
"slaanesh" : slaanesh,
|
||||
"tzeentch" : tzeentch,
|
||||
"total" : int((w_base + w_khorne + slaanesh + tzeentch) * rust_factor),
|
||||
},
|
||||
looser_id: { # <-- Variable als Key (z.B. 7: {...})
|
||||
"base" : -l_base,
|
||||
"khorne" : l_khorne,
|
||||
"slaanesh" : -slaanesh,
|
||||
"tzeentch" : -tzeentch,
|
||||
"total" : int(-l_base * rust_factor + l_khorne - slaanesh - tzeentch)
|
||||
"base" : -l_base,
|
||||
"khorne" : l_khorne,
|
||||
"slaanesh" : -slaanesh,
|
||||
"tzeentch" : -tzeentch,
|
||||
"total" : int((-l_base + l_khorne - slaanesh - tzeentch) * rust_factor),
|
||||
}
|
||||
}
|
||||
|
||||
logger.log(f"...Winner: Base({w_base} × {rust_factor}) + Khorne({w_khorne}) + Slaanesh({slaanesh}) + Tzeentch({tzeentch}) = {total}")
|
||||
logger.log(f"...Loser: Base(-{l_base} × {rust_factor}) + Khorne({l_khorne}) - Slaanesh({slaanesh}) - Tzeentch({tzeentch}) = {total}")
|
||||
logger.log(f"Match{match_id}: Winner {data_api.get_player_name(winner_id)}: Base {w_base} + Khorne({w_khorne}) + Slaanesh({slaanesh}) + Tzeentch({tzeentch}) = {calc_results[winner_id]["total"]}")
|
||||
logger.log(f"Match{match_id}: Looser {data_api.get_player_name(looser_id)}: -Base({l_base}) + Khorne({l_khorne}) - Slaanesh({slaanesh}) - Tzeentch({tzeentch}) = {calc_results[looser_id]["total"]}")
|
||||
data_api.save_calculated_match(calc_results)
|
||||
achievments.check_player_achievments(winner_id, system_id)
|
||||
achievments.check_player_achievments(looser_id, system_id)
|
||||
|
|
|
|||
|
|
@ -37,17 +37,18 @@ def calc_rust_factor(winner_id, looser_id, gamesystem_id):
|
|||
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)
|
||||
|
||||
# Der größere der beiden Werte wird verwendet.
|
||||
# Der größeren der beiden Werte wird verwendet.
|
||||
days_ago = max(w_days, l_days)
|
||||
|
||||
"""Berechnet den Dämpfungsfaktor basierend auf den vergangen Tagen."""
|
||||
if days_ago <= 30:
|
||||
return 1.0 # Volle Punkte
|
||||
elif days_ago > 90:
|
||||
return 0.4 # Maximal eingerostet (40% der Punkteänderung)
|
||||
return 0.1 # Maximal eingerostet (nur 10% der Punkteänderung)
|
||||
else:
|
||||
# Lineare Rampe von 0.9 (bei Tag 31) runter auf 0.4 (bei Tag 90)
|
||||
factor = 0.9 - 0.4 * ((days_ago - 30) / 60)
|
||||
# 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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ sudo apt install -y \
|
|||
python3-venv \
|
||||
python3-full \
|
||||
libmariadb3 \
|
||||
libmariadb-dev
|
||||
libmariadb-dev
|
||||
|
||||
echo "✅ Updates installiert. Überprüfe Softwarestand von Git ..."
|
||||
|
||||
|
|
@ -60,8 +60,7 @@ pip install \
|
|||
nicegui \
|
||||
requests \
|
||||
python-dotenv \
|
||||
pymysql \
|
||||
schedule
|
||||
mariadb
|
||||
|
||||
echo "✅ Pakete installiert. Starte Liga Service ..."
|
||||
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
import schedule
|
||||
import time
|
||||
from datetime import date
|
||||
from data import database
|
||||
|
||||
|
||||
def check_new_seasons():
|
||||
today = date.today()
|
||||
|
||||
conn = database.db_connection()
|
||||
cursor = conn.cursor(dictionary=True)
|
||||
cursor.execute(
|
||||
"SELECT id, gamesystem_id FROM season_schedule "
|
||||
"WHERE start_month = %s AND start_day = %s",
|
||||
(today.month, today.day)
|
||||
)
|
||||
triggers = cursor.fetchall()
|
||||
|
||||
print(f"[Season Check] {today} – {len(triggers)} Treffer gefunden")
|
||||
|
||||
cursor.close()
|
||||
conn.close()
|
||||
|
||||
|
||||
def run_scheduler():
|
||||
"""Läuft in einem Hintergrund-Thread."""
|
||||
schedule.every().day.at("00:01").do(check_new_seasons)
|
||||
|
||||
while True:
|
||||
schedule.run_pending()
|
||||
time.sleep(60)
|
||||