Compare commits
3 Commits
master
...
dev_league
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b1031a5e7 | ||
|
|
e281160ec7 | ||
| 85e0b57833 |
|
|
@ -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.
|
||||
|
|
@ -182,41 +182,75 @@ def get_player_statistics(player_id):
|
|||
return rows
|
||||
|
||||
|
||||
def get_player_statistic(player_id, system_id):
|
||||
def get_player_statistic(player_id, system_id, season_id=None):
|
||||
"""
|
||||
- Wenn season_id angegeben: Daten für diese Season.
|
||||
- Wenn season_id = None: Aggregierte Gesamtstatistik.
|
||||
"""
|
||||
connection = db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
query = """
|
||||
SELECT
|
||||
sys.id AS gamesystem_id,
|
||||
sys.name AS gamesystem_name,
|
||||
sys.description,
|
||||
stat.mmr,
|
||||
stat.games_in_system,
|
||||
stat.points,
|
||||
stat.avv_points,
|
||||
stat.last_played,
|
||||
stat.win_rate,
|
||||
stat.trend,
|
||||
stat.tyrann_id,
|
||||
stat.pushover_id,
|
||||
stat.nemesis_id
|
||||
FROM gamesystems sys
|
||||
LEFT JOIN player_game_statistic stat
|
||||
ON sys.id = stat.gamesystem_id AND stat.player_id = %s
|
||||
WHERE sys.id = %s
|
||||
"""
|
||||
if season_id:
|
||||
query = """
|
||||
SELECT
|
||||
sys.name AS gamesystem_name,
|
||||
stat.mmr, stat.games_in_system, stat.points,
|
||||
stat.avv_points, stat.last_played, stat.win_rate,
|
||||
stat.trend, stat.tyrann_id, stat.pushover_id, stat.nemesis_id
|
||||
FROM gamesystems sys
|
||||
LEFT JOIN player_game_statistic stat
|
||||
ON sys.id = stat.gamesystem_id
|
||||
AND stat.player_id = %s
|
||||
AND stat.season_id = %s
|
||||
WHERE sys.id = %s
|
||||
"""
|
||||
cursor.execute(query, (player_id, season_id, system_id))
|
||||
else:
|
||||
query = """
|
||||
SELECT
|
||||
sys.name AS gamesystem_name,
|
||||
SUM(stat.mmr) / COUNT(stat.season_id) AS mmr, -- Durchschnitt MMR über Seasons
|
||||
SUM(stat.games_in_system) AS games_in_system,
|
||||
SUM(stat.points) AS points,
|
||||
SUM(stat.points) / SUM(stat.games_in_system) AS avv_points,
|
||||
MAX(stat.last_played) AS last_played
|
||||
FROM gamesystems sys
|
||||
LEFT JOIN player_game_statistic stat
|
||||
ON sys.id = stat.gamesystem_id
|
||||
AND stat.player_id = %s
|
||||
WHERE sys.id = %s
|
||||
GROUP BY sys.id
|
||||
"""
|
||||
cursor.execute(query, (player_id, system_id))
|
||||
|
||||
cursor.execute(query, (player_id, system_id))
|
||||
row = cursor.fetchone()
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
return row
|
||||
|
||||
|
||||
def get_player_rank(player_id, system_id):
|
||||
"""Holt den Rang eines Spielers in einem System basierend auf MMR (absteigend)."""
|
||||
def get_seasons_by_system(gamesystem_id):
|
||||
"""Holt alle Seasons eines Spielsystems, sortiert nach Nummer absteigend."""
|
||||
connection = db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
query = """
|
||||
SELECT id, name, season_number
|
||||
FROM seasons
|
||||
WHERE gamesystem_id = %s
|
||||
ORDER BY season_number DESC
|
||||
"""
|
||||
|
||||
cursor.execute(query, (gamesystem_id,))
|
||||
seasons = cursor.fetchall()
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
return seasons
|
||||
|
||||
|
||||
def get_player_rank(player_id, system_id, season_id):
|
||||
"""Holt den Rang eines Spielers in einem System für eine spezifische Season."""
|
||||
connection = db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
|
|
@ -224,23 +258,24 @@ def get_player_rank(player_id, system_id):
|
|||
SELECT COUNT(*) + 1 AS rank
|
||||
FROM player_game_statistic stat
|
||||
WHERE stat.gamesystem_id = %s
|
||||
AND stat.season_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
|
||||
AND stat2.season_id = %s
|
||||
)
|
||||
"""
|
||||
cursor.execute(query, (system_id, player_id, system_id))
|
||||
# Parameter: gamesystem, season, player, gamesystem, season
|
||||
cursor.execute(query, (system_id, season_id, player_id, system_id, season_id))
|
||||
row = cursor.fetchone()
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
if row and row['rank'] is not None:
|
||||
return row['rank']
|
||||
return None
|
||||
return row['rank'] if row and row['rank'] is not None else 1
|
||||
|
||||
|
||||
|
||||
|
|
@ -548,7 +583,7 @@ def get_player_name(player_id):
|
|||
return "Gelöschter Spieler"
|
||||
|
||||
|
||||
def get_system_name(sys_id):
|
||||
def get_system_name_by_id(sys_id):
|
||||
if sys_id is None:
|
||||
return "Unbekanntes System"
|
||||
|
||||
|
|
|
|||
|
|
@ -20,9 +20,8 @@ def db_connection():
|
|||
password=os.getenv("DB_PASSWORD"),
|
||||
database=os.getenv(f"{prefix}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}")
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from data import database, data_api
|
|||
from gui import gui_style
|
||||
from wood import logger
|
||||
from gui import main_gui
|
||||
from utils.season_scheduler import check_new_seasons
|
||||
|
||||
def setup_routes():
|
||||
@ui.page('/admin', dark=True)
|
||||
|
|
@ -15,7 +16,13 @@ def setup_routes():
|
|||
if app.storage.user.get('authenticated', False):
|
||||
with ui.row().classes("w-full"):
|
||||
ui.button(text= "test", on_click=lambda: data_api.create_random_dummy_match(2))
|
||||
ui.button(icon="refresh", on_click= lambda: ui.navigate.to("/admin") )
|
||||
ui.button(icon="refresh", on_click= lambda: ui.navigate.to("/admin") )
|
||||
def handle_manual_check():
|
||||
ui.notify('Starte Season-Check...', type='info')
|
||||
check_new_seasons()
|
||||
ui.notify('Season-Check abgeschlossen!', type='positive')
|
||||
ui.button('Admin: Season-Check erzwingen', on_click=handle_manual_check)
|
||||
|
||||
with ui.card().classes("w-full"):
|
||||
ui.label("System Audit Log").classes('text-2xl font-bold text-white mb-4')
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ from gui.templates.leaderboard import Leaderboard
|
|||
def setup_routes():
|
||||
@ui.page('/statistic/{system_name}', dark=True)
|
||||
def gamesystem_statistic_page(system_name: str):
|
||||
|
||||
if not app.storage.user.get('authenticated', False):
|
||||
ui.navigate.to('/')
|
||||
return
|
||||
|
|
@ -16,76 +15,5 @@ def setup_routes():
|
|||
|
||||
player_id = app.storage.user.get('db_id')
|
||||
system_id = data_api.get_gamesystem_id_by_name(system_name)
|
||||
print(player_id, system_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}'))
|
||||
|
||||
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')
|
||||
|
||||
# --- 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.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.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 2 (5 Karten) ---
|
||||
with ui.card().classes("w-full"):
|
||||
with ui.element('div').classes("w-full grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"):
|
||||
|
||||
with ui.card().classes("items-center justify-center text-center"):
|
||||
ui.label("Spiele: ").classes('text-2xl font-bold')
|
||||
ui.label(str(player_stats["games_in_system"])).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(player_stats["avv_points"])).classes('text-4xl font-bold text-blue-100')
|
||||
|
||||
with ui.card().classes("items-center justify-center text-center"):
|
||||
ui.label("Win-Rate: ").classes('text-2xl font-bold')
|
||||
ui.label("-").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("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(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("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("Dein 'Prügelknabe': ").classes('text-2xl font-bold')
|
||||
ui.label(str(player_stats["pushover_id"])).classes('text-4xl font-bold text-blue-100')
|
||||
|
||||
|
|
@ -72,7 +72,6 @@ def setup_routes(admin_discord_id):
|
|||
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:
|
||||
print("save")
|
||||
data_api.change_display_name(player_id, new_name) # Deine DB Funktion
|
||||
app.storage.user['display_name'] = new_name
|
||||
ui.notify('Name gespeichert!', color='positive')
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,26 +1,88 @@
|
|||
import schedule
|
||||
import time
|
||||
from datetime import date
|
||||
from data import database
|
||||
|
||||
from data import database, data_api
|
||||
from wood import logger
|
||||
|
||||
def check_new_seasons():
|
||||
print("Season mach brrrrr")
|
||||
today = date.today()
|
||||
|
||||
conn = database.db_connection()
|
||||
cursor = conn.cursor(dictionary=True)
|
||||
|
||||
# 1. Prüfen, ob für heute ein Start geplant ist
|
||||
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(triggers)
|
||||
|
||||
print(f"[Season Check] {today} – {len(triggers)} Treffer gefunden")
|
||||
for trigger in triggers:
|
||||
gamesystem_id = trigger['gamesystem_id']
|
||||
|
||||
# 2. Prüfen, ob heute schon eine Season für das System erstellt wurde
|
||||
cursor.execute(
|
||||
"SELECT id FROM seasons WHERE gamesystem_id = %s AND start_date = %s",
|
||||
(gamesystem_id, today)
|
||||
)
|
||||
if cursor.fetchone():
|
||||
continue
|
||||
|
||||
# 3. Alte Season beenden (end_date = gestern)
|
||||
cursor.execute(
|
||||
"UPDATE seasons SET end_date = DATE_SUB(CURDATE(), INTERVAL 1 DAY) "
|
||||
"WHERE gamesystem_id = %s AND end_date IS NULL",
|
||||
(gamesystem_id,)
|
||||
)
|
||||
|
||||
# 4. Neue Season anlegen
|
||||
cursor.execute(
|
||||
"SELECT MAX(season_number) as last_num FROM seasons WHERE gamesystem_id = %s",
|
||||
(gamesystem_id,)
|
||||
)
|
||||
result = cursor.fetchone()
|
||||
last_num = result['last_num'] if result['last_num'] else 0
|
||||
new_season_number = last_num + 1
|
||||
|
||||
cursor.execute(
|
||||
"INSERT INTO seasons (gamesystem_id, name, start_date, season_number) "
|
||||
"VALUES (%s, %s, %s, %s)",
|
||||
(gamesystem_id, f"{data_api.get_system_name_by_id(gamesystem_id)} - Season{new_season_number}", today, new_season_number)
|
||||
)
|
||||
new_season_id = cursor.lastrowid
|
||||
|
||||
logger.log(f"new Season for {data_api.get_system_name_by_id(gamesystem_id)}, Season ID:{new_season_id}")
|
||||
|
||||
# 5. AUTO-JOIN: Spieler der alten Season in die neue Season kopieren
|
||||
cursor.execute(
|
||||
"SELECT id FROM seasons WHERE gamesystem_id = %s AND id != %s ORDER BY id DESC LIMIT 1",
|
||||
(gamesystem_id, new_season_id)
|
||||
)
|
||||
old_season = cursor.fetchone()
|
||||
|
||||
if old_season:
|
||||
old_season_id = old_season['id']
|
||||
|
||||
# Der Auto-Join Befehl
|
||||
# Wir nehmen player_id und gamesystem_id aus der alten Season
|
||||
# und setzen die neue season_id ein.
|
||||
cursor.execute("""
|
||||
INSERT INTO player_game_statistic (player_id, gamesystem_id, season_id, wins, loss, draws, games_in_system)
|
||||
SELECT player_id, gamesystem_id, %s, 0, 0, 0, 0
|
||||
FROM player_game_statistic
|
||||
WHERE season_id = %s
|
||||
""", (new_season_id, old_season_id))
|
||||
|
||||
logger.log(f"Player copied from SeasonID {old_season_id} to SeasonID {new_season_id}")
|
||||
|
||||
conn.commit()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
|
||||
|
||||
|
||||
|
||||
def run_scheduler():
|
||||
"""Läuft in einem Hintergrund-Thread."""
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user