dev von master #30
498
data/data_api.py
|
|
@ -1,100 +1,120 @@
|
|||
import sqlite3
|
||||
import random
|
||||
from wood import logger
|
||||
|
||||
from data.setup_database import DB_PATH
|
||||
from data.database import db_connection
|
||||
|
||||
|
||||
# -----------------------------------------------------
|
||||
# User / Session
|
||||
# -----------------------------------------------------
|
||||
|
||||
def validate_user_session(db_id, discord_id):
|
||||
"""Prüft, ob das Cookie noch zur aktuellen Datenbank passt."""
|
||||
connection = sqlite3.connect(DB_PATH)
|
||||
connection = db_connection()
|
||||
cursor = connection.cursor()
|
||||
|
||||
cursor.execute("SELECT discord_id FROM players WHERE id = ?", (db_id,))
|
||||
cursor.execute("SELECT discord_id FROM players WHERE id = %s", (db_id,))
|
||||
result = cursor.fetchone()
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
# 1. Fall: Die ID gibt es gar nicht mehr in der Datenbank
|
||||
if result is None:
|
||||
logger.log(f"Player not found in database. Discord:{discord_id}")
|
||||
return False
|
||||
|
||||
# 2. Fall: Die ID gehört jetzt einem anderen Discord-Account (Datenbank wurde resettet)
|
||||
if str(result[0]) != str(discord_id):
|
||||
logger.log(f"Player with false coockies logged in! {discord_id} doesnt belong to {db_id}")
|
||||
return False
|
||||
|
||||
# 3. Fall: Alles ist korrekt!
|
||||
return True
|
||||
|
||||
|
||||
def change_display_name(player_id, new_name):
|
||||
connection = sqlite3.connect(DB_PATH)
|
||||
connection = db_connection()
|
||||
cursor = connection.cursor()
|
||||
|
||||
cursor.execute("UPDATE players SET display_name = ? WHERE id = ?", (new_name, player_id))
|
||||
cursor.execute("UPDATE players SET display_name = %s WHERE id = %s", (new_name, player_id))
|
||||
|
||||
connection.commit()
|
||||
cursor.close()
|
||||
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}"
|
||||
adjectives = ["Verwirrter", "Blinder", "Heulender", "Zorniger", "Chaos",
|
||||
"Verzweifelter", "Schreiender", "Stolpernder", "Schwitzender"]
|
||||
nouns = ["Grot", "Kultist", "Servitor", "Snotling", "Guardmen",
|
||||
"Würfellecker", "Regelvergesser", "Meta-Chaser", "Klebschnüffler"]
|
||||
return f"{random.choice(adjectives)} {random.choice(nouns)}"
|
||||
|
||||
|
||||
def get_or_create_player(discord_id, discord_name, avatar_url):
|
||||
connection = sqlite3.connect(DB_PATH)
|
||||
cursor = connection.cursor()
|
||||
connection = db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
# 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,))
|
||||
cursor.execute(
|
||||
"SELECT id, discord_name, display_name, discord_avatar_url FROM players WHERE discord_id = %s",
|
||||
(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))
|
||||
logger.log(f"new player added. Discord:{discord_name}")
|
||||
cursor.execute(
|
||||
"INSERT INTO players (discord_id, discord_name, display_name, discord_avatar_url) "
|
||||
"VALUES (%s, %s, %s, %s)",
|
||||
(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,))
|
||||
logger.log(f"new player added. Discord:{discord_name}")
|
||||
|
||||
cursor.execute(
|
||||
"SELECT id, discord_name, display_name, discord_avatar_url FROM players WHERE discord_id = %s",
|
||||
(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))
|
||||
cursor.execute(
|
||||
"UPDATE players SET discord_name = %s, discord_avatar_url = %s WHERE discord_id = %s",
|
||||
(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,))
|
||||
cursor.execute(
|
||||
"SELECT id, discord_name, display_name, discord_avatar_url FROM players WHERE discord_id = %s",
|
||||
(discord_id,)
|
||||
)
|
||||
player = cursor.fetchone()
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
return player
|
||||
|
||||
|
||||
# -----------------------------------------------------
|
||||
# Player-Listen
|
||||
# -----------------------------------------------------
|
||||
|
||||
def get_all_players():
|
||||
connection = sqlite3.connect(DB_PATH)
|
||||
cursor = connection.cursor()
|
||||
"""Alle Spieler – Basisdaten."""
|
||||
connection = db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
# Alle Spieler laden, absteigend sortiert nach MMR
|
||||
cursor.execute("SELECT id, name, mmr, points, games FROM players ORDER BY mmr DESC")
|
||||
players = cursor.fetchall()
|
||||
cursor.execute(
|
||||
"SELECT id, display_name, discord_name, discord_avatar_url FROM players "
|
||||
"ORDER BY display_name ASC"
|
||||
)
|
||||
rows = cursor.fetchall()
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
# Die Daten für das Web-GUI in eine lesbare Form umwandeln (Liste von Dictionaries)
|
||||
result = []
|
||||
|
||||
return result
|
||||
return rows
|
||||
|
||||
|
||||
def get_all_players_from_system(system_name):
|
||||
connection = sqlite3.connect(DB_PATH)
|
||||
connection.row_factory = sqlite3.Row
|
||||
cursor = connection.cursor()
|
||||
connection = db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
# ID und Namen der Spieler.
|
||||
# DISTINCT stellt sicher, dass jeder Spieler nur einmal vorkommt.
|
||||
query = """
|
||||
SELECT DISTINCT
|
||||
p.id AS player_id,
|
||||
|
|
@ -103,47 +123,37 @@ def get_all_players_from_system(system_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 = ?
|
||||
WHERE sys.name = %s
|
||||
ORDER BY p.display_name ASC
|
||||
"""
|
||||
|
||||
cursor.execute(query, (system_name,))
|
||||
rows = cursor.fetchall()
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
result = []
|
||||
for row in rows:
|
||||
result.append(dict(row))
|
||||
|
||||
return result
|
||||
return rows
|
||||
|
||||
|
||||
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)
|
||||
connection = db_connection()
|
||||
cursor = connection.cursor()
|
||||
|
||||
# Wir suchen exakt nach dem Namen
|
||||
cursor.execute("SELECT id FROM gamesystems WHERE name = ?", (system_name,))
|
||||
cursor.execute("SELECT id FROM gamesystems WHERE name = %s", (system_name,))
|
||||
row = cursor.fetchone()
|
||||
|
||||
cursor.close()
|
||||
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
|
||||
|
||||
|
||||
return row[0] if row else None
|
||||
|
||||
|
||||
# -----------------------------------------------------
|
||||
# Player Statistics
|
||||
# -----------------------------------------------------
|
||||
|
||||
def get_player_statistics(player_id):
|
||||
connection = sqlite3.connect(DB_PATH)
|
||||
connection.row_factory = sqlite3.Row
|
||||
cursor = connection.cursor()
|
||||
connection = db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
query = """
|
||||
SELECT
|
||||
|
|
@ -162,42 +172,71 @@ def get_player_statistics(player_id):
|
|||
stat.nemesis_id
|
||||
FROM gamesystems sys
|
||||
LEFT JOIN player_game_statistic stat
|
||||
ON sys.id = stat.gamesystem_id AND stat.player_id = ?
|
||||
ON sys.id = stat.gamesystem_id AND stat.player_id = %s
|
||||
"""
|
||||
cursor.execute(query, (player_id,))
|
||||
rows = cursor.fetchall()
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
result = []
|
||||
for row in rows:
|
||||
result.append(dict(row))
|
||||
|
||||
return result
|
||||
return rows
|
||||
|
||||
|
||||
def get_player_statistic(player_id, system_id):
|
||||
connection = db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
query = """
|
||||
SELECT
|
||||
sys.id AS gamesystem_id,
|
||||
sys.name AS gamesystem_name,
|
||||
sys.*,
|
||||
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
|
||||
"""
|
||||
cursor.execute(query, (player_id, system_id))
|
||||
row = cursor.fetchone()
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
return row
|
||||
|
||||
|
||||
def join_league(player_id, gamesystem_id):
|
||||
connection = sqlite3.connect(DB_PATH)
|
||||
connection = db_connection()
|
||||
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 (?, ?)
|
||||
VALUES (%s, %s)
|
||||
"""
|
||||
logger.log(f"{get_player_name(player_id)} joined {gamesystem_id}")
|
||||
cursor.execute(query, (player_id, gamesystem_id))
|
||||
connection.commit()
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
|
||||
# -----------------------------------------------------
|
||||
# Matches: Lesen
|
||||
# -----------------------------------------------------
|
||||
|
||||
def get_recent_matches_for_player(player_id):
|
||||
connection = sqlite3.connect(DB_PATH)
|
||||
connection.row_factory = sqlite3.Row
|
||||
cursor = connection.cursor()
|
||||
connection = db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
query = """
|
||||
SELECT
|
||||
|
|
@ -216,69 +255,62 @@ def get_recent_matches_for_player(player_id):
|
|||
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 = ?
|
||||
WHERE m.player1_id = %s OR m.player2_id = %s
|
||||
ORDER BY m.played_at DESC
|
||||
LIMIT 10
|
||||
"""
|
||||
|
||||
cursor.execute(query, (player_id, player_id))
|
||||
rows = cursor.fetchall()
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
result = []
|
||||
for row in rows:
|
||||
result.append(dict(row))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
return rows
|
||||
|
||||
|
||||
def add_new_match(system_name, player1_id, player2_id, score_p1, score_p2):
|
||||
connection = sqlite3.connect(DB_PATH)
|
||||
connection = db_connection()
|
||||
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,))
|
||||
cursor.execute("SELECT id FROM gamesystems WHERE name = %s", (system_name,))
|
||||
sys_row = cursor.fetchone()
|
||||
|
||||
# Sicherheitscheck (sollte eigentlich nie passieren)
|
||||
if not sys_row:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
return None
|
||||
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 (?, ?, ?, ?, ?)
|
||||
VALUES (%s, %s, %s, %s, %s)
|
||||
"""
|
||||
|
||||
cursor.execute(query, (sys_id, player1_id, player2_id, score_p1, score_p2))
|
||||
new_match_id = cursor.lastrowid
|
||||
|
||||
logger.log(f"{get_player_name(player1_id)}:({score_p1}) posted Match. System: {system_name}, {get_player_name(player2_id)}:({score_p2})")
|
||||
logger.log(
|
||||
f"{get_player_name(player1_id)}:({score_p1}) posted Match. "
|
||||
f"System: {system_name}, {get_player_name(player2_id)}:({score_p2})"
|
||||
)
|
||||
|
||||
connection.commit()
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
return new_match_id
|
||||
|
||||
|
||||
|
||||
def save_calculated_match(calc_results: dict):
|
||||
connection = sqlite3.connect(DB_PATH)
|
||||
connection = db_connection()
|
||||
cursor = connection.cursor()
|
||||
|
||||
try:
|
||||
match_id = calc_results["match_id"]
|
||||
winner_id = calc_results["winner_id"]
|
||||
looser_id = calc_results["looser_id"]
|
||||
match_id = calc_results["match_id"]
|
||||
winner_id = calc_results["winner_id"]
|
||||
looser_id = calc_results["looser_id"]
|
||||
|
||||
# Match aus DB lesen
|
||||
cursor.execute(
|
||||
"SELECT player1_id, player2_id, gamesystem_id FROM matches WHERE id = ?",
|
||||
"SELECT player1_id, player2_id, gamesystem_id FROM matches WHERE id = %s",
|
||||
(match_id,)
|
||||
)
|
||||
row = cursor.fetchone()
|
||||
|
|
@ -287,21 +319,19 @@ def save_calculated_match(calc_results: dict):
|
|||
|
||||
player1_id, player2_id, gamesystem_id = row
|
||||
|
||||
# Daten der Spieler aus calc_results holen (Key = player_id als int/str)
|
||||
p1 = calc_results[player1_id]
|
||||
p2 = calc_results[player2_id]
|
||||
|
||||
# 1. Match-Tabelle updaten
|
||||
match_query = """
|
||||
UPDATE matches
|
||||
SET
|
||||
player1_base_change = ?, player1_khorne = ?, player1_slaanesh = ?,
|
||||
player1_tzeentch = ?, player1_mmr_change = ?,
|
||||
player2_base_change = ?, player2_khorne = ?, player2_slaanesh = ?,
|
||||
player2_tzeentch = ?, player2_mmr_change = ?,
|
||||
rust_factor = ?, elo_factor = ?, point_inflation = ?,
|
||||
player1_base_change = %s, player1_khorne = %s, player1_slaanesh = %s,
|
||||
player1_tzeentch = %s, player1_mmr_change = %s,
|
||||
player2_base_change = %s, player2_khorne = %s, player2_slaanesh = %s,
|
||||
player2_tzeentch = %s, player2_mmr_change = %s,
|
||||
rust_factor = %s, elo_factor = %s, point_inflation = %s,
|
||||
match_is_counted = 1
|
||||
WHERE id = ?
|
||||
WHERE id = %s
|
||||
"""
|
||||
cursor.execute(match_query, (
|
||||
p1["base"], p1["khorne"], p1["slaanesh"], p1["tzeentch"], p1["total"],
|
||||
|
|
@ -310,36 +340,29 @@ def save_calculated_match(calc_results: dict):
|
|||
match_id
|
||||
))
|
||||
|
||||
# 2. Scores holen
|
||||
cursor.execute(
|
||||
"SELECT score_player1, score_player2 FROM matches WHERE id = ?",
|
||||
"SELECT score_player1, score_player2 FROM matches WHERE id = %s",
|
||||
(match_id,)
|
||||
)
|
||||
score_row = cursor.fetchone()
|
||||
score_p1, score_p2 = score_row if score_row else (0, 0)
|
||||
|
||||
# 3. Statistik-Query
|
||||
stat_query = """
|
||||
UPDATE player_game_statistic
|
||||
SET
|
||||
mmr = mmr + ?,
|
||||
mmr = mmr + %s,
|
||||
games_in_system = games_in_system + 1,
|
||||
points = points + ?,
|
||||
avv_points = (points + ?) / (games_in_system + 1),
|
||||
points = points + %s,
|
||||
avv_points = (points + %s) / (games_in_system + 1),
|
||||
last_played = CURRENT_TIMESTAMP
|
||||
WHERE player_id = ? AND gamesystem_id = ?
|
||||
WHERE player_id = %s AND gamesystem_id = %s
|
||||
"""
|
||||
|
||||
# 4. Statistik Spieler 1
|
||||
cursor.execute(stat_query, (
|
||||
p1["total"], score_p1, score_p1,
|
||||
player1_id, gamesystem_id
|
||||
p1["total"], score_p1, score_p1, player1_id, gamesystem_id
|
||||
))
|
||||
|
||||
# 5. Statistik Spieler 2
|
||||
cursor.execute(stat_query, (
|
||||
p2["total"], score_p2, score_p2,
|
||||
player2_id, gamesystem_id
|
||||
p2["total"], score_p2, score_p2, player2_id, gamesystem_id
|
||||
))
|
||||
|
||||
connection.commit()
|
||||
|
|
@ -352,76 +375,63 @@ def save_calculated_match(calc_results: dict):
|
|||
return False
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# -----------------------------------------------------
|
||||
# Get Data Funktionen
|
||||
# -----------------------------------------------------
|
||||
# -----------------------------------------------------
|
||||
|
||||
def get_gamesystem_data(system_id):
|
||||
connection = sqlite3.connect(DB_PATH)
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("SELECT * FROM gamesystems WHERE id = ?", (system_id,))
|
||||
connection = db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
cursor.execute("SELECT * FROM gamesystems WHERE id = %s", (system_id,))
|
||||
row = cursor.fetchone()
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
return dict(zip([col[0] for col in cursor.description], row)) if row else None
|
||||
return row
|
||||
|
||||
|
||||
def get_gamesystems_data():
|
||||
connection = sqlite3.connect(DB_PATH)
|
||||
|
||||
connection.row_factory = sqlite3.Row
|
||||
cursor = connection.cursor()
|
||||
connection = db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
cursor.execute("SELECT * FROM gamesystems")
|
||||
rows = cursor.fetchall()
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
# SQLite-Rows in normale Python-Dictionaries um
|
||||
result = []
|
||||
for row in rows:
|
||||
result.append(dict(row))
|
||||
|
||||
return result
|
||||
|
||||
return rows
|
||||
|
||||
|
||||
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()
|
||||
connection = db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
# 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
|
||||
WHERE sys.name = %s AND stat.games_in_system > 0
|
||||
ORDER BY stat.mmr DESC
|
||||
"""
|
||||
|
||||
cursor.execute(query, (system_name,))
|
||||
rows = cursor.fetchall()
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
result = []
|
||||
for row in rows:
|
||||
result.append(dict(row))
|
||||
|
||||
return result
|
||||
return rows
|
||||
|
||||
|
||||
def get_match_by_id(match_id: int) -> dict | None:
|
||||
"""Gibt alle Match-Daten inkl. Gamesystem-Name als Dict zurück."""
|
||||
connection = sqlite3.connect(DB_PATH)
|
||||
cursor = connection.cursor()
|
||||
connection = db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
try:
|
||||
cursor.execute("""
|
||||
|
|
@ -436,99 +446,86 @@ def get_match_by_id(match_id: int) -> dict | None:
|
|||
m.played_at
|
||||
FROM matches m
|
||||
JOIN gamesystems g ON m.gamesystem_id = g.id
|
||||
WHERE m.id = ?
|
||||
WHERE m.id = %s
|
||||
""", (match_id,))
|
||||
|
||||
row = cursor.fetchone()
|
||||
if not row:
|
||||
return None
|
||||
|
||||
columns = [desc[0] for desc in cursor.description]
|
||||
return dict(zip(columns, row))
|
||||
return cursor.fetchone()
|
||||
|
||||
except Exception as e:
|
||||
logger.log(f"Fehler beim Laden des Matches: {e}")
|
||||
return None
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
|
||||
def get_player_name(player_id):
|
||||
"""Gibt den Namen eines Spielers im Format 'Anzeigename (Discordname)' zurück."""
|
||||
|
||||
# Sicherheits-Check: Falls aus Versehen gar keine ID übergeben wird
|
||||
if player_id is None:
|
||||
return "Unbekannter Spieler"
|
||||
|
||||
connection = sqlite3.connect(DB_PATH)
|
||||
connection = db_connection()
|
||||
cursor = connection.cursor()
|
||||
|
||||
cursor.execute("SELECT display_name, discord_name FROM players WHERE id = ?", (player_id,))
|
||||
cursor.execute("SELECT display_name, discord_name FROM players WHERE id = %s", (player_id,))
|
||||
row = cursor.fetchone()
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
# Wenn die Datenbank den Spieler gefunden hat:
|
||||
if row:
|
||||
display_name = row[0]
|
||||
discord_name = row[1]
|
||||
return f"{display_name} ({discord_name})"
|
||||
else:
|
||||
return "Gelöschter Spieler"
|
||||
return f"{row[0]} ({row[1]})"
|
||||
return "Gelöschter Spieler"
|
||||
|
||||
|
||||
def get_system_name(sys_id):
|
||||
if sys_id is None:
|
||||
return "Unbekanntes System"
|
||||
connection = sqlite3.connect(DB_PATH)
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("SELECT name FROM gamesystems WHERE id = ?", (sys_id,))
|
||||
|
||||
connection = db_connection()
|
||||
cursor = connection.cursor()
|
||||
|
||||
cursor.execute("SELECT name FROM gamesystems WHERE id = %s", (sys_id,))
|
||||
row = cursor.fetchone()
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
if row:
|
||||
return row[0]
|
||||
else:
|
||||
return "Gelöschtes System"
|
||||
|
||||
|
||||
return row[0] if row else "Gelöschtes System"
|
||||
|
||||
|
||||
def get_days_since_last_system_game(player_id, gamesystem_id):
|
||||
"""Gibt zurück, wie viele Tage das letzte Spiel in einem bestimmten System her ist."""
|
||||
connection = sqlite3.connect(DB_PATH)
|
||||
connection = db_connection()
|
||||
cursor = connection.cursor()
|
||||
|
||||
# Julianday() berechnet in SQLite die Differenz in Tagen zwischen zwei Daten
|
||||
# MariaDB: DATEDIFF statt julianday()
|
||||
query = """
|
||||
SELECT CAST(julianday('now') - julianday(last_played) AS INTEGER)
|
||||
SELECT DATEDIFF(NOW(), last_played)
|
||||
FROM player_game_statistic
|
||||
WHERE player_id = ? AND gamesystem_id = ?
|
||||
WHERE player_id = %s AND gamesystem_id = %s
|
||||
"""
|
||||
cursor.execute(query, (player_id, gamesystem_id))
|
||||
result = cursor.fetchone()
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
# Wenn er gefunden wurde und ein Datum hat, gib die Tage zurück. Sonst (Erstes Spiel) 0.
|
||||
if result and result[0] is not None:
|
||||
return result[0]
|
||||
return int(result[0])
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
|
||||
# -----------------------------------------------------
|
||||
# Matches Bestätigen, Löschen, Berechnen, ...
|
||||
# -----------------------------------------------------
|
||||
# -----------------------------------------------------
|
||||
|
||||
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()
|
||||
connection = db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
# 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,
|
||||
|
|
@ -536,58 +533,58 @@ def get_unconfirmed_matches(player_id):
|
|||
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
|
||||
WHERE m.player2_id = %s 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]
|
||||
cursor.close()
|
||||
connection.close()
|
||||
return rows
|
||||
|
||||
|
||||
def delete_match(match_id, player_id):
|
||||
"""Löscht ein Match anhand seiner ID komplett aus der Datenbank."""
|
||||
connection = sqlite3.connect(DB_PATH)
|
||||
connection = db_connection()
|
||||
cursor = connection.cursor()
|
||||
|
||||
# DELETE FROM löscht die gesamte Zeile, bei der die ID übereinstimmt.
|
||||
cursor.execute("DELETE FROM matches WHERE id = ?", (match_id,))
|
||||
cursor.execute("DELETE FROM matches WHERE id = %s", (match_id,))
|
||||
logger.log(f"Match ID{match_id} deleted from user {get_player_name(player_id)}(ID:{player_id})")
|
||||
connection.commit()
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
|
||||
def confirm_match(match_id):
|
||||
connection = sqlite3.connect(DB_PATH)
|
||||
connection = db_connection()
|
||||
cursor = connection.cursor()
|
||||
|
||||
# Ändert nur die Spalte player2_check auf 1 (True)
|
||||
cursor.execute("UPDATE matches SET player2_check = 1 WHERE id = ?", (match_id,))
|
||||
cursor.execute("UPDATE matches SET player2_check = 1 WHERE id = %s", (match_id,))
|
||||
logger.log(f"Match mit ID{match_id} von Player2 bestätigt.")
|
||||
connection.commit()
|
||||
|
||||
cursor.close()
|
||||
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)
|
||||
connection = db_connection()
|
||||
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,))
|
||||
cursor.execute("UPDATE matches SET match_is_counted = 1 WHERE id = %s", (match_id,))
|
||||
connection.commit()
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
|
||||
def get_submitted_matches(player_id):
|
||||
"""Holt alle offenen Matches, die der Spieler selbst eingetragen hat, aber vom Gegner noch nicht bestätigt wurden."""
|
||||
connection = sqlite3.connect(DB_PATH)
|
||||
connection.row_factory = sqlite3.Row
|
||||
cursor = connection.cursor()
|
||||
"""Holt alle offenen Matches, die der Spieler selbst eingetragen hat."""
|
||||
connection = db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
# ACHTUNG: Wir joinen hier p2 (player2), weil wir den Namen des Gegners anzeigen wollen!
|
||||
query = """
|
||||
SELECT m.id AS match_id, m.score_player1, m.score_player2, m.played_at,
|
||||
sys.name AS system_name,
|
||||
|
|
@ -595,21 +592,20 @@ def get_submitted_matches(player_id):
|
|||
FROM matches m
|
||||
JOIN gamesystems sys ON m.gamesystem_id = sys.id
|
||||
JOIN players p2 ON m.player2_id = p2.id
|
||||
WHERE m.player1_id = ? AND m.player2_check = 0
|
||||
WHERE m.player1_id = %s AND m.player2_check = 0
|
||||
"""
|
||||
|
||||
cursor.execute(query, (player_id,))
|
||||
rows = cursor.fetchall()
|
||||
connection.close()
|
||||
|
||||
return [dict(row) for row in rows]
|
||||
cursor.close()
|
||||
connection.close()
|
||||
return rows
|
||||
|
||||
|
||||
def get_match_history_log(player_id):
|
||||
"""Holt ALLE Matches eines Spielers inklusive der MMR-Änderungen und Faktoren für das Log."""
|
||||
connection = sqlite3.connect(DB_PATH)
|
||||
connection.row_factory = sqlite3.Row
|
||||
cursor = connection.cursor()
|
||||
"""Holt ALLE Matches eines Spielers inkl. MMR-Änderungen und Faktoren."""
|
||||
connection = db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
query = """
|
||||
SELECT m.played_at, sys.name AS gamesystem_name,
|
||||
|
|
@ -625,54 +621,12 @@ def get_match_history_log(player_id):
|
|||
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 = ?
|
||||
WHERE m.player1_id = %s OR m.player2_id = %s
|
||||
ORDER BY m.played_at DESC
|
||||
"""
|
||||
cursor.execute(query, (player_id, player_id))
|
||||
rows = cursor.fetchall()
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# -----------------------------------------------------
|
||||
# Testing and Prototyping
|
||||
# -----------------------------------------------------
|
||||
def create_random_dummy_match(player_id):
|
||||
connection = sqlite3.connect(DB_PATH)
|
||||
cursor = connection.cursor()
|
||||
|
||||
# 1. Die ID des Dummys suchen (Wir suchen einfach nach dem Namen 'Dummy')
|
||||
cursor.execute("SELECT id FROM players WHERE display_name LIKE '%Dummy%' OR discord_name LIKE '%Dummy%' LIMIT 1")
|
||||
dummy_row = cursor.fetchone()
|
||||
|
||||
if not dummy_row:
|
||||
connection.close()
|
||||
print("Fehler: Kein Dummy in der Datenbank gefunden!")
|
||||
return False
|
||||
dummy_id = dummy_row[0]
|
||||
# 2. Zufällige Punkte generieren (z.B. zwischen 0 und 100)
|
||||
score_p1 = random.randint(0, 100)
|
||||
score_p2 = random.randint(0, 100)
|
||||
# 3. Das Match hart in System 1 (gamesystem_id = 1) eintragen
|
||||
query = """
|
||||
INSERT INTO matches (gamesystem_id, player2_id, player1_id, score_player1, score_player2, player2_check)
|
||||
VALUES (?, ?, ?, ?, ?, 0)
|
||||
"""
|
||||
cursor.execute(query, (1, player_id, dummy_id, score_p1, score_p2))
|
||||
|
||||
connection.commit()
|
||||
connection.close()
|
||||
|
||||
p1_name = get_player_name(player_id)
|
||||
dummy_name = get_player_name(dummy_id)
|
||||
sys_name = get_system_name(1)
|
||||
|
||||
logger.log(f"Zufallsspiel generiert. [{sys_name}]: {p1_name} ({score_p1}) vs. {dummy_name} ({score_p2})")
|
||||
|
||||
return True
|
||||
|
||||
return rows
|
||||
|
|
|
|||
|
|
@ -1,27 +1,23 @@
|
|||
import sqlite3
|
||||
|
||||
import mariadb
|
||||
import os
|
||||
from data import setup_database, data_api
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from data.setup_database import DB_PATH
|
||||
load_dotenv()
|
||||
|
||||
def db_connection():
|
||||
try:
|
||||
connection = mariadb.connect(
|
||||
host=os.getenv("DB_HOST", "localhost"),
|
||||
port=int(os.getenv("DB_PORT", 3306)),
|
||||
user=os.getenv("DB_USER"),
|
||||
password=os.getenv("DB_PASSWORD"),
|
||||
database=os.getenv("DB_NAME")
|
||||
)
|
||||
return connection
|
||||
except mariadb.Error as e:
|
||||
raise RuntimeError(f"Fehler beim Verbinden mit DB: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
setup_database.init_db()
|
||||
|
||||
def check_db():
|
||||
# Prüfen, ob die Datei existiert
|
||||
db_file = DB_PATH
|
||||
|
||||
if not os.path.exists(db_file):
|
||||
print(f"WARNUNG: '{db_file}' nicht gefunden!")
|
||||
print("Starte Datenbank-Einrichtung...")
|
||||
|
||||
# 1. Erstellt die leere Datei und alle Tabellen-Strukturen
|
||||
setup_database.init_db()
|
||||
|
||||
print("Datenbank aufgebaut!")
|
||||
else:
|
||||
print(f"OK: Datenbank '{db_file}' gefunden. Lade System...")
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,222 +0,0 @@
|
|||
|
||||
import sqlite3
|
||||
import os
|
||||
import json
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "league_database.db")
|
||||
|
||||
|
||||
dummy_is_in = True
|
||||
|
||||
def init_db():
|
||||
|
||||
connection = sqlite3.connect(DB_PATH)
|
||||
cursor = connection.cursor()
|
||||
|
||||
# Fremdschlüssel (Foreign Keys) in SQLite aktivieren
|
||||
cursor.execute('PRAGMA foreign_keys = ON;')
|
||||
|
||||
# 1. Tabelle: players (Stammdaten)
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS players (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
discord_id TEXT UNIQUE,
|
||||
discord_name TEXT NOT NULL,
|
||||
discord_avatar_url TEXT,
|
||||
display_name TEXT
|
||||
)
|
||||
''')
|
||||
|
||||
# 2. Tabelle: gamesystems (Globale Spielsysteme)
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS gamesystems (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
picture TEXT,
|
||||
description TEXT,
|
||||
active_players INTEGER DEFAULT 0,
|
||||
min_score INTEGER DEFAULT 0,
|
||||
max_score INTEGER DEFAULT 100
|
||||
)
|
||||
''')
|
||||
|
||||
# 3. Tabelle: player_game_statistic (Welcher Spieler hat in welchem System welche Stats?)
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS player_game_statistic (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
player_id INTEGER,
|
||||
gamesystem_id INTEGER,
|
||||
mmr INTEGER DEFAULT 1000,
|
||||
games_in_system INTEGER DEFAULT 0,
|
||||
points INTEGER DEFAULT 0,
|
||||
avv_points INTEGER DEFAULT 0,
|
||||
last_played TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
win_rate REAL DEFAULT 0,
|
||||
win_streak INTEGER DEFAULT 0,
|
||||
trend INTEGER DEFAULT 0,
|
||||
tyrann_id INTEGER,
|
||||
pushover_id INTEGER,
|
||||
nemesis_id INTEGER,
|
||||
FOREIGN KEY (player_id) REFERENCES players (id),
|
||||
FOREIGN KEY (tyrann_id) REFERENCES players (id),
|
||||
FOREIGN KEY (pushover_id) REFERENCES players (id),
|
||||
FOREIGN KEY (nemesis_id) REFERENCES players (id),
|
||||
FOREIGN KEY (gamesystem_id) REFERENCES gamesystems (id)
|
||||
)
|
||||
''')
|
||||
|
||||
# 4. Tabelle: matches (Wer hat wann gegen wen in welchem System gespielt?)
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS matches (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
gamesystem_id INTEGER,
|
||||
player1_id INTEGER,
|
||||
score_player1 INTEGER,
|
||||
player2_id INTEGER,
|
||||
score_player2 INTEGER,
|
||||
played_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
player1_base_change INTEGER,
|
||||
player1_khorne INTEGER,
|
||||
player1_slaanesh INTEGER,
|
||||
player1_tzeentch INTEGER,
|
||||
player1_mmr_change INTEGER,
|
||||
|
||||
player2_base_change INTEGER,
|
||||
player2_khorne INTEGER,
|
||||
player2_slaanesh INTEGER,
|
||||
player2_tzeentch INTEGER,
|
||||
player2_mmr_change INTEGER,
|
||||
|
||||
rust_factor REAL,
|
||||
elo_factor REAL,
|
||||
point_inflation REAL,
|
||||
|
||||
player2_check INTEGER DEFAULT 0,
|
||||
match_is_counted INTEGER DEFAULT 0,
|
||||
FOREIGN KEY (gamesystem_id) REFERENCES gamesystems (id),
|
||||
FOREIGN KEY (player1_id) REFERENCES players (id),
|
||||
FOREIGN KEY (player2_id) REFERENCES players (id)
|
||||
)
|
||||
''')
|
||||
|
||||
# 5. Tabelle: achievements (Der globale Katalog aller möglichen Erfolge - Stammdaten)
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS achievements (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
icon TEXT
|
||||
)
|
||||
''')
|
||||
|
||||
# 6. Tabelle: player_achievements. Wer hat welchen Erfolg wann bekommen
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS player_achievements (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
player_id INTEGER,
|
||||
achievement_id INTEGER,
|
||||
earned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
earned_in INTEGER,
|
||||
FOREIGN KEY (earned_in) REFERENCES gamesystems (id),
|
||||
FOREIGN KEY (player_id) REFERENCES players (id),
|
||||
FOREIGN KEY (achievement_id) REFERENCES achievements (id)
|
||||
)
|
||||
''')
|
||||
|
||||
connection.commit()
|
||||
connection.close()
|
||||
print("Datenbank Struktur aufgebaut")
|
||||
|
||||
#nächster Schritt: Standard Spielsysteme eintragen
|
||||
seed_gamesystems()
|
||||
|
||||
|
||||
# --------------------
|
||||
# --- Start Setup der DB Daten. Damit sie nicht GANZ leer sind.
|
||||
# --------------------
|
||||
def seed_gamesystems():
|
||||
connection = sqlite3.connect(DB_PATH)
|
||||
cursor = connection.cursor()
|
||||
systems = [
|
||||
("Warhammer 40k", "40k_logo.png", "Die Schlacht um die Galaxie in einer entfernten Zukunft.", 0, 100),
|
||||
("Warhammer Age of Sigmar", "aos_logo.png", "Der ewige Krieg um die Reiche der Sterblichen in einer epischen Fantasy-Welt.", 0, 50),
|
||||
("Spearhead", "aos_logo.png", "Schnelle und taktische Scharmützel für actiongeladene Kämpfe.", 0, 50)
|
||||
]
|
||||
|
||||
# executemany ist eine extrem schnelle SQL For-Schleife für Inserts
|
||||
cursor.executemany("INSERT INTO gamesystems (name, picture, description, min_score, max_score) VALUES (?, ?, ?, ?, ?)", systems)
|
||||
|
||||
connection.commit()
|
||||
connection.close()
|
||||
print("Spielsysteme angelegt!")
|
||||
|
||||
#Nächster Schritt: Standard Achievments eintragen.
|
||||
seed_achievements()
|
||||
|
||||
def seed_achievements():
|
||||
connection = sqlite3.connect(DB_PATH)
|
||||
cursor = connection.cursor()
|
||||
|
||||
# Unsere Start-Stückliste an Erfolgen (Name, Beschreibung, Icon)
|
||||
achievements = [
|
||||
("Kingslayer", "Hat den Spieler auf Platz 1 besiegt!", "👑"),
|
||||
("Unstoppable", "3 Spiele in Folge gewonnen.", "🔥"),
|
||||
("First Blood", "Das allererste Ligaspiel absolviert.", "🩸")
|
||||
]
|
||||
|
||||
cursor.executemany("INSERT INTO achievements (name, description, icon) VALUES (?, ?, ?)", achievements)
|
||||
|
||||
connection.commit()
|
||||
connection.close()
|
||||
print("Achievements angelegt.")
|
||||
|
||||
if dummy_is_in:
|
||||
seed_dummy_player()
|
||||
|
||||
|
||||
def seed_dummy_player():
|
||||
connection = sqlite3.connect(DB_PATH)
|
||||
cursor = connection.cursor()
|
||||
|
||||
# 1. Dummy-Spieler anlegen (falls noch nicht vorhanden)
|
||||
query_player = """
|
||||
INSERT OR IGNORE INTO players
|
||||
(discord_id, discord_name, display_name, discord_avatar_url)
|
||||
VALUES (?, ?, ?, ?)
|
||||
"""
|
||||
cursor.execute(query_player, ("dummy_001", "dummy_user", "Dummy Mc DummDumm", ""))
|
||||
|
||||
# 2. Wir holen uns die ID des Dummys (egal ob neu oder alt)
|
||||
cursor.execute("SELECT id FROM players WHERE discord_id = 'dummy_001'")
|
||||
dummy_row = cursor.fetchone()
|
||||
|
||||
if dummy_row:
|
||||
dummy_id = dummy_row[0]
|
||||
|
||||
# 3. Wir holen alle IDs der aktuellen Spielsysteme
|
||||
cursor.execute("SELECT id FROM gamesystems")
|
||||
systems = cursor.fetchall()
|
||||
|
||||
# 4. Wir gehen jedes System durch und prüfen, ob er schon drin ist
|
||||
for sys in systems:
|
||||
sys_id = sys[0]
|
||||
|
||||
cursor.execute("SELECT id FROM player_game_statistic WHERE player_id = ? AND gamesystem_id = ?", (dummy_id, sys_id))
|
||||
is_in_league = cursor.fetchone()
|
||||
|
||||
if not is_in_league:
|
||||
# Er ist noch nicht drin -> Eintragen! (MMR startet durch DEFAULT automatisch bei 1000)
|
||||
cursor.execute("""
|
||||
INSERT INTO player_game_statistic (player_id, gamesystem_id)
|
||||
VALUES (?, ?)
|
||||
""", (dummy_id, sys_id))
|
||||
|
||||
connection.commit()
|
||||
connection.close()
|
||||
print("Test-Gegner 'Dummy Mc DummDumm' ist bereit und in allen Ligen angemeldet!")
|
||||
|
||||
|
||||
|
|
@ -89,9 +89,16 @@ def setup_login_routes():
|
|||
app.storage.user['authenticated'] = True
|
||||
app.storage.user['discord_id'] = discord_id
|
||||
app.storage.user['discord_name'] = discord_name
|
||||
app.storage.user['db_id'] = player[0]
|
||||
app.storage.user['display_name'] = player[2]
|
||||
app.storage.user['discord_avatar_url'] = player[3]
|
||||
|
||||
# Kompatibel mit dict (MariaDB) UND tuple (SQLite)
|
||||
if isinstance(player, dict):
|
||||
app.storage.user['db_id'] = player['id'] # oder der echte Spaltenname
|
||||
app.storage.user['display_name'] = player['display_name']
|
||||
app.storage.user['discord_avatar_url'] = player['discord_avatar_url']
|
||||
else:
|
||||
app.storage.user['db_id'] = player[0]
|
||||
app.storage.user['display_name'] = player[2]
|
||||
app.storage.user['discord_avatar_url'] = player['discord_avatar_url']
|
||||
|
||||
ui.navigate.to('/')
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
],
|
||||
"mmr_info": [
|
||||
"**MMR Punkte** sind die Liga Punkte um die gespielt wird.",
|
||||
"Verliert man ein Spiel, verliert man Punkte. Und umgekehrt."
|
||||
"Auf der Hauptseite findest du eine detailierte Übersicht über deine letzen Spiele und die Punkte die du verloren oder gewonnen hast."
|
||||
],
|
||||
"match_form_info": [
|
||||
"Um ein Spiel einzutragen gibt einfach deine Punkte ein. Wähle deinen Gegner aus. Und gibt seine Punkte ein.",
|
||||
|
|
@ -25,5 +25,29 @@
|
|||
"Verlierer: (-l_base + l_khorne - slaanesh - tzeentch) * rust_factor)"
|
||||
],
|
||||
"tyrann_info": [],
|
||||
"prügelknabe_info": []
|
||||
"prügelknabe_info": [],
|
||||
"rang_info": [
|
||||
"Der Rang ergibt sich aus den MMR Punkten. Je mehr Punkte du hast, desto höher bist du wahrscheinlich in der Rangliste.",
|
||||
"MMR Punkte werden nach jedem Spiel errechnet."
|
||||
],
|
||||
"khorne_rule_info": [
|
||||
"Khorne mag es wenn Blut fließt!",
|
||||
"Deshalb schenkt er 8 Punkte den Spielern die oft Spielen. Ist dein letztes Spiel in dieser Liga noch keine 16 Tage her, kriegst du 8 Bonus Punkte. Egal ob du gewonnen oder verloren hast."
|
||||
],
|
||||
"tzeentch_rule_info": [
|
||||
"Der Gebieter der Intriegen mag es wenn geschmiedete Pläne aufgehen. Wenn du deinen Gegner mit einem großen Punkte abstand besiegst, gewährt er dir extra Puntke.",
|
||||
"Sieger Punkte - Verlierer Punkte / Maximal Punkte im Spielsystem. Ergibt 0-9 Punkte, je nach Abstand."
|
||||
],
|
||||
"slaanesh_rule_info": [
|
||||
"Diese Regel wird noch entworfen. Irgend eine Funktion mit dem ***Rivale*** und ***Prügelknabe***. Updates folgen!"
|
||||
],
|
||||
"rust_factor_info": [
|
||||
"Hat ein Spieler schon länger als 30 Tage in einem System nicht mehr gespielt, wird ein Multiplikator auf die gewonnenen oder verlorenen MMR Punkte angewandt.",
|
||||
"Bei 30 Tagen startet der Rost-Faktor bei 0,8 (80%) und erhöht sich bis 0,1 (10%) bei 90 Tagen.",
|
||||
"Der Rost Faktor soll verhindern dass Gelegenheitsspieler mit einem Spiel die ganze Rangliste umkrempeln. Oder als ***Schutz*** für Wiedereinsteiger."
|
||||
],
|
||||
"elo_factor_info": [
|
||||
"Der Elo Faktor errechnet sich aus den Sieger MMR Punkte und des Verlierer MMR Punkte. Für genauere Infos: Google=>'Schach Elo'",
|
||||
"im Prinzip gibt er an wie wahrscheinlich es war dass der Sieger gewonnen hat. Elo 0,5 entspricht 50%. Je höher der Elo Faktor desto **weniger** Base Punkte kriegt der Sieger. Oder verliert der Verlierer."
|
||||
]
|
||||
}
|
||||
|
|
@ -70,7 +70,10 @@ def setup_routes():
|
|||
ui.label(str(mmr)).classes('text-4xl font-bold text-accent')
|
||||
|
||||
with ui.card().classes("w-full items-center justify-center text-center"):
|
||||
ui.label("Rang: ").classes('text-2xl font-bold')
|
||||
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("w-full lg:col-span-2"):
|
||||
|
|
|
|||
BIN
gui/pictures/achievments/AchievementIcon_128_01.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_02.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_03.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_04.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_05.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_06.png
Normal file
|
After Width: | Height: | Size: 69 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_07.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_08.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_09.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_10.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_11.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_12.png
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_13.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_14.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_15.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_16.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_17.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_18.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_19.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_20.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_21.png
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_22.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_23.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_24.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_25.png
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_26.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_27.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_28.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_29.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_30.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_31.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_32.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_33.png
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_34.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_35.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_36.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_37.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_38.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_39.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_40.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_41.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_42.png
Normal file
|
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 |
BIN
gui/pictures/achievments/AchievementIcon_128_46.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_47.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_48.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_49.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_50.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_51.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_52.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_53.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_54.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_55.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_56.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_57.png
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_58.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_59.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_60.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_61.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_62.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_63.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_64.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_65.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_66.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_67.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_68.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_69.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_70.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_71.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_72.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_73.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_74.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
gui/pictures/achievments/AchievementIcon_128_75.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
2
main.py
|
|
@ -23,10 +23,8 @@ client_secret = os.getenv("DISCORD_CLIENT_SECRET")
|
|||
admin_discord_id = os.getenv("ADMIN")
|
||||
url = os.getenv("APP_URL")
|
||||
|
||||
database.check_db()
|
||||
logger.setup_log_db()
|
||||
|
||||
|
||||
# 3. Seitenrouten aufbauen
|
||||
main_gui.setup_routes(admin_discord_id)
|
||||
discord_login.setup_login_routes()
|
||||
|
|
|
|||
25
match_calculations/achievments/achievments.py
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
from data import data_api
|
||||
from wood import logger
|
||||
import sqlite3
|
||||
|
||||
|
||||
def check_player_achievments(player_id):
|
||||
# Schauen ob der Spieler sich nach dem Match (function wird aufgerufen NACHDEM ein Match berechnet wurde) sich ein Achievment verdient hat.
|
||||
game_stats = data_api.get_player_statistic(player_id, system_id)
|
||||
|
||||
# Checken ob die Gamestats da sind und geladen sind.
|
||||
if not game_stats:
|
||||
logger.log(f"no game_stats could be loaded for PlayerID {player_id}.")
|
||||
return
|
||||
else:
|
||||
if game_stats["mmr"] is None:
|
||||
logger.log(f"PlayerID {player_id} has no game statistics AFTER match got calculated.")
|
||||
return
|
||||
|
||||
played_games_achievment(player_id)
|
||||
|
||||
|
||||
|
||||
|
||||
def played_games_achievment(player_id):
|
||||
return
|
||||
|
|
@ -3,6 +3,7 @@ from match_calculations import calculation
|
|||
import json
|
||||
import os
|
||||
from wood import logger
|
||||
from match_calculations.achievments import achievments
|
||||
|
||||
|
||||
point_inflation = 1 # => entspricht %
|
||||
|
|
@ -96,6 +97,9 @@ def calculate_match (match_id):
|
|||
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)
|
||||
|
||||
|
||||
|
||||
def determine_draw_diff(sys_id):
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
nicegui
|
||||
requests
|
||||
python-dotenv
|
||||
85
setup_env.sh
|
|
@ -1,15 +1,88 @@
|
|||
#!/bin/bash
|
||||
|
||||
# 1. Erstellt die virtuelle Umgebung im Ordner ".venv" (falls sie nicht existiert)
|
||||
# Sicherstellen dass das Script abbricht wenn ein Fehler passiert
|
||||
# -e: Beendet das Script sofort wenn ein Befehl einen Fehlercode zurückgibt (Exit-Code != 0)
|
||||
set -e
|
||||
|
||||
echo "✅ Starte einrichtung der Liga Umgebung. Updates und System Abhängigkeiten werden installiert ..."
|
||||
|
||||
# Prüfen ob der Liga-Service auf dem System installiert ist
|
||||
# systemctl list-unit-files zeigt alle registrierten Service-Dateien
|
||||
SERVICE_EXISTS=false
|
||||
if systemctl list-unit-files --type service | grep -q "liga.service"; then
|
||||
# Service existiert → sicher anhalten bevor Updates kommen
|
||||
sudo systemctl stop liga
|
||||
SERVICE_EXISTS=true
|
||||
echo "✅ Liga Service wurde gestoppt."
|
||||
else
|
||||
# Service nicht installiert → kein Fehler, nur Info
|
||||
echo "ℹ️ Liga Service nicht gefunden. Wird übersprungen."
|
||||
fi
|
||||
|
||||
# 1. System updaten
|
||||
# apt update: Lädt die aktuellen Paketlisten vom Server herunter
|
||||
# apt upgrade -y: Installiert alle verfügbaren Updates, -y bestätigt automatisch
|
||||
sudo apt update && sudo apt upgrade -y
|
||||
|
||||
# 2. System-Abhängigkeiten installieren
|
||||
# -y: Automatisch mit "Ja" bestätigen
|
||||
sudo apt install -y \
|
||||
python3 \
|
||||
python3-venv \
|
||||
python3-full \
|
||||
libmariadb3 \
|
||||
libmariadb-dev
|
||||
|
||||
echo "✅ Updates installiert. Überprüfe Softwarestand von Git ..."
|
||||
|
||||
# Git Pull: Holt die neuesten Änderungen vom Remote-Repository
|
||||
# und merged sie in den lokalen Branch
|
||||
git pull
|
||||
|
||||
echo "✅ Softwarestand von Git aktuell. Richte Umgebung ein ..."
|
||||
|
||||
# 3. Virtuelle Umgebung erstellen (falls noch nicht vorhanden)
|
||||
# Wenn .venv schon existiert wird es einfach aktualisiert/überschrieben
|
||||
python3 -m venv .venv
|
||||
|
||||
# 2. Aktiviert die virtuelle Umgebung für dieses Skript
|
||||
# 4. Virtuelle Umgebung aktivieren
|
||||
# WICHTIG: Da wir in einem Bash-Script sind, gilt die Aktivierung nur für dieses Script
|
||||
source .venv/bin/activate
|
||||
|
||||
# 3. Aktualisiert das Installationsprogramm "pip" auf die neueste Version
|
||||
echo "✅ Umgebung eingerichtet! Installiere Pakete ..."
|
||||
|
||||
# 5. pip aktualisieren
|
||||
# pip ist der Paketmanager für Python – sollte aktuell sein um Kompatibilitätsprobleme zu vermeiden
|
||||
pip install --upgrade pip
|
||||
|
||||
# 4. Installiert alle Pakete aus deiner "Stückliste"
|
||||
pip install -r requirements.txt
|
||||
# 6. Python-Pakete installieren
|
||||
pip install \
|
||||
nicegui \
|
||||
requests \
|
||||
python-dotenv \
|
||||
mariadb
|
||||
|
||||
echo "✅ Pakete installiert. Starte Liga Service ..."
|
||||
|
||||
# Prüfen ob der Service vorher existierte und versuche ihn zu starten
|
||||
if [ "$SERVICE_EXISTS" = true ]; then
|
||||
# Service existiert bereits auf dem System → versuchen zu starten
|
||||
# systemctl start gibt Exit-Code 0 bei Erfolg, != 0 bei Fehler
|
||||
if sudo systemctl start liga; then
|
||||
echo "✅ Liga Service erfolgreich gestartet. Alles bereit!"
|
||||
else
|
||||
# Service konnte nicht gestartet werden – mögliche Ursachen:
|
||||
# - Konfigurationsfehler in der .service Datei
|
||||
# - Python-Fehler beim Start (Import-Error, Syntax-Error etc.)
|
||||
# - Port bereits belegt
|
||||
# - Fehlende Berechtigungen
|
||||
echo "❌ FEHLER: Liga Service konnte nicht gestartet werden!"
|
||||
echo " Prüfe mit: sudo systemctl status liga"
|
||||
echo " Prüfe mit: sudo journalctl -u liga --no-pager -n 50"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
# Service war vorher auch nicht installiert – Info an den User
|
||||
echo "ℹ️ Liga Service nicht installiert. Manuell starten oder Service erst einrichten."
|
||||
fi
|
||||
|
||||
echo "Umgebung wurde erfolgreich eingerichtet!"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import sqlite3
|
|||
import os
|
||||
import inspect
|
||||
|
||||
from data.setup_database import DB_PATH
|
||||
|
||||
DB_FILE = "wood/log.db"
|
||||
|
||||
|
|
|
|||