Liga-System/data/data_api.py

539 lines
16 KiB
Python

import sqlite3
import random
from data.setup_database import DB_PATH
def change_display_name(player_id, new_name):
connection = sqlite3.connect(DB_PATH)
cursor = connection.cursor()
cursor.execute("UPDATE players SET display_name = ? WHERE id = ?", (new_name, player_id))
connection.commit()
connection.close()
def generate_silly_name():
adjectives = ["Verwirrter", "Blinder", "Heulender", "Zorniger", "Chaos", "Verzweifelter", "Schreiender", "Stolpernder", "Schwitzender"]
nouns = ["Grot", "Kultist", "Servitor", "Snotling", "Guardmen", "Würfellecker", "Regelvergesser", "Meta-Chaser", "Klebschnüffler"]
adj = random.choice(adjectives)
noun = random.choice(nouns)
return f"{adj} {noun}"
def get_or_create_player(discord_id, discord_name, avatar_url):
connection = sqlite3.connect(DB_PATH)
cursor = connection.cursor()
# REPARIERT: Wir fragen 4 Dinge ab (id, discord_name, display_name, discord_avatar_url)
cursor.execute("SELECT id, discord_name, display_name, discord_avatar_url FROM players WHERE discord_id = ?", (discord_id,))
player = cursor.fetchone()
if player is None:
# Random Silly Name Generator für neue Spieler. Damit sie angeregt werden ihren richtigen Namen einzutragen.
silly_name = generate_silly_name()
cursor.execute("INSERT INTO players (discord_id, discord_name, display_name, discord_avatar_url) VALUES (?, ?, ?, ?)", (discord_id, discord_name, silly_name, avatar_url))
connection.commit()
cursor.execute("SELECT id, discord_name, display_name, discord_avatar_url FROM players WHERE discord_id = ?", (discord_id,))
player = cursor.fetchone()
else:
# Falls sich Name oder Bild auf Discord geändert haben, machen wir ein Update
cursor.execute("UPDATE players SET discord_name = ?, discord_avatar_url = ? WHERE discord_id = ?", (discord_name, avatar_url, discord_id))
connection.commit()
cursor.execute("SELECT id, discord_name, display_name, discord_avatar_url FROM players WHERE discord_id = ?", (discord_id,))
player = cursor.fetchone()
connection.close()
return player
def get_all_players():
connection = sqlite3.connect(DB_PATH)
cursor = connection.cursor()
# Alle Spieler laden, absteigend sortiert nach MMR
cursor.execute("SELECT id, name, mmr, points, games FROM players ORDER BY mmr DESC")
players = cursor.fetchall()
connection.close()
# Die Daten für das Web-GUI in eine lesbare Form umwandeln (Liste von Dictionaries)
result = []
return result
def get_all_players_from_system(system_name):
connection = sqlite3.connect(DB_PATH)
connection.row_factory = sqlite3.Row
cursor = connection.cursor()
# ID und Namen der Spieler.
# DISTINCT stellt sicher, dass jeder Spieler nur einmal vorkommt.
query = """
SELECT DISTINCT
p.id AS player_id,
p.display_name,
p.discord_name
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 = ?
ORDER BY p.display_name ASC
"""
cursor.execute(query, (system_name,))
rows = cursor.fetchall()
connection.close()
result = []
for row in rows:
result.append(dict(row))
return result
def get_gamesystem_id_by_name(system_name):
"""Holt die interne ID eines Spielsystems anhand seines Namens (z.B. 'Warhammer 40k' -> 1)."""
connection = sqlite3.connect(DB_PATH)
cursor = connection.cursor()
# Wir suchen exakt nach dem Namen
cursor.execute("SELECT id FROM gamesystems WHERE name = ?", (system_name,))
row = cursor.fetchone()
connection.close()
# Wenn wir einen Treffer haben, geben wir die erste Spalte (die ID) zurück
if row:
return row[0]
# Wenn das System nicht existiert
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():
connection = sqlite3.connect(DB_PATH)
connection.row_factory = sqlite3.Row
cursor = connection.cursor()
cursor.execute("SELECT * FROM gamesystems")
rows = cursor.fetchall()
connection.close()
# SQLite-Rows in normale Python-Dictionaries um
result = []
for row in rows:
result.append(dict(row))
return result
def get_player_statistics(player_id):
connection = sqlite3.connect(DB_PATH)
connection.row_factory = sqlite3.Row
cursor = connection.cursor()
query = """
SELECT
sys.id AS gamesystem_id,
sys.name AS gamesystem_name,
sys.*,
stat.mmr,
stat.games_in_system,
stat.points
FROM gamesystems sys
LEFT JOIN player_game_statistic stat
ON sys.id = stat.gamesystem_id AND stat.player_id = ?
"""
cursor.execute(query, (player_id,))
rows = cursor.fetchall()
connection.close()
result = []
for row in rows:
result.append(dict(row))
return result
def join_league(player_id, gamesystem_id):
connection = sqlite3.connect(DB_PATH)
cursor = connection.cursor()
# Wir fügen nur die beiden IDs ein, der Rest wird von den DEFAULT-Werten der DB erledigt
query = """
INSERT INTO player_game_statistic (player_id, gamesystem_id)
VALUES (?, ?)
"""
cursor.execute(query, (player_id, gamesystem_id))
connection.commit()
connection.close()
def get_recent_matches_for_player(player_id):
connection = sqlite3.connect(DB_PATH)
connection.row_factory = sqlite3.Row
cursor = connection.cursor()
query = """
SELECT
m.id AS match_id,
sys.name AS gamesystem_name,
m.player1_id,
p1.display_name AS p1_display,
p1.discord_name AS p1_discord,
m.score_player1,
m.player2_id,
p2.display_name AS p2_display,
p2.discord_name AS p2_discord,
m.score_player2,
m.played_at
FROM matches m
JOIN gamesystems sys ON m.gamesystem_id = sys.id
JOIN players p1 ON m.player1_id = p1.id
JOIN players p2 ON m.player2_id = p2.id
WHERE m.player1_id = ? OR m.player2_id = ?
ORDER BY m.played_at DESC
LIMIT 10
"""
cursor.execute(query, (player_id, player_id))
rows = cursor.fetchall()
connection.close()
result = []
for row in rows:
result.append(dict(row))
return result
def add_new_match(system_name, player1_id, player2_id, score_p1, score_p2):
connection = sqlite3.connect(DB_PATH)
cursor = connection.cursor()
# 1. Wir suchen die interne ID des Spielsystems anhand des Namens (z.B. "Warhammer 40k" -> 1)
cursor.execute("SELECT id FROM gamesystems WHERE name = ?", (system_name,))
sys_row = cursor.fetchone()
# Sicherheitscheck (sollte eigentlich nie passieren)
if not sys_row:
connection.close()
return None
sys_id = sys_row[0]
# 2. Das Match eintragen! (Datum 'played_at' macht SQLite durch DEFAULT CURRENT_TIMESTAMP automatisch)
query = """
INSERT INTO matches (gamesystem_id, player1_id, player2_id, score_player1, score_player2)
VALUES (?, ?, ?, ?, ?)
"""
cursor.execute(query, (sys_id, player1_id, player2_id, score_p1, score_p2))
new_match_id = cursor.lastrowid
connection.commit()
connection.close()
return new_match_id
def get_player_system_stats(player_id, system_name):
"""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)
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
def apply_match_to_player_statistic (player_id, gamesystem_id, mmr_change, scored_points):
connection = sqlite3.connect(DB_PATH)
cursor = connection.cursor()
# CURRENT_TIMESTAMP setzt die Uhrzeit für das "Letzte Spiel" automatisch auf genau JETZT.
query = """
UPDATE player_game_statistic
SET
mmr = mmr + ?,
games_in_system = games_in_system + 1,
points = points + ?,
avv_points = (points + ?) / (games_in_system + 1),
last_played = CURRENT_TIMESTAMP
WHERE player_id = ? AND gamesystem_id = ?
"""
# ACHTUNG: Wir müssen scored_points ZWEIMAL übergeben!
# Einmal für die 'points' und einmal für die Berechnung der 'avv_points'.
cursor.execute(query, (mmr_change, scored_points, scored_points, player_id, gamesystem_id))
connection.commit()
connection.close()
def get_leaderboard(system_name):
"""Holt alle Spieler eines Systems sortiert nach MMR für die Rangliste."""
connection = sqlite3.connect(DB_PATH)
connection.row_factory = sqlite3.Row
cursor = connection.cursor()
# WIR HABEN HIER EINE BEDINGUNG HINZUGEFÜGT: AND stat.games_in_system > 0
# Dadurch filtert die Datenbank direkt auf dem Server schon alle "0-Spiele"-Accounts raus.
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 = ? AND stat.games_in_system > 0
ORDER BY stat.mmr DESC
"""
cursor.execute(query, (system_name,))
rows = cursor.fetchall()
connection.close()
result = []
for row in rows:
result.append(dict(row))
return result
def get_unconfirmed_matches(player_id):
"""Holt alle offenen Matches, die der Spieler noch bestätigen muss."""
connection = sqlite3.connect(DB_PATH)
connection.row_factory = sqlite3.Row
cursor = connection.cursor()
# Wir joinen players (für den Namen des Gegners) und gamesystems (für den Systemnamen)
query = """
SELECT m.id AS match_id, m.score_player1, m.score_player2, m.played_at,
sys.name AS system_name,
p1.display_name AS p1_name
FROM matches m
JOIN gamesystems sys ON m.gamesystem_id = sys.id
JOIN players p1 ON m.player1_id = p1.id
WHERE m.player2_id = ? AND m.player2_check = 0
"""
cursor.execute(query, (player_id,))
rows = cursor.fetchall()
connection.close()
# Wir geben eine Liste mit Dictionaries zurück
return [dict(row) for row in rows]
def delete_match(match_id):
"""Löscht ein Match anhand seiner ID komplett aus der Datenbank."""
connection = sqlite3.connect(DB_PATH)
cursor = connection.cursor()
# DELETE FROM löscht die gesamte Zeile, bei der die ID übereinstimmt.
cursor.execute("DELETE FROM matches WHERE id = ?", (match_id,))
connection.commit()
connection.close()
def confirm_match(match_id):
connection = sqlite3.connect(DB_PATH)
cursor = connection.cursor()
# Ändert nur die Spalte player2_check auf 1 (True)
cursor.execute("UPDATE matches SET player2_check = 1 WHERE id = ?", (match_id,))
connection.commit()
connection.close()
def set_match_counted(match_id):
"""Setzt den Haken (1), dass das Match erfolgreich in die MMR eingeflossen ist."""
connection = sqlite3.connect(DB_PATH)
cursor = connection.cursor()
# Ändert nur die Spalte match_is_counted auf 1 (True)
cursor.execute("UPDATE matches SET match_is_counted = 1 WHERE id = ?", (match_id,))
connection.commit()
connection.close()