Compare commits
13 Commits
a27fa61904
...
ba7edbdc80
| Author | SHA1 | Date | |
|---|---|---|---|
| ba7edbdc80 | |||
|
|
2d3c17bcf2 | ||
|
|
931189baca | ||
| aaaf96caed | |||
| 1dcfb0694b | |||
| c5dcd56410 | |||
|
|
c91271dc48 | ||
|
|
80071e2b57 | ||
|
|
b10a1508f2 | ||
|
|
a715cd7bff | ||
|
|
c363e35e53 | ||
| 9b399af323 | |||
|
|
4dfdf2165c |
|
|
@ -1 +1 @@
|
||||||
{"authenticated":true,"discord_id":"277898241750859776","discord_name":"mrteels","db_id":2,"display_name":"Verzweifelter Grot","discord_avatar_url":"https://cdn.discordapp.com/avatars/277898241750859776/7c3446bb51fafd72b1b4c21124b4994f.png"}
|
{"authenticated":true,"discord_id":"277898241750859776","discord_name":"mrteels","db_id":2,"display_name":"Stolpernder Servitor","discord_avatar_url":"https://cdn.discordapp.com/avatars/277898241750859776/7c3446bb51fafd72b1b4c21124b4994f.png"}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"authenticated":true,"discord_id":"159672532159561728","discord_name":"buddyl33t","db_id":3,"display_name":"Marian","discord_avatar_url":"https://cdn.discordapp.com/avatars/159672532159561728/866d0dddade9d551f3c5025bb6467da0.png"}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"authenticated":true,"discord_id":"113708052485636100","discord_name":"staelwulf","db_id":4,"display_name":"Max","discord_avatar_url":"https://cdn.discordapp.com/avatars/113708052485636100/d53339dd6a6659231c5c16645ba258df.png"}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"authenticated":true,"discord_id":"277898241750859776","discord_name":"mrteels","db_id":2,"display_name":"Daniel N","discord_avatar_url":"https://cdn.discordapp.com/avatars/277898241750859776/7c3446bb51fafd72b1b4c21124b4994f.png"}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"authenticated":true,"discord_id":"277898241750859776","discord_name":"mrteels","db_id":2,"display_name":"Daniel","discord_avatar_url":"https://cdn.discordapp.com/avatars/277898241750859776/7c3446bb51fafd72b1b4c21124b4994f.png"}
|
||||||
416
data/data_api.py
416
data/data_api.py
|
|
@ -46,7 +46,7 @@ def get_or_create_player(discord_id, discord_name, avatar_url):
|
||||||
connection = sqlite3.connect(DB_PATH)
|
connection = sqlite3.connect(DB_PATH)
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
|
|
||||||
# REPARIERT: Wir fragen 4 Dinge ab (id, discord_name, display_name, discord_avatar_url)
|
# 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 = ?", (discord_id,))
|
||||||
player = cursor.fetchone()
|
player = cursor.fetchone()
|
||||||
|
|
||||||
|
|
@ -70,8 +70,6 @@ def get_or_create_player(discord_id, discord_name, avatar_url):
|
||||||
return player
|
return player
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_all_players():
|
def get_all_players():
|
||||||
connection = sqlite3.connect(DB_PATH)
|
connection = sqlite3.connect(DB_PATH)
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
|
|
@ -88,8 +86,6 @@ def get_all_players():
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_all_players_from_system(system_name):
|
def get_all_players_from_system(system_name):
|
||||||
connection = sqlite3.connect(DB_PATH)
|
connection = sqlite3.connect(DB_PATH)
|
||||||
connection.row_factory = sqlite3.Row
|
connection.row_factory = sqlite3.Row
|
||||||
|
|
@ -119,6 +115,7 @@ def get_all_players_from_system(system_name):
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def get_gamesystem_id_by_name(system_name):
|
def get_gamesystem_id_by_name(system_name):
|
||||||
"""Holt die interne ID eines Spielsystems anhand seines Namens (z.B. 'Warhammer 40k' -> 1)."""
|
"""Holt die interne ID eines Spielsystems anhand seines Namens (z.B. 'Warhammer 40k' -> 1)."""
|
||||||
connection = sqlite3.connect(DB_PATH)
|
connection = sqlite3.connect(DB_PATH)
|
||||||
|
|
@ -138,49 +135,6 @@ def get_gamesystem_id_by_name(system_name):
|
||||||
return None
|
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
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -224,7 +178,7 @@ def join_league(player_id, gamesystem_id):
|
||||||
INSERT INTO player_game_statistic (player_id, gamesystem_id)
|
INSERT INTO player_game_statistic (player_id, gamesystem_id)
|
||||||
VALUES (?, ?)
|
VALUES (?, ?)
|
||||||
"""
|
"""
|
||||||
logger.log("INTO", f"{get_player_name(player_id)} ist Liga {gamesystem_id} beigetreten", player_id)
|
logger.log("DataAPI", f"{get_player_name(player_id)} joined {gamesystem_id}", player_id)
|
||||||
cursor.execute(query, (player_id, gamesystem_id))
|
cursor.execute(query, (player_id, gamesystem_id))
|
||||||
connection.commit()
|
connection.commit()
|
||||||
|
|
||||||
|
|
@ -296,7 +250,7 @@ def add_new_match(system_name, player1_id, player2_id, score_p1, score_p2):
|
||||||
cursor.execute(query, (sys_id, player1_id, player2_id, score_p1, score_p2))
|
cursor.execute(query, (sys_id, player1_id, player2_id, score_p1, score_p2))
|
||||||
new_match_id = cursor.lastrowid
|
new_match_id = cursor.lastrowid
|
||||||
|
|
||||||
logger.log("MATCH", f"Match eingetragen. {get_system_name(sys_id)} - {get_player_name(player1_id)} ({score_p1}) vs. {get_player_name(player2_id)} ({score_p2})", player1_id)
|
logger.log("DataAPI", f"{get_player_name(player1_id)}:({score_p1}) posted Match. System: {gamesystem_id}, {get_player_name(player2_id)}:({score_p2})", player_id)
|
||||||
|
|
||||||
connection.commit()
|
connection.commit()
|
||||||
connection.close()
|
connection.close()
|
||||||
|
|
@ -305,167 +259,129 @@ def add_new_match(system_name, player1_id, player2_id, score_p1, score_p2):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_player_system_stats(player_id, system_name):
|
def save_calculated_match(calc_results: dict):
|
||||||
"""Holt die gespeicherten Statistiken eines Spielers für ein spezielles System direkt aus der Tabelle."""
|
|
||||||
connection = sqlite3.connect(DB_PATH)
|
connection = sqlite3.connect(DB_PATH)
|
||||||
connection.row_factory = sqlite3.Row
|
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
|
|
||||||
# stat.* holt einfach ALLE Spalten, die in der Tabelle player_game_statistic stehen
|
try:
|
||||||
query = """
|
match_id = calc_results["match_id"]
|
||||||
SELECT stat.*
|
winner_id = calc_results["winner_id"]
|
||||||
FROM player_game_statistic stat
|
looser_id = calc_results["looser_id"]
|
||||||
JOIN gamesystems sys ON stat.gamesystem_id = sys.id
|
|
||||||
WHERE stat.player_id = ? AND sys.name = ?
|
|
||||||
"""
|
|
||||||
|
|
||||||
cursor.execute(query, (player_id, system_name))
|
# Match aus DB lesen
|
||||||
|
cursor.execute(
|
||||||
|
"SELECT player1_id, player2_id, gamesystem_id FROM matches WHERE id = ?",
|
||||||
|
(match_id,)
|
||||||
|
)
|
||||||
|
row = cursor.fetchone()
|
||||||
|
if not row:
|
||||||
|
raise ValueError(f"Match ID {match_id} nicht in der Datenbank gefunden.")
|
||||||
|
|
||||||
|
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 = ?,
|
||||||
|
match_is_counted = 1
|
||||||
|
WHERE id = ?
|
||||||
|
"""
|
||||||
|
cursor.execute(match_query, (
|
||||||
|
p1["base"], p1["khorne"], p1["slaanesh"], p1["tzeentch"], p1["total"],
|
||||||
|
p2["base"], p2["khorne"], p2["slaanesh"], p2["tzeentch"], p2["total"],
|
||||||
|
calc_results["rust_factor"], calc_results["elo_factor"], calc_results["point_inflation"],
|
||||||
|
match_id
|
||||||
|
))
|
||||||
|
|
||||||
|
# 2. Scores holen
|
||||||
|
cursor.execute(
|
||||||
|
"SELECT score_player1, score_player2 FROM matches WHERE id = ?",
|
||||||
|
(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 + ?,
|
||||||
|
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 = ?
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 4. Statistik Spieler 1
|
||||||
|
cursor.execute(stat_query, (
|
||||||
|
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
|
||||||
|
))
|
||||||
|
|
||||||
|
connection.commit()
|
||||||
|
logger.log("MATCH_CALC", f"Match ID {match_id} wurde komplett berechnet und verbucht.")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
connection.rollback()
|
||||||
|
print(f"KRITISCHER FEHLER beim Speichern des Matches: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
finally:
|
||||||
|
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,))
|
||||||
row = cursor.fetchone()
|
row = cursor.fetchone()
|
||||||
connection.close()
|
connection.close()
|
||||||
|
return dict(zip([col[0] for col in cursor.description], row)) if row else None
|
||||||
# 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."""
|
|
||||||
|
|
||||||
|
def get_gamesystems_data():
|
||||||
connection = sqlite3.connect(DB_PATH)
|
connection = sqlite3.connect(DB_PATH)
|
||||||
connection.row_factory = sqlite3.Row
|
|
||||||
|
connection.row_factory = sqlite3.Row
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
|
|
||||||
# NEU: Wir haben 'm.played_at' im SELECT hinzugefügt!
|
cursor.execute("SELECT * FROM gamesystems")
|
||||||
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()
|
rows = cursor.fetchall()
|
||||||
connection.close()
|
connection.close()
|
||||||
|
|
||||||
# Wieder umdrehen für den zeitlichen Verlauf (links alt, rechts neu)
|
# SQLite-Rows in normale Python-Dictionaries um
|
||||||
rows.reverse()
|
result = []
|
||||||
|
|
||||||
points_list = []
|
|
||||||
labels_list = []
|
|
||||||
|
|
||||||
for row in rows:
|
for row in rows:
|
||||||
if row['player1_id'] == player_id:
|
result.append(dict(row))
|
||||||
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!
|
return result
|
||||||
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))
|
|
||||||
logger.log("MATCH CALC", f"Bei {get_player_name(player_id)} haben sich die MMR Punkte um {mmr_change} geändert.")
|
|
||||||
connection.commit()
|
|
||||||
connection.close()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -497,20 +413,40 @@ def get_leaderboard(system_name):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def update_match_mmr_change(match_id, p1_change, p2_change):
|
def get_match_by_id(match_id: int) -> dict | None:
|
||||||
"""Speichert die tatsächliche MMR-Änderung für das Log fest im Match ab."""
|
"""Gibt alle Match-Daten inkl. Gamesystem-Name als Dict zurück."""
|
||||||
connection = sqlite3.connect(DB_PATH)
|
connection = sqlite3.connect(DB_PATH)
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
|
|
||||||
cursor.execute("""
|
try:
|
||||||
UPDATE matches
|
cursor.execute("""
|
||||||
SET player1_mmr_change = ?, player2_mmr_change = ?
|
SELECT
|
||||||
WHERE id = ?
|
m.id,
|
||||||
""", (p1_change, p2_change, match_id))
|
m.gamesystem_id,
|
||||||
|
g.name AS gamesystem_name,
|
||||||
|
m.player1_id,
|
||||||
|
m.score_player1,
|
||||||
|
m.player2_id,
|
||||||
|
m.score_player2,
|
||||||
|
m.played_at
|
||||||
|
FROM matches m
|
||||||
|
JOIN gamesystems g ON m.gamesystem_id = g.id
|
||||||
|
WHERE m.id = ?
|
||||||
|
""", (match_id,))
|
||||||
|
|
||||||
connection.commit()
|
row = cursor.fetchone()
|
||||||
connection.close()
|
if not row:
|
||||||
|
return None
|
||||||
|
|
||||||
|
columns = [desc[0] for desc in cursor.description]
|
||||||
|
return dict(zip(columns, row))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Fehler beim Laden des Matches: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
finally:
|
||||||
|
connection.close()
|
||||||
|
|
||||||
|
|
||||||
def get_player_name(player_id):
|
def get_player_name(player_id):
|
||||||
|
|
@ -535,6 +471,7 @@ def get_player_name(player_id):
|
||||||
else:
|
else:
|
||||||
return "Gelöschter Spieler"
|
return "Gelöschter Spieler"
|
||||||
|
|
||||||
|
|
||||||
def get_system_name(sys_id):
|
def get_system_name(sys_id):
|
||||||
if sys_id is None:
|
if sys_id is None:
|
||||||
return "Unbekanntes System"
|
return "Unbekanntes System"
|
||||||
|
|
@ -553,6 +490,27 @@ def get_system_name(sys_id):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
cursor = connection.cursor()
|
||||||
|
|
||||||
|
# Julianday() berechnet in SQLite die Differenz in Tagen zwischen zwei Daten
|
||||||
|
query = """
|
||||||
|
SELECT CAST(julianday('now') - julianday(last_played) AS INTEGER)
|
||||||
|
FROM player_game_statistic
|
||||||
|
WHERE player_id = ? AND gamesystem_id = ?
|
||||||
|
"""
|
||||||
|
cursor.execute(query, (player_id, gamesystem_id))
|
||||||
|
result = cursor.fetchone()
|
||||||
|
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 0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------
|
# -----------------------------------------------------
|
||||||
|
|
@ -591,7 +549,7 @@ def delete_match(match_id, player_id):
|
||||||
|
|
||||||
# DELETE FROM löscht die gesamte Zeile, bei der die ID übereinstimmt.
|
# 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 = ?", (match_id,))
|
||||||
logger.log ("MATCH", f"Match mit ID:{match_id} wurde gelöscht von {get_player_name(player_id)}")
|
logger.log("data_api.delete_match", f"Match mit ID{match_id} von User gelöscht.", f"{player_id}")
|
||||||
connection.commit()
|
connection.commit()
|
||||||
connection.close()
|
connection.close()
|
||||||
|
|
||||||
|
|
@ -602,7 +560,7 @@ def confirm_match(match_id):
|
||||||
|
|
||||||
# Ändert nur die Spalte player2_check auf 1 (True)
|
# Ä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 = ?", (match_id,))
|
||||||
logger.log ("MATCH CALC", f"Match mit ID{match_id} wurde als 'Confirmed' gekennzeichnet")
|
logger.log("data_api.confirm_match", f"Match mit ID{match_id} von Player2 bestätigt.")
|
||||||
connection.commit()
|
connection.commit()
|
||||||
connection.close()
|
connection.close()
|
||||||
|
|
||||||
|
|
@ -614,7 +572,7 @@ def set_match_counted(match_id):
|
||||||
|
|
||||||
# Ändert nur die Spalte match_is_counted auf 1 (True)
|
# Ä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 = ?", (match_id,))
|
||||||
logger.log ("MATCH CALC", f"Match mit ID{match_id} wurde als 'Counted' gekennzeichnet")
|
logger.log("data_api.set_match_counted", f"Match mit ID: {match_id} berechnet.")
|
||||||
connection.commit()
|
connection.commit()
|
||||||
connection.close()
|
connection.close()
|
||||||
|
|
||||||
|
|
@ -643,8 +601,6 @@ def get_submitted_matches(player_id):
|
||||||
return [dict(row) for row in rows]
|
return [dict(row) for row in rows]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_match_history_log(player_id):
|
def get_match_history_log(player_id):
|
||||||
"""Holt ALLE Matches eines Spielers inklusive der MMR-Änderungen für das Log."""
|
"""Holt ALLE Matches eines Spielers inklusive der MMR-Änderungen für das Log."""
|
||||||
connection = sqlite3.connect(DB_PATH)
|
connection = sqlite3.connect(DB_PATH)
|
||||||
|
|
@ -668,3 +624,45 @@ def get_match_history_log(player_id):
|
||||||
connection.close()
|
connection.close()
|
||||||
|
|
||||||
return [dict(row) for row in rows]
|
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("TEST_MATCH", f"Zufallsspiel generiert. [{sys_name}]: {p1_name} ({score_p1}) vs. {dummy_name} ({score_p2})", player_id)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ if __name__ == "__main__":
|
||||||
setup_database.init_db()
|
setup_database.init_db()
|
||||||
|
|
||||||
def check_db():
|
def check_db():
|
||||||
# --- DATENBANK CHECK ---
|
|
||||||
# Prüfen, ob die Datei existiert
|
# Prüfen, ob die Datei existiert
|
||||||
db_file = DB_PATH
|
db_file = DB_PATH
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ load_dotenv()
|
||||||
|
|
||||||
DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "league_database.db")
|
DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "league_database.db")
|
||||||
|
|
||||||
dummy_is_in = True
|
|
||||||
|
|
||||||
|
dummy_is_in = True
|
||||||
|
|
||||||
def init_db():
|
def init_db():
|
||||||
|
|
||||||
|
|
@ -54,6 +54,7 @@ def init_db():
|
||||||
points INTEGER DEFAULT 0,
|
points INTEGER DEFAULT 0,
|
||||||
avv_points INTEGER DEFAULT 0,
|
avv_points INTEGER DEFAULT 0,
|
||||||
last_played TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
last_played TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
trend INTEGER DEFAULT 0,
|
||||||
FOREIGN KEY (player_id) REFERENCES players (id),
|
FOREIGN KEY (player_id) REFERENCES players (id),
|
||||||
FOREIGN KEY (gamesystem_id) REFERENCES gamesystems (id)
|
FOREIGN KEY (gamesystem_id) REFERENCES gamesystems (id)
|
||||||
)
|
)
|
||||||
|
|
@ -69,8 +70,23 @@ def init_db():
|
||||||
player2_id INTEGER,
|
player2_id INTEGER,
|
||||||
score_player2 INTEGER,
|
score_player2 INTEGER,
|
||||||
played_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
played_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
player1_base_change INTEGER,
|
||||||
|
player1_khorne INTEGER,
|
||||||
|
player1_slaanesh INTEGER,
|
||||||
|
player1_tzeentch INTEGER,
|
||||||
player1_mmr_change INTEGER,
|
player1_mmr_change INTEGER,
|
||||||
|
|
||||||
|
player2_base_change INTEGER,
|
||||||
|
player2_khorne INTEGER,
|
||||||
|
player2_slaanesh INTEGER,
|
||||||
|
player2_tzeentch INTEGER,
|
||||||
player2_mmr_change INTEGER,
|
player2_mmr_change INTEGER,
|
||||||
|
|
||||||
|
rust_factor REAL,
|
||||||
|
elo_factor REAL,
|
||||||
|
point_inflation REAL,
|
||||||
|
|
||||||
player2_check INTEGER DEFAULT 0,
|
player2_check INTEGER DEFAULT 0,
|
||||||
match_is_counted INTEGER DEFAULT 0,
|
match_is_counted INTEGER DEFAULT 0,
|
||||||
FOREIGN KEY (gamesystem_id) REFERENCES gamesystems (id),
|
FOREIGN KEY (gamesystem_id) REFERENCES gamesystems (id),
|
||||||
|
|
@ -89,7 +105,7 @@ def init_db():
|
||||||
)
|
)
|
||||||
''')
|
''')
|
||||||
|
|
||||||
# 6. Tabelle: player_achievements (Wer hat welchen Erfolg wann bekommen? - Bewegungsdaten)
|
# 6. Tabelle: player_achievements. Wer hat welchen Erfolg wann bekommen
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
CREATE TABLE IF NOT EXISTS player_achievements (
|
CREATE TABLE IF NOT EXISTS player_achievements (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
|
@ -131,7 +147,6 @@ def seed_gamesystems():
|
||||||
print("Spielsysteme angelegt!")
|
print("Spielsysteme angelegt!")
|
||||||
|
|
||||||
#Nächster Schritt: Standard Achievments eintragen.
|
#Nächster Schritt: Standard Achievments eintragen.
|
||||||
generate_default_mmr_rules()
|
|
||||||
seed_achievements()
|
seed_achievements()
|
||||||
|
|
||||||
def seed_achievements():
|
def seed_achievements():
|
||||||
|
|
@ -197,74 +212,3 @@ def seed_dummy_player():
|
||||||
print("Test-Gegner 'Dummy Mc DummDumm' ist bereit und in allen Ligen angemeldet!")
|
print("Test-Gegner 'Dummy Mc DummDumm' ist bereit und in allen Ligen angemeldet!")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def generate_default_mmr_rules():
|
|
||||||
"""Erstellt für jedes Spielsystem eine Standard-JSON-Datei, falls sie noch nicht existiert."""
|
|
||||||
connection = sqlite3.connect(DB_PATH)
|
|
||||||
cursor = connection.cursor()
|
|
||||||
|
|
||||||
cursor.execute("SELECT name FROM gamesystems")
|
|
||||||
systems = cursor.fetchall()
|
|
||||||
connection.close()
|
|
||||||
|
|
||||||
# 1. Sicherstellen, dass der Ordner existiert (Best Practice!)
|
|
||||||
folder_path = "mmr_calculations"
|
|
||||||
os.makedirs(folder_path, exist_ok=True)
|
|
||||||
|
|
||||||
# 2. Wir gehen jedes gefundene Spielsystem durch
|
|
||||||
for sys in systems:
|
|
||||||
sys_name = sys[0]
|
|
||||||
|
|
||||||
# Wir machen den Namen "dateisicher" (z.B. "Warhammer 40k" -> "warhammer_40k")
|
|
||||||
safe_name = sys_name.replace(" ", "_").lower()
|
|
||||||
file_path = os.path.join(folder_path, f"mmr_rules_{safe_name}.json")
|
|
||||||
|
|
||||||
# 3. SICHERHEITS-CHECK: Existiert die Datei schon?
|
|
||||||
# Wenn ja, ignorieren wir sie, damit wir dein händisches Balancing nicht überschreiben!
|
|
||||||
if not os.path.exists(file_path):
|
|
||||||
|
|
||||||
# Das ist unsere Standard-Vorlage (Faktor 10)
|
|
||||||
default_rules = {
|
|
||||||
"system_info": f"Balancing für {sys_name}",
|
|
||||||
"draw_point_difference": 3,
|
|
||||||
"rank_matrix": {
|
|
||||||
"10": {"win": 10, "draw": 30},
|
|
||||||
"9": {"win": 10, "draw": 30},
|
|
||||||
"8": {"win": 10, "draw": 20},
|
|
||||||
"7": {"win": 10, "draw": 20},
|
|
||||||
"6": {"win": 20, "draw": 10},
|
|
||||||
"5": {"win": 20, "draw": 10},
|
|
||||||
"4": {"win": 20, "draw": 0},
|
|
||||||
"3": {"win": 20, "draw": 0},
|
|
||||||
"2": {"win": 30, "draw": 0},
|
|
||||||
"1": {"win": 30, "draw": 0},
|
|
||||||
"0": {"win": 30, "draw": 0},
|
|
||||||
"-1": {"win": 30, "draw": 0},
|
|
||||||
"-2": {"win": 40, "draw": 0},
|
|
||||||
"-3": {"win": 40, "draw": -10},
|
|
||||||
"-4": {"win": 50, "draw": -20},
|
|
||||||
"-5": {"win": 60, "draw": -20},
|
|
||||||
"-6": {"win": 70, "draw": -20},
|
|
||||||
"-7": {"win": 80, "draw": -50},
|
|
||||||
"-8": {"win": 100, "draw": -50},
|
|
||||||
"-9": {"win": 120, "draw": -60},
|
|
||||||
"-10": {"win": 150, "draw": -60}
|
|
||||||
},
|
|
||||||
"score_bonus": [
|
|
||||||
{"min_diff": 95, "bonus": 40},
|
|
||||||
{"min_diff": 85, "bonus": 30},
|
|
||||||
{"min_diff": 65, "bonus": 20},
|
|
||||||
{"min_diff": 35, "bonus": 10},
|
|
||||||
{"min_diff": 0, "bonus": 0}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
# 4. JSON-Datei schreiben
|
|
||||||
# 'w' steht für write (schreiben). indent=4 macht es für Menschen schön lesbar formatiert.
|
|
||||||
with open(file_path, 'w', encoding='utf-8') as f:
|
|
||||||
json.dump(default_rules, f, indent=4, ensure_ascii=False)
|
|
||||||
|
|
||||||
print(f"Neu: Balancing-Datei für '{sys_name}' wurde erstellt -> {file_path}")
|
|
||||||
else:
|
|
||||||
print(f"OK: Balancing-Datei für '{sys_name}' existiert bereits.")
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ from nicegui import ui, app
|
||||||
from data import database, data_api
|
from data import database, data_api
|
||||||
from gui import gui_style
|
from gui import gui_style
|
||||||
from wood import logger
|
from wood import logger
|
||||||
|
from gui import main_gui
|
||||||
|
|
||||||
def setup_routes():
|
def setup_routes():
|
||||||
@ui.page('/admin', dark=True)
|
@ui.page('/admin', dark=True)
|
||||||
|
|
@ -14,7 +15,7 @@ def setup_routes():
|
||||||
if app.storage.user.get('authenticated', False):
|
if app.storage.user.get('authenticated', False):
|
||||||
with ui.card().classes("w-full"):
|
with ui.card().classes("w-full"):
|
||||||
with ui.row().classes("w-full"):
|
with ui.row().classes("w-full"):
|
||||||
ui.button("test")
|
ui.button(text= "test", on_click=lambda: data_api.create_random_dummy_match(2))
|
||||||
ui.button(icon="refresh", on_click=lambda: ui.navigate.reload)
|
ui.button(icon="refresh", on_click=lambda: ui.navigate.reload)
|
||||||
|
|
||||||
ui.label("System Audit Log").classes('text-2xl font-bold text-white mb-4')
|
ui.label("System Audit Log").classes('text-2xl font-bold text-white mb-4')
|
||||||
|
|
|
||||||
|
|
@ -4,22 +4,20 @@ from nicegui import ui
|
||||||
# muss man es nur hier ändern!
|
# muss man es nur hier ändern!
|
||||||
|
|
||||||
def apply_design():
|
def apply_design():
|
||||||
ui.add_css('body { background-color: #18181b; }')
|
ui.add_css('body { background-color: #171721; }')
|
||||||
|
|
||||||
# 1. Dark Mode aktivieren
|
# 1. Dark Mode aktivieren
|
||||||
ui.dark_mode(True)
|
ui.dark_mode(True)
|
||||||
|
|
||||||
# 2. Hier definierst du deine Farben fest im Code!
|
|
||||||
# Du kannst normale englische Farbnamen (wie 'red') oder HEX-Codes (wie '#ff0000') nutzen.
|
|
||||||
ui.colors(
|
ui.colors(
|
||||||
primary='#b91c1c', # Hauptfarbe (z.B. für Standard-Buttons) - Hier ein dunkles Rot
|
primary='#B80B0B', # Hauptfarbe (z.B. für Standard-Buttons) Wenn keine Farbe angegeben wird, ist es diese Farbe
|
||||||
secondary='#1f2937', # Zweitfarbe
|
secondary='#FF3333', # Zweitfarbe
|
||||||
accent='#3b82f6', # Akzentfarbe (z.B. Blau)
|
accent='#2078D4', # Akzentfarbe
|
||||||
positive='#22c55e', # Farbe für Erfolg (Grün)
|
positive='#188C42', # Farbe für Erfolg (Grün)
|
||||||
negative='#ef4444', # Farbe für Fehler/Abbruch (Rot)
|
negative='#E31919', # Farbe für Fehler/Abbruch (Rot)
|
||||||
info='#3b82f6', # Info-Farbe
|
info='#939393', # Info-Farbe
|
||||||
warning='#f59e0b' # Warn-Farbe (Orange)
|
warning='#f59e0b', # Warn-Farbe (Orange)
|
||||||
|
# Farben für Texte
|
||||||
|
normaltext="#F7F7F7",
|
||||||
|
accenttext="#2078D4",
|
||||||
|
infotext="#A8A8A8"
|
||||||
)
|
)
|
||||||
|
|
||||||
# (Optional) Wenn du die Hintergrundfarbe der ganzen Seite ändern willst:
|
|
||||||
# ui.query('body').style('background-color: #121212;')
|
|
||||||
|
|
|
||||||
34
gui/info_text/info_system.py
Normal file
34
gui/info_text/info_system.py
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from nicegui import ui
|
||||||
|
|
||||||
|
# 1. Pfad zur JSON Datei berechnen
|
||||||
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
JSON_PATH = os.path.join(BASE_DIR, "info_texts.json")
|
||||||
|
|
||||||
|
# 2. JSON Datei in den Speicher laden
|
||||||
|
def load_info_texts():
|
||||||
|
if os.path.exists(JSON_PATH):
|
||||||
|
# encoding="utf-8" ist wichtig für deutsche Umlaute (ä, ö, ü)!
|
||||||
|
with open(JSON_PATH, "r", encoding="utf-8") as file:
|
||||||
|
return json.load(file)
|
||||||
|
else:
|
||||||
|
print("FEHLER! Keine info_texts.json gefunden!")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Wir laden das Wörterbuch genau 1x beim Serverstart
|
||||||
|
TEXT_DICTIONARY = load_info_texts()
|
||||||
|
|
||||||
|
# 3. Unser neuer Baustein für die Webseite
|
||||||
|
def create_info_button(topic_key):
|
||||||
|
# Den Text aus dem Wörterbuch holen. Falls nicht vorhanden wird eine Fehlermeldung geworfen.
|
||||||
|
texts = TEXT_DICTIONARY.get(topic_key, ["Hilfetext nicht gefunden!"])
|
||||||
|
|
||||||
|
# --- DER DIALOG (Unsichtbar im Hintergrund) ---
|
||||||
|
with ui.dialog().classes("w-full items-center") as info_dialog, ui.card().classes("items-center text-textnormal"):
|
||||||
|
# Loopen durch die Sätze in der JSON
|
||||||
|
for sentence in texts:
|
||||||
|
ui.markdown(sentence).classes("font-bold text-white text-lg text-center")
|
||||||
|
|
||||||
|
# --- DER BUTTON (Sichtbar auf der Seite) ---
|
||||||
|
ui.button(icon="info_outline", color= "", on_click=info_dialog.open).props('round dense')
|
||||||
24
gui/info_text/info_texts.json
Normal file
24
gui/info_text/info_texts.json
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"league_info": [
|
||||||
|
"Um einer Liga beizutreten einfach auf **BEITRETEN** drücken und bestätigen.",
|
||||||
|
"Um deine Statistik in einer Liga zu sehen, klick auf eine Liga."
|
||||||
|
],
|
||||||
|
|
||||||
|
"mmr_info": [
|
||||||
|
"**MMR Punkte** sind die Liga Punkte um die gespielt wird.",
|
||||||
|
"Verliert man ein Spiel, verliert man Punkte. Und umgekehrt."
|
||||||
|
],
|
||||||
|
|
||||||
|
"match_form_info": [
|
||||||
|
"Um ein Spiel einzutragen gibt einfach deine Punkte ein. Wähle deinen Gegner aus. Und gibt seine Punkte ein.",
|
||||||
|
"**ACHTUNG:** Ein Spieler ist nur als Gegner auswählbar wenn er sich in der Liga angemeldet hat!"
|
||||||
|
],
|
||||||
|
|
||||||
|
"tyrann_info":[
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
"prügelknabe_info":[
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
from nicegui import ui, app
|
from nicegui import ui, app
|
||||||
from gui import gui_style
|
from gui import gui_style
|
||||||
from data import data_api
|
from data import data_api
|
||||||
|
from gui.info_text import info_system
|
||||||
|
|
||||||
def setup_routes():
|
def setup_routes():
|
||||||
# 1. Die {}-Klammern definieren eine dynamische Variable in der URL
|
|
||||||
@ui.page('/statistic/{systemname}', dark=True)
|
@ui.page('/statistic/{systemname}', dark=True)
|
||||||
def gamesystem_statistic_page(systemname: str): # <--- WICHTIG: Hier fangen wir das Wort aus der URL auf!
|
def gamesystem_statistic_page(systemname: str):
|
||||||
|
|
||||||
# Sicherheitscheck: Ist der User eingeloggt?
|
|
||||||
if not app.storage.user.get('authenticated', False):
|
if not app.storage.user.get('authenticated', False):
|
||||||
ui.navigate.to('/')
|
ui.navigate.to('/')
|
||||||
return
|
return
|
||||||
|
|
@ -15,80 +14,81 @@ def setup_routes():
|
||||||
gui_style.apply_design()
|
gui_style.apply_design()
|
||||||
|
|
||||||
player_id = app.storage.user.get('db_id')
|
player_id = app.storage.user.get('db_id')
|
||||||
stats = data_api.get_player_system_stats(player_id, systemname)
|
all_stats = data_api.get_player_statistics(player_id)
|
||||||
|
|
||||||
|
# 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
|
||||||
|
)
|
||||||
|
|
||||||
|
if system_stat:
|
||||||
|
# None-Werte absichern
|
||||||
|
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 "-"
|
||||||
|
|
||||||
if stats:
|
|
||||||
with ui.header().classes('items-center justify-between bg-zinc-900 p-4 shadow-lg'):
|
with ui.header().classes('items-center justify-between bg-zinc-900 p-4 shadow-lg'):
|
||||||
ui.button('Zurück zur Übersicht', on_click=lambda: ui.navigate.to('/'))
|
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}'))
|
ui.button("Spiel eintragen", on_click=lambda: ui.navigate.to(f'/add-match/{systemname}'))
|
||||||
|
|
||||||
with ui.column().classes('w-full items-center mt-10'):
|
|
||||||
ui.label(f'Deine Statistik in {systemname}').classes('text-5xl font-bold text-blue-400')
|
|
||||||
|
|
||||||
|
with ui.column().classes('w-full items-center justify-center mt-10'):
|
||||||
# --- BLOCK 1 (2 Karten) ---
|
ui.label(f'Deine Statistik in {systemname}').classes('text-3xl justify-center font-bold text-normaltext')
|
||||||
# 1. Daten für die Rangliste holen
|
|
||||||
|
# --- BLOCK 1 (MMR & Rang | Rangliste) ---
|
||||||
leaderboard_data = data_api.get_leaderboard(systemname)
|
leaderboard_data = data_api.get_leaderboard(systemname)
|
||||||
|
|
||||||
# 2. Tabelle vorbereiten und den EIGENEN Rang herausfinden
|
|
||||||
table_rows = []
|
table_rows = []
|
||||||
my_rank = "-"
|
my_rank = "-"
|
||||||
|
|
||||||
for index, player in enumerate(leaderboard_data):
|
for index, player in enumerate(leaderboard_data):
|
||||||
current_rank = index + 1
|
current_rank = index + 1
|
||||||
|
|
||||||
# Wenn wir in der Liste über uns selbst stolpern, merken wir uns den Rang für unsere Karte!
|
|
||||||
if player['id'] == player_id:
|
if player['id'] == player_id:
|
||||||
my_rank = current_rank
|
my_rank = current_rank
|
||||||
|
|
||||||
table_rows.append({
|
table_rows.append({
|
||||||
'rank': current_rank,
|
'rank': current_rank,
|
||||||
'trend': '➖', # Platzhalter für später
|
'trend': '➖',
|
||||||
'name': f"{player['display_name']} 'aka' {player['discord_name']}",
|
'name': f"{player['display_name']} 'aka' {player['discord_name']}",
|
||||||
'mmr': player['mmr']
|
'mmr': player['mmr']
|
||||||
})
|
})
|
||||||
|
|
||||||
table_columns = [
|
table_columns = [
|
||||||
{'name': 'rank', 'label': '#', 'field': 'rank', 'align': 'left'},
|
{'name': 'rank', 'label': '#', 'field': 'rank', 'align': 'left'},
|
||||||
{'name': 'trend', 'label': 'Trend', 'field': 'trend', 'align': 'center'},
|
{'name': 'trend', 'label': 'Trend', 'field': 'trend', 'align': 'center'},
|
||||||
{'name': 'name', 'label': 'Spieler', 'field': 'name', 'align': 'left'},
|
{'name': 'name', 'label': 'Spieler', 'field': 'name', 'align': 'left'},
|
||||||
{'name': 'mmr', 'label': 'MMR', 'field': 'mmr', 'align': 'left'}
|
{'name': 'mmr', 'label': 'MMR', 'field': 'mmr', 'align': 'left'},
|
||||||
]
|
]
|
||||||
|
|
||||||
# --- BLOCK 1 (Links: MMR & Rang | Rechts: Rangliste) ---
|
|
||||||
# lg:grid-cols-3 teilt den Bildschirm auf großen Monitoren in 3 gleich große unsichtbare Spalten
|
|
||||||
with ui.element('div').classes("w-full grid grid-cols-1 lg:grid-cols-3 gap-4 mt-4"):
|
with ui.element('div').classes("w-full grid grid-cols-1 lg:grid-cols-3 gap-4 mt-4"):
|
||||||
|
|
||||||
# LINKE SEITE (Belegt 1 Spalte)
|
|
||||||
# flex-col setzt die beiden Karten exakt übereinander
|
|
||||||
with ui.column().classes("w-full gap-4"):
|
with ui.column().classes("w-full gap-4"):
|
||||||
|
|
||||||
with ui.card().classes("w-full items-center justify-center text-center"):
|
with ui.card().classes("w-full items-center justify-center text-center"):
|
||||||
ui.label("MMR Punkte: ").classes('text-2xl font-bold')
|
with ui.row().classes("w-full items-center text-center"):
|
||||||
ui.label(str(stats["mmr"])).classes('text-4xl font-bold text-blue-100')
|
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("w-full items-center justify-center text-center"):
|
with ui.card().classes("w-full items-center justify-center text-center"):
|
||||||
ui.label("Rang: ").classes('text-2xl font-bold')
|
ui.label("Rang: ").classes('text-2xl font-bold')
|
||||||
# Hier tragen wir jetzt unsere gefundene Platzierung ein!
|
|
||||||
ui.label(str(my_rank)).classes('text-4xl font-bold text-blue-100')
|
ui.label(str(my_rank)).classes('text-4xl font-bold text-blue-100')
|
||||||
|
|
||||||
# RECHTE SEITE (Belegt 2 Spalten -> lg:col-span-2)
|
|
||||||
with ui.card().classes("w-full lg:col-span-2"):
|
with ui.card().classes("w-full lg:col-span-2"):
|
||||||
ui.label("Liga Rangliste").classes("text-xl font-bold text-white mb-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')
|
ui.table(columns=table_columns, rows=table_rows, row_key='rank').classes('w-full bg-zinc-900 text-white')
|
||||||
|
|
||||||
|
|
||||||
# --- BLOCK 2 (5 Karten) ---
|
# --- BLOCK 2 (5 Karten) ---
|
||||||
with ui.card().classes("w-full"):
|
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.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"):
|
with ui.card().classes("items-center justify-center text-center"):
|
||||||
ui.label("Spiele: ").classes('text-2xl font-bold')
|
ui.label("Spiele: ").classes('text-2xl font-bold')
|
||||||
ui.label(str(stats["games_in_system"])).classes('text-4xl font-bold text-blue-100')
|
ui.label(str(games)).classes('text-4xl font-bold text-blue-100')
|
||||||
|
|
||||||
with ui.card().classes("items-center justify-center text-center"):
|
with ui.card().classes("items-center justify-center text-center"):
|
||||||
ui.label("Ø Punkte pro Spiel: ").classes('text-2xl font-bold')
|
ui.label("Ø Punkte pro Spiel: ").classes('text-2xl font-bold')
|
||||||
ui.label(str(stats["avv_points"])).classes('text-4xl font-bold text-blue-100')
|
ui.label(str(avv_points)).classes('text-4xl font-bold text-blue-100')
|
||||||
|
|
||||||
with ui.card().classes("items-center justify-center text-center"):
|
with ui.card().classes("items-center justify-center text-center"):
|
||||||
ui.label("Win-Rate: ").classes('text-2xl font-bold')
|
ui.label("Win-Rate: ").classes('text-2xl font-bold')
|
||||||
|
|
@ -96,8 +96,6 @@ def setup_routes():
|
||||||
|
|
||||||
with ui.card().classes("items-center justify-center text-center"):
|
with ui.card().classes("items-center justify-center text-center"):
|
||||||
ui.label("Letztes Spiel am: ").classes('text-2xl font-bold')
|
ui.label("Letztes Spiel am: ").classes('text-2xl font-bold')
|
||||||
# Schneidet die Zeit vom Datum ab, falls eins existiert
|
|
||||||
last_played = str(stats["last_played"])[:10] if stats["last_played"] else "-"
|
|
||||||
ui.label(last_played).classes('text-4xl font-bold text-blue-100')
|
ui.label(last_played).classes('text-4xl font-bold text-blue-100')
|
||||||
|
|
||||||
with ui.card().classes("items-center justify-center text-center"):
|
with ui.card().classes("items-center justify-center text-center"):
|
||||||
|
|
@ -120,52 +118,6 @@ def setup_routes():
|
||||||
ui.label("Dein 'Prügelknabe': ").classes('text-2xl font-bold')
|
ui.label("Dein 'Prügelknabe': ").classes('text-2xl font-bold')
|
||||||
ui.label("-").classes('text-4xl font-bold text-blue-100')
|
ui.label("-").classes('text-4xl font-bold text-blue-100')
|
||||||
|
|
||||||
|
|
||||||
# --- BLOCK 4 (Diagramm der letzten 20 Spiele) ---
|
|
||||||
with ui.card().classes("w-full items-center"):
|
|
||||||
ui.label("Punkte in den letzten 20 Spielen").classes('text-2xl font-bold mb-4')
|
|
||||||
|
|
||||||
# 1. Daten aus der API holen
|
|
||||||
chart_data = data_api.get_last_20_match_scores(player_id, systemname)
|
|
||||||
|
|
||||||
# 2. Nur zeichnen, wenn es überhaupt schon Spiele gibt!
|
|
||||||
if len(chart_data['points']) > 0:
|
|
||||||
ui.echart({
|
|
||||||
'xAxis': {
|
|
||||||
'type': 'category',
|
|
||||||
'data': chart_data['labels'],
|
|
||||||
'axisLabel': {'color': '#e5e7eb'}
|
|
||||||
},
|
|
||||||
'yAxis': {
|
|
||||||
'type': 'value',
|
|
||||||
'axisLabel': {'color': '#e5e7eb'},
|
|
||||||
'splitLine': {'lineStyle': {'color': '#3f3f46'}}
|
|
||||||
},
|
|
||||||
'series': [
|
|
||||||
{
|
|
||||||
'name': 'Punkte', # Zeigt im Tooltip "Punkte: 76" statt einer unbenannten Zahl
|
|
||||||
'type': 'line',
|
|
||||||
'data': chart_data['points'],
|
|
||||||
'smooth': True,
|
|
||||||
'color': '#60a5fa'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'tooltip': {
|
|
||||||
'trigger': 'axis',
|
|
||||||
'backgroundColor': '#27272a',
|
|
||||||
'textStyle': {'color': '#ffffff'},
|
|
||||||
'borderColor': '#3f3f46' # Ein feiner Rand für den Kasten
|
|
||||||
}
|
|
||||||
}).classes('w-full h-64')
|
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
ui.label("Keine Statistiken für dieses System gefunden.").classes('text-red-500 mt-4')
|
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')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ from nicegui import ui, app
|
||||||
from data import database, data_api
|
from data import database, data_api
|
||||||
from gui import discord_login, gui_style
|
from gui import discord_login, gui_style
|
||||||
from match_calculations import calc_match
|
from match_calculations import calc_match
|
||||||
|
from gui.info_text import info_system
|
||||||
|
|
||||||
def setup_routes(admin_discord_id):
|
def setup_routes(admin_discord_id):
|
||||||
@ui.page('/', dark=True)
|
@ui.page('/', dark=True)
|
||||||
|
|
@ -27,18 +28,19 @@ def setup_routes(admin_discord_id):
|
||||||
# --- NAVIGATIONSLEISTE (HEADER) ---
|
# --- NAVIGATIONSLEISTE (HEADER) ---
|
||||||
# ---------------------------
|
# ---------------------------
|
||||||
|
|
||||||
with ui.header().classes('items-center justify-between bg-zinc-900 p-4 shadow-lg'):
|
with ui.header().classes('items-center justify-between bg-zinc-900 shadow-lg').props("reveal reveal-offset=1"):
|
||||||
|
|
||||||
# --- LINKE SEITE ---
|
# --- LINKE SEITE ---
|
||||||
# Vereinslogo und den Titel in einer eigenen Reihe (Reihe 1)
|
# Vereinslogo und den Titel in einer eigenen Reihe (Reihe 1)
|
||||||
with ui.row().classes('items-center gap-4'):
|
with ui.row().classes('items-center'):
|
||||||
ui.image("gui/pictures/wsdg.png").classes('w-20 h-20 rounded-full')
|
ui.image("gui/pictures/wsdg.png").classes('w-15 h-15 rounded-full')
|
||||||
ui.label('Diceghost Liga').classes('text-2xl font-bold text-white')
|
ui.label('Diceghost Liga').classes('text-2xl font-bold text-normaltext')
|
||||||
|
|
||||||
# --- MITTE ---
|
# --- MITTE ---
|
||||||
discord_id = app.storage.user.get("discord_id")
|
if app.storage.user.get('authenticated', False):
|
||||||
if discord_id == admin_discord_id:
|
discord_id = app.storage.user.get("discord_id")
|
||||||
ui.button('Admin Panel', on_click=lambda: ui.navigate.to('/admin'))
|
if discord_id == admin_discord_id:
|
||||||
|
ui.button(icon="hardware", on_click=lambda: ui.navigate.to('/admin')).props("round")
|
||||||
|
|
||||||
# --- RECHTE SEITE ---
|
# --- RECHTE SEITE ---
|
||||||
if app.storage.user.get('authenticated', False):
|
if app.storage.user.get('authenticated', False):
|
||||||
|
|
@ -52,22 +54,21 @@ def setup_routes(admin_discord_id):
|
||||||
def toggle_edit_mode():
|
def toggle_edit_mode():
|
||||||
display_row.visible = not display_row.visible
|
display_row.visible = not display_row.visible
|
||||||
edit_row.visible = not edit_row.visible
|
edit_row.visible = not edit_row.visible
|
||||||
|
edit_button.visible = not edit_button.visible
|
||||||
|
|
||||||
# --- ANSICHT 1: Der normale Text mit Edit-Button ---
|
# --- ANSICHT 1: Der normale Text mit Edit-Button ---
|
||||||
with ui.row().classes('items-center gap-2') as display_row:
|
with ui.column().classes('items-center gap-0') as display_row:
|
||||||
ui.label(display_name).classes('text-xl font-bold text-white')
|
with ui.column():
|
||||||
ui.label("'aka'").classes('text-sm text-gray-500')
|
ui.label(display_name).classes('text-xl font-bold text-normaltext')
|
||||||
ui.label(discord_name).classes('text-lg text-gray-400')
|
with ui.row().classes("items-center justify-between"):
|
||||||
|
ui.label("'aka'").classes('text-sm text-italic text-infotext')
|
||||||
# Ein runder Button (.props('round'))
|
ui.label(discord_name).classes('text-m text-bold text-infotext')
|
||||||
ui.button(icon='edit', color='primary', on_click=toggle_edit_mode).props('round dense')
|
edit_button = ui.button(icon='edit', color='accent', on_click=toggle_edit_mode).props('round dense')
|
||||||
|
|
||||||
# --- ANSICHT 2: Das Eingabefeld (startet unsichtbar!) ---
|
# --- ANSICHT 2: Das Eingabefeld (startet unsichtbar!) ---
|
||||||
with ui.row().classes('items-center gap-5') as edit_row:
|
with ui.row().classes('items-center gap-5') as edit_row:
|
||||||
edit_row.visible = False # Am Anfang verstecken
|
edit_row.visible = False # Am Anfang verstecken
|
||||||
|
|
||||||
def save_new_name():
|
def save_new_name():
|
||||||
|
|
||||||
new_name = name_input.value
|
new_name = name_input.value
|
||||||
# Nur speichern, wenn ein Name drinsteht und er anders ist als vorher
|
# Nur speichern, wenn ein Name drinsteht und er anders ist als vorher
|
||||||
if new_name and new_name != display_name:
|
if new_name and new_name != display_name:
|
||||||
|
|
@ -84,21 +85,20 @@ def setup_routes(admin_discord_id):
|
||||||
silly_name = data_api.generate_silly_name()
|
silly_name = data_api.generate_silly_name()
|
||||||
name_input.value=silly_name
|
name_input.value=silly_name
|
||||||
|
|
||||||
|
|
||||||
name_input = ui.input('Neuer Name', value=display_name).on('keydown.enter', save_new_name)
|
name_input = ui.input('Neuer Name', value=display_name).on('keydown.enter', save_new_name)
|
||||||
ui.button(icon='save', color='positive', on_click=save_new_name).props('round dense')
|
ui.button(icon='save', color='positive', on_click=save_new_name).props('round dense')
|
||||||
ui.button(icon='casino', on_click=generate_random_silly_name).props('round dense')
|
ui.button(icon='casino', color="accent", on_click=generate_random_silly_name).props('round dense')
|
||||||
ui.button(icon='close', color='negative', on_click=toggle_edit_mode).props('round dense')
|
ui.button(icon='close', color='negative', on_click=toggle_edit_mode).props('round dense')
|
||||||
|
|
||||||
avatar = app.storage.user.get('avatar_url')
|
avatar = app.storage.user.get('avatar_url')
|
||||||
if avatar:
|
if avatar:
|
||||||
ui.image(avatar).classes('w-12 h-12 rounded-full border-2 border-red-500')
|
ui.image(avatar).classes('w-5 h-5 rounded-full border-2 border-red-500')
|
||||||
|
|
||||||
def logout():
|
def logout():
|
||||||
app.storage.user.clear()
|
app.storage.user.clear()
|
||||||
ui.navigate.to('/')
|
ui.navigate.to('/')
|
||||||
|
|
||||||
ui.button('Logout', on_click=logout).classes('bg-red-500 text-white')
|
ui.button(icon="logout", on_click=logout).props('round dense size=lg')
|
||||||
|
|
||||||
else:
|
else:
|
||||||
auth_url = discord_login.get_auth_url()
|
auth_url = discord_login.get_auth_url()
|
||||||
|
|
@ -115,19 +115,20 @@ def setup_routes(admin_discord_id):
|
||||||
if len(unconfirmed_matches) > 0:
|
if len(unconfirmed_matches) > 0:
|
||||||
# Eine auffällige, rote Karte über die volle Breite
|
# Eine auffällige, rote Karte über die volle Breite
|
||||||
with ui.card().classes('w-full bg-red-900/80 border-2 border-red-500 mb-6'):
|
with ui.card().classes('w-full bg-red-900/80 border-2 border-red-500 mb-6'):
|
||||||
ui.label(f"Aktion erforderlich: Du hast {len(unconfirmed_matches)} unbestätigte(s) Spiel(e)!").classes('text-2xl font-bold text-white mb-2')
|
ui.label(f"Aktion erforderlich: Du hast {len(unconfirmed_matches)} offen(e) Spiel(e)!").classes('text-2xl font-bold text-normaltext mb-2')
|
||||||
|
|
||||||
for match in unconfirmed_matches:
|
for match in unconfirmed_matches:
|
||||||
|
|
||||||
# Button Funktionen. Akzeptieren oder Rejecten. Der Reject Button löscht das Match aus der DB.
|
# Button Funktionen. Akzeptieren oder Rejecten. Der Reject Button löscht das Match aus der DB.
|
||||||
def reject_match(m_id):
|
def reject_match(m_id):
|
||||||
data_api.delete_match(m_id)
|
data_api.delete_match(m_id, player_id)
|
||||||
ui.notify("Spiel abgelehnt und gelöscht!", color="warning")
|
ui.notify("Spiel abgelehnt und gelöscht!", color="warning")
|
||||||
ui.navigate.reload() # Lädt die Seite neu, um die Karte zu aktualisieren
|
ui.navigate.reload() # Lädt die Seite neu, um die Karte zu aktualisieren
|
||||||
|
|
||||||
def acccept_match(m_id):
|
def acccept_match(m_id):
|
||||||
ui.notify("Spiel akzeptiert. Wird Berechnet.")
|
ui.notify("Spiel akzeptiert. Wird Berechnet.")
|
||||||
ui.navigate.reload() # Lädt die Seite neu, um die Karte zu aktualisieren
|
ui.navigate.reload() # Lädt die Seite neu, um die Karte zu aktualisieren
|
||||||
|
data_api.confirm_match(m_id)
|
||||||
calc_match.calculate_match(m_id)
|
calc_match.calculate_match(m_id)
|
||||||
|
|
||||||
with ui.row().classes('w-full items-center justify-between bg-zinc-900 p-3 rounded shadow-inner'):
|
with ui.row().classes('w-full items-center justify-between bg-zinc-900 p-3 rounded shadow-inner'):
|
||||||
|
|
@ -138,10 +139,10 @@ def setup_routes(admin_discord_id):
|
||||||
# Die Buttons (Funktion machen wir im nächsten Schritt!)
|
# Die Buttons (Funktion machen wir im nächsten Schritt!)
|
||||||
with ui.row().classes('gap-2'):
|
with ui.row().classes('gap-2'):
|
||||||
# ABLEHNEN und Spiel löschen
|
# ABLEHNEN und Spiel löschen
|
||||||
ui.button("Ablehnen", color="negative", icon="close", on_click=lambda e, m_id=match['match_id']: reject_match(m_id))
|
ui.button(color="negative", icon="close", on_click=lambda e, m_id=match['match_id']: reject_match(m_id))
|
||||||
ui.space()
|
ui.space()
|
||||||
# BESTÄTIGEN und spiel berechnen lassen
|
# BESTÄTIGEN und spiel berechnen lassen
|
||||||
ui.button("Bestätigen", color="positive", icon="check", on_click=lambda e, m_id=match['match_id']: acccept_match(m_id))
|
ui.button(color="positive", icon="check", on_click=lambda e, m_id=match['match_id']: acccept_match(m_id))
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------
|
# ---------------------------
|
||||||
|
|
@ -154,7 +155,6 @@ def setup_routes(admin_discord_id):
|
||||||
ui.label(f"Warten auf Gegner: Du hast {len(submitted_matches)} offene(s) Spiel(e) eingetragen.").classes('text-xl font-bold text-gray-300 mb-2')
|
ui.label(f"Warten auf Gegner: Du hast {len(submitted_matches)} offene(s) Spiel(e) eingetragen.").classes('text-xl font-bold text-gray-300 mb-2')
|
||||||
|
|
||||||
for match in submitted_matches:
|
for match in submitted_matches:
|
||||||
|
|
||||||
# Die Lösch-Funktion, die beim Klick ausgeführt wird
|
# Die Lösch-Funktion, die beim Klick ausgeführt wird
|
||||||
def retract_match(m_id):
|
def retract_match(m_id):
|
||||||
data_api.delete_match(m_id, player_id)
|
data_api.delete_match(m_id, player_id)
|
||||||
|
|
@ -176,11 +176,13 @@ def setup_routes(admin_discord_id):
|
||||||
# --- Spielsysteme ---
|
# --- Spielsysteme ---
|
||||||
# ---------------------------
|
# ---------------------------
|
||||||
if app.storage.user.get('authenticated', False):
|
if app.storage.user.get('authenticated', False):
|
||||||
with ui.card().classes("w-full"):
|
with ui.card().classes("w-full items-center"):
|
||||||
ui.label(text="Meine Ligaplätze").classes("font-bold text-white text-xl")
|
with ui.row():
|
||||||
|
ui.label(text="Meine Ligaplätze").classes("font-bold text-white text-xl text-normaltext")
|
||||||
|
info_system.create_info_button("league_info")
|
||||||
|
|
||||||
placements = data_api.get_player_statistics(player_id)
|
placements = data_api.get_player_statistics(player_id)
|
||||||
systems = data_api.get_gamesystem_data()
|
systems = data_api.get_gamesystems_data()
|
||||||
|
|
||||||
my_stats = { p['gamesystem_id']: p for p in placements }
|
my_stats = { p['gamesystem_id']: p for p in placements }
|
||||||
|
|
||||||
|
|
@ -203,12 +205,12 @@ def setup_routes(admin_discord_id):
|
||||||
sys_card = ui.card().classes("h-60 w-full items-center justify-center transition-colors")
|
sys_card = ui.card().classes("h-60 w-full items-center justify-center transition-colors")
|
||||||
|
|
||||||
with sys_card:
|
with sys_card:
|
||||||
ui.label(text=sys_name).classes('text-xl font-bold text-center')
|
ui.label(text=sys_name).classes('text-xl font-bold text-center text-normaltext')
|
||||||
if sys_logo:
|
if sys_logo:
|
||||||
ui.image(f"/pictures/{sys_logo}").classes("w-60")
|
ui.image(f"/pictures/{sys_logo}").classes("w-60")
|
||||||
|
|
||||||
if sys_description:
|
if sys_description:
|
||||||
ui.label(text=sys['description']).classes('text-xs text-gray-400 text-center mt-2')
|
ui.label(text=sys['description']).classes('text-xs text-gray-400 text-center mt-2 text-infotext')
|
||||||
|
|
||||||
# Prüfen: Ist diese sys_id in den Stats des Spielers? UND hat er ein MMR?
|
# 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:
|
if sys_id not in my_stats or my_stats[sys_id]['mmr'] is None:
|
||||||
|
|
@ -232,16 +234,16 @@ def setup_routes(admin_discord_id):
|
||||||
sys_card.on('click', lambda e, name=sys_name: ui.navigate.to(f'/statistic/{name}'))
|
sys_card.on('click', lambda e, name=sys_name: ui.navigate.to(f'/statistic/{name}'))
|
||||||
|
|
||||||
with ui.row().classes('items-center gap-4'):
|
with ui.row().classes('items-center gap-4'):
|
||||||
ui.label(text=f"MMR: {stat['mmr']}").classes("text-lg font-bold text-blue-400")
|
ui.label(text=f"MMR: {stat['mmr']}").classes("text-lg font-bold text-accenttext")
|
||||||
ui.label(text=f"Spiele: {stat['games_in_system']}").classes("text-lg")
|
ui.label(text=f"Spiele: {stat['games_in_system']}").classes("text-lg font-bold text-accenttext")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------
|
# ---------------------------
|
||||||
# Match Historie
|
# Match Historie
|
||||||
# ---------------------------
|
# ---------------------------
|
||||||
with ui.card().classes("w-full"):
|
with ui.card().classes("w-full items-center"):
|
||||||
ui.label(text= "Meine letzten Spiele").classes("font-bold text-white text-xl")
|
ui.label(text= "Meine letzten Spiele").classes("font-bold text-normaltext text-xl")
|
||||||
# 1. Daten aus der DB holen ABER per [:5] hart auf die neuesten 5 Listen-Einträge abschneiden!
|
# 1. Daten aus der DB holen ABER per [:5] hart auf die neuesten 5 Listen-Einträge abschneiden!
|
||||||
raw_matches = data_api.get_recent_matches_for_player(player_id)[:5]
|
raw_matches = data_api.get_recent_matches_for_player(player_id)[:5]
|
||||||
|
|
||||||
|
|
@ -285,7 +287,7 @@ def setup_routes(admin_discord_id):
|
||||||
ui.table(columns=table_columns, rows=table_rows, row_key='date').classes('w-full bg-zinc-900 text-white')
|
ui.table(columns=table_columns, rows=table_rows, row_key='date').classes('w-full bg-zinc-900 text-white')
|
||||||
|
|
||||||
# NEU: Der Button, der zur großen Log-Seite führt
|
# NEU: Der Button, der zur großen Log-Seite führt
|
||||||
ui.button("Komplette Historie & Log anzeigen", icon="history", on_click=lambda: ui.navigate.to('/matchhistory')).classes('w-full mt-4 bg-zinc-700 text-white hover:bg-zinc-600')
|
ui.button("Komplette Historie & Log anzeigen", icon="history", on_click=lambda: ui.navigate.to('/matchhistory')).classes('mt-4 bg-zinc-700 text-white hover:bg-zinc-600')
|
||||||
else:
|
else:
|
||||||
ui.label("Noch keine Spiele absolviert.").classes("text-gray-500 italic")
|
ui.label("Noch keine Spiele absolviert.").classes("text-infotext italic")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,15 @@ def setup_routes():
|
||||||
ui.notify("Match erfolgreich eingetragen!", color="green")
|
ui.notify("Match erfolgreich eingetragen!", color="green")
|
||||||
ui.navigate.to(f'/statistic/{systemname}')
|
ui.navigate.to(f'/statistic/{systemname}')
|
||||||
|
|
||||||
|
|
||||||
|
with ui.dialog().classes("w-full items-center") as form_info, ui.card():
|
||||||
|
ui.label('Um ein Spiel einzutragen einfach deine erspielten Punkte, deinen Gegner und die Punkte von deinem Gegner eintragen').classes("font-bold text-white text-l")
|
||||||
|
ui.label('ACHTUNG: Damit ein Spieler als Gegner ausgewählt werden kann, muss er der Liga beigetreten sein!').classes("font-bold text-white text-l")
|
||||||
|
ui.label('Nach dem Absenden muss dein Gegner in seiner Liga App das Spiel noch kurz bestätigen. Solltest du einen Fehler gemacht haben, kannst du das Spiel (bevor es bestätigt wurde) auf der Hauptseite selber löschen.').classes("font-bold text-white text-l")
|
||||||
|
ui.button(icon="close", on_click=form_info.close).classes("w-10 h-8 rounded-full")
|
||||||
|
|
||||||
# Buttons ganz unten in einer Reihe
|
# Buttons ganz unten in einer Reihe
|
||||||
with ui.row().classes("w-full items-center justify-between mt-8"):
|
with ui.row().classes("w-full items-center justify-between mt-8"):
|
||||||
ui.button('Cancel', on_click=lambda: ui.navigate.to(f'/statistic/{systemname}')).classes('bg-gray-500 text-white')
|
ui.button(icon="close", on_click=lambda: ui.navigate.to(f'/statistic/{systemname}')).classes("w-10 h-8 rounded-full")
|
||||||
|
ui.button(icon="help", color="information" ,on_click=form_info.open).classes("w-10 h-8 rounded-full")
|
||||||
ui.button(text="Absenden", color="positive", on_click=lambda: input_match_to_database())
|
ui.button(text="Absenden", color="positive", on_click=lambda: input_match_to_database())
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,8 @@ def setup_routes():
|
||||||
mmr_text = "0"
|
mmr_text = "0"
|
||||||
elif my_mmr_change > 0:
|
elif my_mmr_change > 0:
|
||||||
mmr_text = f"+{my_mmr_change}"
|
mmr_text = f"+{my_mmr_change}"
|
||||||
|
elif my_mmr_change < 0:
|
||||||
|
mmr_text = f"{my_mmr_change}"
|
||||||
else:
|
else:
|
||||||
mmr_text = str(my_mmr_change)
|
mmr_text = str(my_mmr_change)
|
||||||
|
|
||||||
|
|
@ -63,7 +65,8 @@ def setup_routes():
|
||||||
'date': str(match['played_at'])[:10],
|
'date': str(match['played_at'])[:10],
|
||||||
'system': match['gamesystem_name'],
|
'system': match['gamesystem_name'],
|
||||||
'opponent': opponent,
|
'opponent': opponent,
|
||||||
'score': f"{my_score} : {opp_score}",
|
'score': f"{my_score}",
|
||||||
|
'opp_score': f"{opp_score}",
|
||||||
'result': result,
|
'result': result,
|
||||||
'mmr': mmr_text
|
'mmr': mmr_text
|
||||||
})
|
})
|
||||||
|
|
@ -72,8 +75,9 @@ def setup_routes():
|
||||||
columns = [
|
columns = [
|
||||||
{'name': 'date', 'label': 'Datum', 'field': 'date', 'align': 'left'},
|
{'name': 'date', 'label': 'Datum', 'field': 'date', 'align': 'left'},
|
||||||
{'name': 'system', 'label': 'System', 'field': 'system', 'align': 'left'},
|
{'name': 'system', 'label': 'System', 'field': 'system', 'align': 'left'},
|
||||||
|
{'name': 'score', 'label': 'Punkte', 'field': 'score', 'align': 'left'},
|
||||||
{'name': 'opponent', 'label': 'Gegner', 'field': 'opponent', 'align': 'left'},
|
{'name': 'opponent', 'label': 'Gegner', 'field': 'opponent', 'align': 'left'},
|
||||||
{'name': 'score', 'label': 'Punkte', 'field': 'score', 'align': 'center'},
|
{'name': 'opp_score', 'label': 'Gegner Punkte', 'field': 'score', 'align': 'center'},
|
||||||
{'name': 'result', 'label': 'Ergebnis', 'field': 'result', 'align': 'left'},
|
{'name': 'result', 'label': 'Ergebnis', 'field': 'result', 'align': 'left'},
|
||||||
{'name': 'mmr', 'label': 'MMR Änderung', 'field': 'mmr', 'align': 'right'}
|
{'name': 'mmr', 'label': 'MMR Änderung', 'field': 'mmr', 'align': 'right'}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
1
main.py
1
main.py
|
|
@ -5,6 +5,7 @@ from nicegui import ui, app
|
||||||
from data import database
|
from data import database
|
||||||
from gui import main_gui, match_gui, discord_login, league_statistic, admin_gui, match_history_gui
|
from gui import main_gui, match_gui, discord_login, league_statistic, admin_gui, match_history_gui
|
||||||
from wood import logger
|
from wood import logger
|
||||||
|
from gui.info_text import info_system
|
||||||
|
|
||||||
# 1. Lade die geheimen Variablen aus der .env Datei in den Speicher
|
# 1. Lade die geheimen Variablen aus der .env Datei in den Speicher
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
from data import data_api
|
from data import data_api
|
||||||
from match_calculations import calc_mmr_change
|
from match_calculations import calculation
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
from wood import logger
|
||||||
|
|
||||||
|
|
||||||
|
point_inflation = 0.7 # => entspricht %
|
||||||
|
K_FACTOR = 30 # 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.
|
# Mach die DB abfrage für die Relevanten Daten. Von hier aus werden die "Aufgaben" und Daten dann an die kleineren Berechnungs Funktionen verteilt.
|
||||||
def calculate_match (match_id):
|
def calculate_match (match_id):
|
||||||
|
|
@ -11,82 +16,94 @@ def calculate_match (match_id):
|
||||||
print("Fehler: Match nicht gefunden!")
|
print("Fehler: Match nicht gefunden!")
|
||||||
return
|
return
|
||||||
|
|
||||||
data_api.confirm_match(match_id)
|
# Laden und aufdröseln der Match Daten
|
||||||
|
|
||||||
# Laden und umsetzen der Match Daten
|
|
||||||
p1_id = match_data['player1_id']
|
p1_id = match_data['player1_id']
|
||||||
p2_id = match_data['player2_id']
|
p2_id = match_data['player2_id']
|
||||||
p1_score = match_data['score_player1']
|
p1_score = match_data['score_player1']
|
||||||
p2_score = match_data['score_player2']
|
p2_score = match_data['score_player2']
|
||||||
sys_name = match_data['gamesystem_name']
|
system_name = match_data['gamesystem_name']
|
||||||
sys_id = match_data['gamesystem_id']
|
system_id = match_data['gamesystem_id']
|
||||||
|
|
||||||
rules = load_mmr_rule_matrix(sys_name)
|
|
||||||
draw_diff = rules["draw_point_difference"]
|
|
||||||
|
|
||||||
calculated = False
|
|
||||||
winner_id = None
|
|
||||||
looser_id = None
|
|
||||||
match_is_draw = False
|
match_is_draw = False
|
||||||
winner_score = 0
|
draw_diff = determine_draw_diff(system_id)
|
||||||
looser_score = 0
|
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
# Abgrenzen ob das Match schon berechnet wurde. Weil ein Draw kann 4 Punkte unterschied haben
|
# 1. IDENTIFIKATION
|
||||||
# 43-41 ist ein Draw aber rein Mathematisch würde es auch ein anderes if triggern
|
# ==========================================
|
||||||
# Abgrenzen, wer gewonnen hat (if, elif, else Kette)
|
# Draw
|
||||||
|
|
||||||
# 1. Ist es ein Unentschieden (Draw)?
|
|
||||||
if -draw_diff <= (p1_score - p2_score) <= draw_diff:
|
if -draw_diff <= (p1_score - p2_score) <= draw_diff:
|
||||||
|
winner_id, looser_id = p1_id, p2_id
|
||||||
|
winner_score, looser_score = p1_score, p2_score
|
||||||
match_is_draw = True
|
match_is_draw = True
|
||||||
winner_id = p1_id # Bei Draw ist egal, wer wo steht
|
# P1 Winner
|
||||||
looser_id = p2_id
|
|
||||||
winner_score = p1_score
|
|
||||||
looser_score = p2_score
|
|
||||||
|
|
||||||
# 2. Wenn KEIN Draw: Hat Spieler 1 gewonnen?
|
|
||||||
elif p1_score > p2_score:
|
elif p1_score > p2_score:
|
||||||
match_is_draw = False
|
winner_id, looser_id = p1_id, p2_id
|
||||||
winner_id = p1_id
|
winner_score, looser_score = p1_score, p2_score
|
||||||
looser_id = p2_id
|
# P2 Winner
|
||||||
winner_score = p1_score
|
|
||||||
looser_score = p2_score
|
|
||||||
|
|
||||||
# 3. Wenn weder Draw noch P1 Sieg, MUSS P2 gewonnen haben!
|
|
||||||
else:
|
else:
|
||||||
match_is_draw = False
|
winner_id, looser_id = p2_id, p1_id
|
||||||
winner_id = p2_id
|
winner_score, looser_score = p2_score, p1_score
|
||||||
looser_id = p1_id
|
|
||||||
winner_score = p2_score
|
|
||||||
looser_score = p1_score
|
|
||||||
|
|
||||||
mmr_change_winner, mmr_change_looser = calc_mmr_change.calc_mmr_change(sys_name, winner_id, looser_id, winner_score, looser_score, match_is_draw, rules)
|
|
||||||
|
|
||||||
data_api.apply_match_to_player_statistic (winner_id, sys_id, mmr_change_winner, winner_score)
|
|
||||||
data_api.apply_match_to_player_statistic (looser_id, sys_id, mmr_change_looser, looser_score)
|
|
||||||
|
|
||||||
# Zuordnen: Welcher Change gehört zu P1 und welcher zu P2?
|
|
||||||
if winner_id == p1_id:
|
|
||||||
p1_change = mmr_change_winner
|
|
||||||
p2_change = mmr_change_looser
|
|
||||||
else:
|
|
||||||
# Wenn der Sieger nicht P1 ist, muss P1 der Verlierer sein (oder Draw, da ist es egal)
|
|
||||||
p1_change = mmr_change_looser
|
|
||||||
p2_change = mmr_change_winner
|
|
||||||
|
|
||||||
# Die Änderungen für das Log ins Match eintragen!
|
|
||||||
data_api.update_match_mmr_change(match_id, p1_change, p2_change)
|
|
||||||
|
|
||||||
# Das Match als Berechnet markieren
|
|
||||||
data_api.set_match_counted(match_id)
|
|
||||||
|
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# 2. DIE BERECHNUNG & ROUTING-TABELLE
|
||||||
|
# ==========================================
|
||||||
|
# Wir speichern die Ergebnisse direkt in einem temporären Dictionary,
|
||||||
|
# und nutzen die SPIELER-ID als Schlüsselwort!
|
||||||
|
elo_factor = calculation.calc_elo_factor(winner_id, looser_id, system_name)
|
||||||
|
base_change = int(calculation.calc_base_change(elo_factor, match_is_draw, K_FACTOR))
|
||||||
|
rust_factor = calculation.calc_rust_factor(winner_id, looser_id, system_id)
|
||||||
|
|
||||||
def load_mmr_rule_matrix(systemname):
|
#winner
|
||||||
safe_name = systemname.replace(" ", "_").lower()
|
w_base = int(base_change)
|
||||||
|
w_khorne = int(calculation.wrath_of_khorne(winner_id, system_id))
|
||||||
|
|
||||||
file_path = f"match_calculations/mmr_rules_{safe_name}.json"
|
#looser
|
||||||
with open(file_path, "r", encoding="utf-8") as file:
|
l_base = int(base_change*point_inflation)
|
||||||
rules = json.load(file)
|
l_khorne = int(calculation.wrath_of_khorne(looser_id, system_id))
|
||||||
|
|
||||||
return rules
|
slaanesh = int(calculation.slaanesh_delight())
|
||||||
|
tzeentch = int(calculation.tzeentch_scemes(system_id, winner_score, looser_score))
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# 3. Daten Verpacken
|
||||||
|
# ==========================================
|
||||||
|
calc_results = {
|
||||||
|
"match_id" : match_id,
|
||||||
|
"elo_factor" : elo_factor,
|
||||||
|
"rust_factor" : rust_factor,
|
||||||
|
"point_inflation" : point_inflation,
|
||||||
|
|
||||||
|
"winner_id" : winner_id, # <-- String-Key für save_calculated_match
|
||||||
|
"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 + 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 + l_khorne - slaanesh - tzeentch) * rust_factor),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.log("MATCH CALC", f"Match{match_id} berechnet.")
|
||||||
|
logger.log("MATCH CALC", f"Winner {data_api.get_player_name(winner_id)}: Base {w_base} + Khorne {w_khorne} + Slaanesh {slaanesh} + Tzeentch {tzeentch}")
|
||||||
|
logger.log("MATCH CALC", f"Looser {data_api.get_player_name(looser_id)}: Base {l_base} + Khorne {l_khorne} - Slaanesh {slaanesh} - Tzeentch {tzeentch}")
|
||||||
|
data_api.save_calculated_match(calc_results)
|
||||||
|
|
||||||
|
|
||||||
|
def determine_draw_diff(sys_id):
|
||||||
|
draw_diff = 0
|
||||||
|
match sys_id:
|
||||||
|
case 1:
|
||||||
|
draw_diff = 3
|
||||||
|
case 2,3:
|
||||||
|
draw_diff = 1
|
||||||
|
return draw_diff
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
from data import data_api
|
|
||||||
|
|
||||||
# Faktor für die Punkte Inflation. Um diesen Wert verliert der Verlierer weniger Punkte als der Sieger bekommt. Über Kurz oder Lang werden
|
|
||||||
# die meisten Spieler über 1000MMR sein. Sprich: Neueinsteiger, oder leute die weniger spielen sind eher im unteren Ende als in der Mitte.
|
|
||||||
point_inflation = 0.7 # => entspricht % ! z.B. 0.7 = 70%
|
|
||||||
|
|
||||||
def calc_mmr_change(systemname, winner_id, looser_id, winner_points, looser_points, match_is_draw, rules):
|
|
||||||
#Rang der Spieler holen.
|
|
||||||
gamesystem_id = data_api.get_gamesystem_id_by_name(systemname)
|
|
||||||
winner_rank = data_api.get_player_rank(winner_id,gamesystem_id)
|
|
||||||
looser_rank = data_api.get_player_rank(looser_id, gamesystem_id)
|
|
||||||
|
|
||||||
if match_is_draw:
|
|
||||||
mmr_change = rules["rank_matrix"][str(max(-10, min(10, winner_rank-looser_rank)))]["draw"]
|
|
||||||
else:
|
|
||||||
mmr_change = rules["rank_matrix"][str(max(-10, min(10, winner_rank-looser_rank)))]["win"]
|
|
||||||
|
|
||||||
# Slaanesh Points berechnen und dem Change hinzufügen.
|
|
||||||
mmr_change += (sla_points := slaanesh_delight(winner_points, looser_points, rules))
|
|
||||||
|
|
||||||
# Variablen für den mmr_change anlegen. Sieger-Verlierer sind unterschiedlich!
|
|
||||||
winner_mmr_change = 0 + (mmr_change + wrath_of_khorne(winner_id))
|
|
||||||
looser_mmr_change = 0 + (mmr_change + wrath_of_khorne(looser_id))
|
|
||||||
|
|
||||||
if not match_is_draw:
|
|
||||||
# Verlierer verliert nur einen Teil der Punkte
|
|
||||||
looser_mmr_change = -int(winner_mmr_change * point_inflation)
|
|
||||||
|
|
||||||
return winner_mmr_change, looser_mmr_change
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------
|
|
||||||
khorne_days = 16
|
|
||||||
khorne_bonus = 8
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------
|
|
||||||
def wrath_of_khorne(player_id):
|
|
||||||
last_played = data_api.get_days_since_last_game(player_id)["days_ago"]
|
|
||||||
if last_played <= khorne_days:
|
|
||||||
return khorne_bonus
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def slaanesh_delight(winner_points, looser_points, rules):
|
|
||||||
|
|
||||||
point_diff = winner_points - looser_points
|
|
||||||
# Standardwert, falls gar nichts zutrifft
|
|
||||||
bonus = 0
|
|
||||||
|
|
||||||
for threshold in rules["score_bonus"]:
|
|
||||||
if point_diff >= threshold["min_diff"]:
|
|
||||||
bonus = threshold["bonus"]
|
|
||||||
break
|
|
||||||
return bonus
|
|
||||||
|
|
||||||
|
|
||||||
def tzeentch_scemes():
|
|
||||||
print("k")
|
|
||||||
|
|
||||||
def nurgles_entropy():
|
|
||||||
print("k")
|
|
||||||
77
match_calculations/calculation.py
Normal file
77
match_calculations/calculation.py
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
from data import data_api
|
||||||
|
from wood import logger
|
||||||
|
|
||||||
|
# Faktor für die Punkte Inflation. Um diesen Wert verliert der Verlierer weniger Punkte als der Sieger bekommt. Über Kurz oder Lang werden
|
||||||
|
# die meisten Spieler über 1000MMR sein. Sprich: Neueinsteiger, oder leute die weniger spielen sind eher im unteren Ende als in der Mitte.
|
||||||
|
|
||||||
|
|
||||||
|
def calc_base_change(elo_factor, match_is_draw, K_FACTOR):
|
||||||
|
if match_is_draw:
|
||||||
|
# Bei einem Draw (0.5) gewinnt der Schwächere leicht Punkte, der Stärkere verliert leicht.
|
||||||
|
base_change = (K_FACTOR * (0.5 - elo_factor)/2)
|
||||||
|
else:
|
||||||
|
# Sieg (1.0). Gewinnt der Favorit, gibt es wenig Punkte. Gewinnt der Underdog, gibt es viele!
|
||||||
|
base_change = K_FACTOR * (1.0 - elo_factor)
|
||||||
|
|
||||||
|
return base_change
|
||||||
|
|
||||||
|
|
||||||
|
def calc_elo_factor(winner_id, looser_id, system_name):
|
||||||
|
w_stats = data_api.get_player_statistics(winner_id)
|
||||||
|
l_stats = data_api.get_player_statistics(looser_id)
|
||||||
|
|
||||||
|
# Passendes System aus der Liste herausfiltern
|
||||||
|
w_stat = next((s for s in w_stats if s['gamesystem_name'] == system_name), None)
|
||||||
|
l_stat = next((s for s in l_stats if s['gamesystem_name'] == system_name), None)
|
||||||
|
|
||||||
|
w_mmr = w_stat['mmr'] if w_stat and w_stat['mmr'] is not None else 1000
|
||||||
|
l_mmr = l_stat['mmr'] if l_stat and l_stat['mmr'] is not None else 1000
|
||||||
|
|
||||||
|
return (1 / (1 + 10 ** ((l_mmr - w_mmr) / 400)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def calc_rust_factor(winner_id, looser_id, gamesystem_id):
|
||||||
|
|
||||||
|
# 3. Den "Rostigkeits-Dämpfer" errechnen.
|
||||||
|
w_days = data_api.get_days_since_last_system_game(winner_id, gamesystem_id)
|
||||||
|
l_days = data_api.get_days_since_last_system_game(looser_id, gamesystem_id)
|
||||||
|
|
||||||
|
# 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.1 # Maximal eingerostet (nur 10% der Punkteänderung)
|
||||||
|
else:
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
|
||||||
|
def wrath_of_khorne(player_id, system_id):
|
||||||
|
# -----------------
|
||||||
|
khorne_days = 16
|
||||||
|
khorne_bonus = 8
|
||||||
|
# -----------------
|
||||||
|
|
||||||
|
last_played = int(data_api.get_days_since_last_system_game(player_id, system_id))
|
||||||
|
if last_played <= khorne_days:
|
||||||
|
return khorne_bonus
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def slaanesh_delight():
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def tzeentch_scemes(system_id, winner_score, looser_score):
|
||||||
|
sys_data = data_api.get_gamesystem_data(system_id)
|
||||||
|
min_score = sys_data["min_score"]
|
||||||
|
max_score = sys_data["max_score"]
|
||||||
|
|
||||||
|
bonus = int(((max_score*(winner_score-looser_score)))/(max_score*1.1)) #Multiplikatiionsfaktor für die Berechnung.
|
||||||
|
|
||||||
|
return bonus
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
{
|
|
||||||
"system_info": "Balancing für Spearhead",
|
|
||||||
"draw_point_difference": 3,
|
|
||||||
"rank_matrix": {
|
|
||||||
"10": {
|
|
||||||
"win": 10,
|
|
||||||
"draw": 30
|
|
||||||
},
|
|
||||||
"9": {
|
|
||||||
"win": 10,
|
|
||||||
"draw": 30
|
|
||||||
},
|
|
||||||
"8": {
|
|
||||||
"win": 10,
|
|
||||||
"draw": 20
|
|
||||||
},
|
|
||||||
"7": {
|
|
||||||
"win": 10,
|
|
||||||
"draw": 20
|
|
||||||
},
|
|
||||||
"6": {
|
|
||||||
"win": 20,
|
|
||||||
"draw": 10
|
|
||||||
},
|
|
||||||
"5": {
|
|
||||||
"win": 20,
|
|
||||||
"draw": 10
|
|
||||||
},
|
|
||||||
"4": {
|
|
||||||
"win": 20,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"3": {
|
|
||||||
"win": 20,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"2": {
|
|
||||||
"win": 30,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"1": {
|
|
||||||
"win": 30,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"0": {
|
|
||||||
"win": 30,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"-1": {
|
|
||||||
"win": 30,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"-2": {
|
|
||||||
"win": 40,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"-3": {
|
|
||||||
"win": 40,
|
|
||||||
"draw": -10
|
|
||||||
},
|
|
||||||
"-4": {
|
|
||||||
"win": 50,
|
|
||||||
"draw": -20
|
|
||||||
},
|
|
||||||
"-5": {
|
|
||||||
"win": 60,
|
|
||||||
"draw": -20
|
|
||||||
},
|
|
||||||
"-6": {
|
|
||||||
"win": 70,
|
|
||||||
"draw": -20
|
|
||||||
},
|
|
||||||
"-7": {
|
|
||||||
"win": 80,
|
|
||||||
"draw": -50
|
|
||||||
},
|
|
||||||
"-8": {
|
|
||||||
"win": 100,
|
|
||||||
"draw": -50
|
|
||||||
},
|
|
||||||
"-9": {
|
|
||||||
"win": 120,
|
|
||||||
"draw": -60
|
|
||||||
},
|
|
||||||
"-10": {
|
|
||||||
"win": 150,
|
|
||||||
"draw": -60
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"score_bonus": [
|
|
||||||
{
|
|
||||||
"min_diff": 15,
|
|
||||||
"bonus": 40
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"min_diff": 10,
|
|
||||||
"bonus": 30
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"min_diff": 6,
|
|
||||||
"bonus": 20
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"min_diff": 3,
|
|
||||||
"bonus": 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"min_diff": 0,
|
|
||||||
"bonus": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
{
|
|
||||||
"system_info": "Balancing für Warhammer 40k",
|
|
||||||
"draw_point_difference": 3,
|
|
||||||
"rank_matrix": {
|
|
||||||
"10": {
|
|
||||||
"win": 10,
|
|
||||||
"draw": 30
|
|
||||||
},
|
|
||||||
"9": {
|
|
||||||
"win": 10,
|
|
||||||
"draw": 30
|
|
||||||
},
|
|
||||||
"8": {
|
|
||||||
"win": 10,
|
|
||||||
"draw": 20
|
|
||||||
},
|
|
||||||
"7": {
|
|
||||||
"win": 10,
|
|
||||||
"draw": 20
|
|
||||||
},
|
|
||||||
"6": {
|
|
||||||
"win": 20,
|
|
||||||
"draw": 10
|
|
||||||
},
|
|
||||||
"5": {
|
|
||||||
"win": 20,
|
|
||||||
"draw": 10
|
|
||||||
},
|
|
||||||
"4": {
|
|
||||||
"win": 20,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"3": {
|
|
||||||
"win": 20,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"2": {
|
|
||||||
"win": 30,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"1": {
|
|
||||||
"win": 30,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"0": {
|
|
||||||
"win": 30,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"-1": {
|
|
||||||
"win": 30,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"-2": {
|
|
||||||
"win": 40,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"-3": {
|
|
||||||
"win": 40,
|
|
||||||
"draw": -10
|
|
||||||
},
|
|
||||||
"-4": {
|
|
||||||
"win": 50,
|
|
||||||
"draw": -20
|
|
||||||
},
|
|
||||||
"-5": {
|
|
||||||
"win": 60,
|
|
||||||
"draw": -20
|
|
||||||
},
|
|
||||||
"-6": {
|
|
||||||
"win": 70,
|
|
||||||
"draw": -20
|
|
||||||
},
|
|
||||||
"-7": {
|
|
||||||
"win": 80,
|
|
||||||
"draw": -50
|
|
||||||
},
|
|
||||||
"-8": {
|
|
||||||
"win": 100,
|
|
||||||
"draw": -50
|
|
||||||
},
|
|
||||||
"-9": {
|
|
||||||
"win": 120,
|
|
||||||
"draw": -60
|
|
||||||
},
|
|
||||||
"-10": {
|
|
||||||
"win": 150,
|
|
||||||
"draw": -60
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"score_bonus": [
|
|
||||||
{
|
|
||||||
"min_diff": 95,
|
|
||||||
"bonus": 40
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"min_diff": 85,
|
|
||||||
"bonus": 30
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"min_diff": 65,
|
|
||||||
"bonus": 20
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"min_diff": 35,
|
|
||||||
"bonus": 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"min_diff": 0,
|
|
||||||
"bonus": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
{
|
|
||||||
"system_info": "Balancing für Warhammer Age of Sigmar",
|
|
||||||
"draw_point_difference": 3,
|
|
||||||
"rank_matrix": {
|
|
||||||
"10": {
|
|
||||||
"win": 10,
|
|
||||||
"draw": 30
|
|
||||||
},
|
|
||||||
"9": {
|
|
||||||
"win": 10,
|
|
||||||
"draw": 30
|
|
||||||
},
|
|
||||||
"8": {
|
|
||||||
"win": 10,
|
|
||||||
"draw": 20
|
|
||||||
},
|
|
||||||
"7": {
|
|
||||||
"win": 10,
|
|
||||||
"draw": 20
|
|
||||||
},
|
|
||||||
"6": {
|
|
||||||
"win": 20,
|
|
||||||
"draw": 10
|
|
||||||
},
|
|
||||||
"5": {
|
|
||||||
"win": 20,
|
|
||||||
"draw": 10
|
|
||||||
},
|
|
||||||
"4": {
|
|
||||||
"win": 20,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"3": {
|
|
||||||
"win": 20,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"2": {
|
|
||||||
"win": 30,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"1": {
|
|
||||||
"win": 30,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"0": {
|
|
||||||
"win": 30,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"-1": {
|
|
||||||
"win": 30,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"-2": {
|
|
||||||
"win": 40,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"-3": {
|
|
||||||
"win": 40,
|
|
||||||
"draw": -10
|
|
||||||
},
|
|
||||||
"-4": {
|
|
||||||
"win": 50,
|
|
||||||
"draw": -20
|
|
||||||
},
|
|
||||||
"-5": {
|
|
||||||
"win": 60,
|
|
||||||
"draw": -20
|
|
||||||
},
|
|
||||||
"-6": {
|
|
||||||
"win": 70,
|
|
||||||
"draw": -20
|
|
||||||
},
|
|
||||||
"-7": {
|
|
||||||
"win": 80,
|
|
||||||
"draw": -50
|
|
||||||
},
|
|
||||||
"-8": {
|
|
||||||
"win": 100,
|
|
||||||
"draw": -50
|
|
||||||
},
|
|
||||||
"-9": {
|
|
||||||
"win": 120,
|
|
||||||
"draw": -60
|
|
||||||
},
|
|
||||||
"-10": {
|
|
||||||
"win": 150,
|
|
||||||
"draw": -60
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"score_bonus": [
|
|
||||||
{
|
|
||||||
"min_diff": 40,
|
|
||||||
"bonus": 40
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"min_diff": 30,
|
|
||||||
"bonus": 30
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"min_diff": 20,
|
|
||||||
"bonus": 20
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"min_diff": 10,
|
|
||||||
"bonus": 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"min_diff": 0,
|
|
||||||
"bonus": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
{
|
|
||||||
"system_info": "Balancing für Spearhead",
|
|
||||||
"draw_point_difference": 3,
|
|
||||||
"rank_matrix": {
|
|
||||||
"10": {
|
|
||||||
"win": 10,
|
|
||||||
"draw": 30
|
|
||||||
},
|
|
||||||
"9": {
|
|
||||||
"win": 10,
|
|
||||||
"draw": 30
|
|
||||||
},
|
|
||||||
"8": {
|
|
||||||
"win": 10,
|
|
||||||
"draw": 20
|
|
||||||
},
|
|
||||||
"7": {
|
|
||||||
"win": 10,
|
|
||||||
"draw": 20
|
|
||||||
},
|
|
||||||
"6": {
|
|
||||||
"win": 20,
|
|
||||||
"draw": 10
|
|
||||||
},
|
|
||||||
"5": {
|
|
||||||
"win": 20,
|
|
||||||
"draw": 10
|
|
||||||
},
|
|
||||||
"4": {
|
|
||||||
"win": 20,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"3": {
|
|
||||||
"win": 20,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"2": {
|
|
||||||
"win": 30,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"1": {
|
|
||||||
"win": 30,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"0": {
|
|
||||||
"win": 30,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"-1": {
|
|
||||||
"win": 30,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"-2": {
|
|
||||||
"win": 40,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"-3": {
|
|
||||||
"win": 40,
|
|
||||||
"draw": -10
|
|
||||||
},
|
|
||||||
"-4": {
|
|
||||||
"win": 50,
|
|
||||||
"draw": -20
|
|
||||||
},
|
|
||||||
"-5": {
|
|
||||||
"win": 60,
|
|
||||||
"draw": -20
|
|
||||||
},
|
|
||||||
"-6": {
|
|
||||||
"win": 70,
|
|
||||||
"draw": -20
|
|
||||||
},
|
|
||||||
"-7": {
|
|
||||||
"win": 80,
|
|
||||||
"draw": -50
|
|
||||||
},
|
|
||||||
"-8": {
|
|
||||||
"win": 100,
|
|
||||||
"draw": -50
|
|
||||||
},
|
|
||||||
"-9": {
|
|
||||||
"win": 120,
|
|
||||||
"draw": -60
|
|
||||||
},
|
|
||||||
"-10": {
|
|
||||||
"win": 150,
|
|
||||||
"draw": -60
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"score_bonus": [
|
|
||||||
{
|
|
||||||
"min_diff": 95,
|
|
||||||
"bonus": 40
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"min_diff": 85,
|
|
||||||
"bonus": 30
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"min_diff": 65,
|
|
||||||
"bonus": 20
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"min_diff": 35,
|
|
||||||
"bonus": 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"min_diff": 0,
|
|
||||||
"bonus": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
{
|
|
||||||
"system_info": "Balancing für Warhammer 40k",
|
|
||||||
"draw_point_difference": 3,
|
|
||||||
"rank_matrix": {
|
|
||||||
"10": {
|
|
||||||
"win": 10,
|
|
||||||
"draw": 30
|
|
||||||
},
|
|
||||||
"9": {
|
|
||||||
"win": 10,
|
|
||||||
"draw": 30
|
|
||||||
},
|
|
||||||
"8": {
|
|
||||||
"win": 10,
|
|
||||||
"draw": 20
|
|
||||||
},
|
|
||||||
"7": {
|
|
||||||
"win": 10,
|
|
||||||
"draw": 20
|
|
||||||
},
|
|
||||||
"6": {
|
|
||||||
"win": 20,
|
|
||||||
"draw": 10
|
|
||||||
},
|
|
||||||
"5": {
|
|
||||||
"win": 20,
|
|
||||||
"draw": 10
|
|
||||||
},
|
|
||||||
"4": {
|
|
||||||
"win": 20,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"3": {
|
|
||||||
"win": 20,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"2": {
|
|
||||||
"win": 30,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"1": {
|
|
||||||
"win": 30,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"0": {
|
|
||||||
"win": 30,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"-1": {
|
|
||||||
"win": 30,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"-2": {
|
|
||||||
"win": 40,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"-3": {
|
|
||||||
"win": 40,
|
|
||||||
"draw": -10
|
|
||||||
},
|
|
||||||
"-4": {
|
|
||||||
"win": 50,
|
|
||||||
"draw": -20
|
|
||||||
},
|
|
||||||
"-5": {
|
|
||||||
"win": 60,
|
|
||||||
"draw": -20
|
|
||||||
},
|
|
||||||
"-6": {
|
|
||||||
"win": 70,
|
|
||||||
"draw": -20
|
|
||||||
},
|
|
||||||
"-7": {
|
|
||||||
"win": 80,
|
|
||||||
"draw": -50
|
|
||||||
},
|
|
||||||
"-8": {
|
|
||||||
"win": 100,
|
|
||||||
"draw": -50
|
|
||||||
},
|
|
||||||
"-9": {
|
|
||||||
"win": 120,
|
|
||||||
"draw": -60
|
|
||||||
},
|
|
||||||
"-10": {
|
|
||||||
"win": 150,
|
|
||||||
"draw": -60
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"score_bonus": [
|
|
||||||
{
|
|
||||||
"min_diff": 95,
|
|
||||||
"bonus": 40
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"min_diff": 85,
|
|
||||||
"bonus": 30
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"min_diff": 65,
|
|
||||||
"bonus": 20
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"min_diff": 35,
|
|
||||||
"bonus": 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"min_diff": 0,
|
|
||||||
"bonus": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
{
|
|
||||||
"system_info": "Balancing für Warhammer Age of Sigmar",
|
|
||||||
"draw_point_difference": 3,
|
|
||||||
"rank_matrix": {
|
|
||||||
"10": {
|
|
||||||
"win": 10,
|
|
||||||
"draw": 30
|
|
||||||
},
|
|
||||||
"9": {
|
|
||||||
"win": 10,
|
|
||||||
"draw": 30
|
|
||||||
},
|
|
||||||
"8": {
|
|
||||||
"win": 10,
|
|
||||||
"draw": 20
|
|
||||||
},
|
|
||||||
"7": {
|
|
||||||
"win": 10,
|
|
||||||
"draw": 20
|
|
||||||
},
|
|
||||||
"6": {
|
|
||||||
"win": 20,
|
|
||||||
"draw": 10
|
|
||||||
},
|
|
||||||
"5": {
|
|
||||||
"win": 20,
|
|
||||||
"draw": 10
|
|
||||||
},
|
|
||||||
"4": {
|
|
||||||
"win": 20,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"3": {
|
|
||||||
"win": 20,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"2": {
|
|
||||||
"win": 30,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"1": {
|
|
||||||
"win": 30,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"0": {
|
|
||||||
"win": 30,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"-1": {
|
|
||||||
"win": 30,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"-2": {
|
|
||||||
"win": 40,
|
|
||||||
"draw": 0
|
|
||||||
},
|
|
||||||
"-3": {
|
|
||||||
"win": 40,
|
|
||||||
"draw": -10
|
|
||||||
},
|
|
||||||
"-4": {
|
|
||||||
"win": 50,
|
|
||||||
"draw": -20
|
|
||||||
},
|
|
||||||
"-5": {
|
|
||||||
"win": 60,
|
|
||||||
"draw": -20
|
|
||||||
},
|
|
||||||
"-6": {
|
|
||||||
"win": 70,
|
|
||||||
"draw": -20
|
|
||||||
},
|
|
||||||
"-7": {
|
|
||||||
"win": 80,
|
|
||||||
"draw": -50
|
|
||||||
},
|
|
||||||
"-8": {
|
|
||||||
"win": 100,
|
|
||||||
"draw": -50
|
|
||||||
},
|
|
||||||
"-9": {
|
|
||||||
"win": 120,
|
|
||||||
"draw": -60
|
|
||||||
},
|
|
||||||
"-10": {
|
|
||||||
"win": 150,
|
|
||||||
"draw": -60
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"score_bonus": [
|
|
||||||
{
|
|
||||||
"min_diff": 95,
|
|
||||||
"bonus": 40
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"min_diff": 85,
|
|
||||||
"bonus": 30
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"min_diff": 65,
|
|
||||||
"bonus": 20
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"min_diff": 35,
|
|
||||||
"bonus": 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"min_diff": 0,
|
|
||||||
"bonus": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user